@@ -33,16 +33,18 @@ export function isParent(node: Node): node is ParentNode {
3333export function walkElements (
3434 parent : ParentNode ,
3535 code : string ,
36- cb : ( n : Node , parents : ParentNode [ ] ) => void ,
36+ enter : ( n : Node , parents : ParentNode [ ] ) => void ,
37+ leave : ( n : Node , parents : ParentNode [ ] ) => void ,
3738 parents : ParentNode [ ] = [ ] ,
3839) : void {
3940 const children = getSortedChildren ( parent , code )
4041 const currParents = [ parent , ...parents ]
4142 for ( const node of children ) {
42- cb ( node , currParents )
43+ enter ( node , currParents )
4344 if ( isParent ( node ) ) {
44- walkElements ( node , code , cb , currParents )
45+ walkElements ( node , code , enter , leave , currParents )
4546 }
47+ leave ( node , currParents )
4648 }
4749}
4850
@@ -51,25 +53,23 @@ export function walk(
5153 parent : ParentNode ,
5254 code : string ,
5355 enter : ( n : Node | AttributeNode , parents : ParentNode [ ] ) => void ,
54- leave ?: ( n : Node | AttributeNode , parents : ParentNode [ ] ) => void ,
55- parents : ParentNode [ ] = [ ] ,
56+ leave : ( n : Node | AttributeNode , parents : ParentNode [ ] ) => void ,
5657) : void {
57- const children = getSortedChildren ( parent , code )
58- const currParents = [ parent , ...parents ]
59- for ( const node of children ) {
60- enter ( node , currParents )
61- if ( isTag ( node ) ) {
62- const attrParents = [ node , ...currParents ]
63- for ( const attr of node . attributes ) {
64- enter ( attr , attrParents )
65- leave ?.( attr , attrParents )
58+ walkElements (
59+ parent ,
60+ code ,
61+ ( node , parents ) => {
62+ enter ( node , parents )
63+ if ( isTag ( node ) ) {
64+ const attrParents = [ node , ...parents ]
65+ for ( const attr of node . attributes ) {
66+ enter ( attr , attrParents )
67+ leave ( attr , attrParents )
68+ }
6669 }
67- }
68- if ( isParent ( node ) ) {
69- walk ( node , code , enter , leave , currParents )
70- }
71- leave ?.( node , currParents )
72- }
70+ } ,
71+ leave ,
72+ )
7373}
7474
7575/**
@@ -103,44 +103,21 @@ export function getTagEndOffset(
103103 if ( node . position ! . end ?. offset != null ) {
104104 return node . position ! . end . offset
105105 }
106+ let beforeIndex : number
106107 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 )
108+ const lastChild = node . children [ node . children . length - 1 ]
109+ beforeIndex = getEndOffset ( lastChild , [ node , ...parents ] , ctx )
110+ } else {
111+ beforeIndex = getStartTagEndOffset ( node , ctx )
133112 }
134-
135- let beforeIndex = getStartTagEndOffset ( node , ctx )
136113 beforeIndex = skipSpaces ( ctx . code , beforeIndex )
137114
138115 if ( ctx . code . startsWith ( `</${ node . name } ` , beforeIndex ) ) {
139116 beforeIndex = beforeIndex + 2 + node . name . length
140117 const info = getTokenInfo ( ctx , [ ">" ] , beforeIndex )
141118 return info . index + info . match . length
142119 }
143- return ctx . code . length
120+ return beforeIndex
144121}
145122
146123/**
@@ -155,32 +132,10 @@ export function getExpressionEndOffset(
155132 return node . position ! . end . offset
156133 }
157134 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 )
135+ const lastChild = node . children [ node . children . length - 1 ]
136+ const beforeIndex = getEndOffset ( lastChild , [ node , ...parents ] , ctx )
137+ const info = getTokenInfo ( ctx , [ "}" ] , beforeIndex )
138+ return info . index + info . match . length
184139 }
185140 const info = getTokenInfo ( ctx , [ "{" , "}" ] , node . position ! . start . offset )
186141 return info . index + info . match . length
@@ -285,7 +240,35 @@ export function getCommentEndOffset(node: CommentNode, ctx: Context): number {
285240}
286241
287242/**
288- * If the given tag is a void tag, get the self-closing tag.
243+ * Get content end offset
244+ */
245+ export function getContentEndOffset (
246+ parent : ParentNode ,
247+ parents : ParentNode [ ] ,
248+ ctx : Context ,
249+ ) : number {
250+ const code = ctx . code
251+ if ( isTag ( parent ) ) {
252+ const end = getTagEndOffset ( parent , parents , ctx )
253+ if ( code [ end - 1 ] !== ">" ) {
254+ return end
255+ }
256+ const index = code . lastIndexOf ( "</" , end )
257+ if ( index >= 0 && code . slice ( index , end ) . trim ( ) === parent . name ) {
258+ return index
259+ }
260+ return end
261+ } else if ( parent . type === "expression" ) {
262+ const end = getExpressionEndOffset ( parent , parents , ctx )
263+ return code . lastIndexOf ( "}" , end )
264+ } else if ( parent . type === "root" ) {
265+ return code . length
266+ }
267+ throw new Error ( `unknown type: ${ ( parent as any ) . type } ` )
268+ }
269+
270+ /**
271+ * If the given tag is a self-close tag, get the self-closing tag.
289272 */
290273export function getSelfClosingTag (
291274 node : TagLikeNode ,
@@ -307,17 +290,7 @@ export function getSelfClosingTag(
307290 const childIndex = parent . children . indexOf ( node )
308291 if ( childIndex === parent . children . length - 1 ) {
309292 // 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- }
293+ nextElementIndex = getContentEndOffset ( parent , parents . slice ( 1 ) , ctx )
321294 } else {
322295 const next = parent . children [ childIndex + 1 ]
323296 nextElementIndex = next . position ! . start . offset
@@ -332,6 +305,68 @@ export function getSelfClosingTag(
332305 end : code . slice ( endOffset - 2 , endOffset ) === "/>" ? "/>" : ">" ,
333306 }
334307}
308+ /**
309+ * If the given tag has a end tag, get the end tag.
310+ */
311+ export function getEndTag (
312+ node : TagLikeNode ,
313+ parents : ParentNode [ ] ,
314+ ctx : Context ,
315+ ) : null | {
316+ offset : number
317+ tag : string
318+ } {
319+ let beforeIndex : number
320+ if ( node . children . length ) {
321+ const lastChild = node . children [ node . children . length - 1 ]
322+ beforeIndex = getEndOffset ( lastChild , [ node , ...parents ] , ctx )
323+ } else {
324+ beforeIndex = getStartTagEndOffset ( node , ctx )
325+ }
326+ beforeIndex = skipSpaces ( ctx . code , beforeIndex )
327+
328+ if ( ctx . code . startsWith ( `</${ node . name } ` , beforeIndex ) ) {
329+ const offset = beforeIndex
330+ beforeIndex = beforeIndex + 2 + node . name . length
331+ const info = getTokenInfo ( ctx , [ ">" ] , beforeIndex )
332+ const end = info . index + info . match . length
333+ return {
334+ offset,
335+ tag : ctx . code . slice ( offset , end ) ,
336+ }
337+ }
338+ return null
339+ }
340+
341+ /**
342+ * Get end offset of tag
343+ */
344+ function getEndOffset ( node : Node , parents : ParentNode [ ] , ctx : Context ) : number {
345+ if ( node . position ! . end ?. offset != null ) {
346+ return node . position ! . end . offset
347+ }
348+ if ( isTag ( node ) ) return getTagEndOffset ( node , parents , ctx )
349+ if ( node . type === "expression" )
350+ return getExpressionEndOffset ( node , parents , ctx )
351+ if ( node . type === "comment" ) return getCommentEndOffset ( node , ctx )
352+ if ( node . type === "frontmatter" ) {
353+ const start = node . position ! . start . offset
354+ return ctx . code . indexOf ( "---" , start + 3 ) + 3
355+ }
356+ if ( node . type === "doctype" ) {
357+ const start = node . position ! . start . offset
358+ return ctx . code . indexOf ( ">" , start ) + 1
359+ }
360+ if ( node . type === "text" ) {
361+ const start = node . position ! . start . offset
362+ return start + node . value . length
363+ }
364+ if ( node . type === "root" ) {
365+ return ctx . code . length
366+ }
367+
368+ throw new Error ( `unknown type: ${ ( node as any ) . type } ` )
369+ }
335370
336371/**
337372 * Get token info
0 commit comments