@@ -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 {
3233export 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(
4750export 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 */
0 commit comments