Skip to content

Commit a7cd452

Browse files
committed
Support expression fragment
1 parent 80ce4ad commit a7cd452

15 files changed

+19265
-57
lines changed

src/ast.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export type AstroNode =
88
| AstroShorthandAttribute
99
| AstroTemplateLiteralAttribute
1010
| AstroRawText
11+
| AstroFragment
1112

1213
/** Node of Astro program root */
1314
export interface AstroProgram extends Omit<TSESTree.Program, "type" | "body"> {
@@ -66,3 +67,10 @@ export interface AstroRawText
6667
type: "AstroRawText"
6768
parent: AstroRootFragment | TSESTree.JSXElement | TSESTree.JSXFragment
6869
}
70+
/** Node of Astro fragment expression */
71+
export interface AstroFragment
72+
extends Omit<TSESTree.BaseNode, "type" | "parent"> {
73+
type: "AstroFragment"
74+
children: TSESTree.JSXFragment["children"]
75+
parent: TSESTree.JSXFragment["parent"]
76+
}

src/astro/index.ts

Lines changed: 158 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
AttributeNode,
33
CommentNode,
44
DoctypeNode,
5+
ExpressionNode,
56
Node,
67
ParentNode,
78
TagLikeNode,
@@ -32,13 +33,15 @@ export function isParent(node: Node): node is ParentNode {
3233
export function walkElements(
3334
parent: ParentNode,
3435
code: string,
35-
cb: (n: Node, parent: ParentNode) => void,
36+
cb: (n: Node, parents: ParentNode[]) => void,
37+
parents: ParentNode[] = [],
3638
): void {
3739
const children = getSortedChildren(parent, code)
40+
const currParents = [parent, ...parents]
3841
for (const node of children) {
39-
cb(node, parent)
42+
cb(node, currParents)
4043
if (isParent(node)) {
41-
walkElements(node, code, cb)
44+
walkElements(node, code, cb, currParents)
4245
}
4346
}
4447
}
@@ -47,22 +50,25 @@ export function walkElements(
4750
export function walk(
4851
parent: ParentNode,
4952
code: string,
50-
enter: (n: Node | AttributeNode, parent: ParentNode) => void,
51-
leave?: (n: Node | AttributeNode, parent: ParentNode) => void,
53+
enter: (n: Node | AttributeNode, parents: ParentNode[]) => void,
54+
leave?: (n: Node | AttributeNode, parents: ParentNode[]) => void,
55+
parents: ParentNode[] = [],
5256
): void {
5357
const children = getSortedChildren(parent, code)
58+
const currParents = [parent, ...parents]
5459
for (const node of children) {
55-
enter(node, parent)
60+
enter(node, currParents)
5661
if (isTag(node)) {
62+
const attrParents = [node, ...currParents]
5763
for (const attr of node.attributes) {
58-
enter(attr, node)
59-
leave?.(attr, node)
64+
enter(attr, attrParents)
65+
leave?.(attr, attrParents)
6066
}
6167
}
6268
if (isParent(node)) {
63-
walk(node, code, enter, leave)
69+
walk(node, code, enter, leave, currParents)
6470
}
65-
leave?.(node, parent)
71+
leave?.(node, currParents)
6672
}
6773
}
6874

@@ -86,6 +92,99 @@ export function getStartTagEndOffset(node: TagLikeNode, ctx: Context): number {
8692
return info.index + info.match.length
8793
}
8894

95+
/**
96+
* Get end offset of tag
97+
*/
98+
export function getTagEndOffset(
99+
node: TagLikeNode,
100+
parents: ParentNode[],
101+
ctx: Context,
102+
): number {
103+
if (node.position!.end?.offset != null) {
104+
return node.position!.end.offset
105+
}
106+
if (node.children.length) {
107+
const code = ctx.code
108+
let nextElementIndex = code.length
109+
const parent = parents[0]
110+
const childIndex = parent.children.indexOf(node)
111+
if (childIndex === parent.children.length - 1) {
112+
// last
113+
if (isTag(parent)) {
114+
nextElementIndex = getTagEndOffset(
115+
parent,
116+
parents.slice(1),
117+
ctx,
118+
)
119+
nextElementIndex = code.lastIndexOf("</", nextElementIndex)
120+
} else if (parent.type === "expression") {
121+
nextElementIndex = getExpressionEndOffset(
122+
parent,
123+
parents.slice(1),
124+
ctx,
125+
)
126+
nextElementIndex = code.lastIndexOf("}", nextElementIndex)
127+
}
128+
} else {
129+
const next = parent.children[childIndex + 1]
130+
nextElementIndex = next.position!.start.offset
131+
}
132+
return code.lastIndexOf(">", nextElementIndex)
133+
}
134+
135+
let beforeIndex = getStartTagEndOffset(node, ctx)
136+
beforeIndex = skipSpaces(ctx.code, beforeIndex)
137+
138+
if (ctx.code.startsWith(`</${node.name}`, beforeIndex)) {
139+
beforeIndex = beforeIndex + 2 + node.name.length
140+
const info = getTokenInfo(ctx, [">"], beforeIndex)
141+
return info.index + info.match.length
142+
}
143+
return ctx.code.length
144+
}
145+
146+
/**
147+
* Get end offset of Expression
148+
*/
149+
export function getExpressionEndOffset(
150+
node: ExpressionNode,
151+
parents: ParentNode[],
152+
ctx: Context,
153+
): number {
154+
if (node.position!.end?.offset != null) {
155+
return node.position!.end.offset
156+
}
157+
if (node.children.length) {
158+
const code = ctx.code
159+
let nextElementIndex = code.length
160+
const parent = parents[0]
161+
const childIndex = parent.children.indexOf(node)
162+
if (childIndex === parent.children.length - 1) {
163+
// last
164+
if (isTag(parent)) {
165+
nextElementIndex = getTagEndOffset(
166+
parent,
167+
parents.slice(1),
168+
ctx,
169+
)
170+
nextElementIndex = code.lastIndexOf("</", nextElementIndex)
171+
} else if (parent.type === "expression") {
172+
nextElementIndex = getExpressionEndOffset(
173+
parent,
174+
parents.slice(1),
175+
ctx,
176+
)
177+
nextElementIndex = code.lastIndexOf("}", nextElementIndex)
178+
}
179+
} else {
180+
const next = parent.children[childIndex + 1]
181+
nextElementIndex = next.position!.start.offset
182+
}
183+
return code.lastIndexOf("}", nextElementIndex)
184+
}
185+
const info = getTokenInfo(ctx, ["{", "}"], node.position!.start.offset)
186+
return info.index + info.match.length
187+
}
89188
/**
90189
* Get end offset of attribute
91190
*/
@@ -185,6 +284,55 @@ export function getCommentEndOffset(node: CommentNode, ctx: Context): number {
185284
return info.index + info.match.length
186285
}
187286

287+
/**
288+
* If the given tag is a void tag, get the self-closing tag.
289+
*/
290+
export function getSelfClosingTag(
291+
node: TagLikeNode,
292+
parents: ParentNode[],
293+
ctx: Context,
294+
): null | {
295+
offset: number
296+
end: "/>" | ">"
297+
} {
298+
const children = node.children.filter(
299+
(c) => c.type !== "text" || c.value.trim(),
300+
)
301+
if (children.length > 0) {
302+
return null
303+
}
304+
const parent = parents[0]
305+
const code = ctx.code
306+
let nextElementIndex = code.length
307+
const childIndex = parent.children.indexOf(node)
308+
if (childIndex === parent.children.length - 1) {
309+
// last
310+
if (isTag(parent)) {
311+
nextElementIndex = getTagEndOffset(parent, parents.slice(1), ctx)
312+
nextElementIndex = code.lastIndexOf("</", nextElementIndex)
313+
} else if (parent.type === "expression") {
314+
nextElementIndex = getExpressionEndOffset(
315+
parent,
316+
parents.slice(1),
317+
ctx,
318+
)
319+
nextElementIndex = code.lastIndexOf("}", nextElementIndex)
320+
}
321+
} else {
322+
const next = parent.children[childIndex + 1]
323+
nextElementIndex = next.position!.start.offset
324+
}
325+
const endOffset = getStartTagEndOffset(node, ctx)
326+
if (code.slice(endOffset, nextElementIndex).trim()) {
327+
// has end tag
328+
return null
329+
}
330+
return {
331+
offset: endOffset,
332+
end: code.slice(endOffset - 2, endOffset) === "/>" ? "/>" : ">",
333+
}
334+
}
335+
188336
/**
189337
* Get token info
190338
*/

src/parser/astro-parser/parse.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ function fixLocations(node: ParentNode, ctx: Context): void {
128128
node,
129129
ctx.code,
130130
// eslint-disable-next-line complexity -- X(
131-
(node, parent) => {
131+
(node, [parent]) => {
132132
if (node.type === "frontmatter") {
133133
start = node.position!.start.offset = tokenIndex(
134134
ctx,
@@ -243,7 +243,7 @@ function fixLocations(node: ParentNode, ctx: Context): void {
243243
// noop
244244
}
245245
},
246-
(node, parent) => {
246+
(node, [parent]) => {
247247
if (node.type === "attribute") {
248248
const attributes = (parent as TagLikeNode).attributes
249249
if (attributes[attributes.length - 1] === node) {

0 commit comments

Comments
 (0)