@@ -11,6 +11,52 @@ import type { Contributor, ContributorIndex, ContributorsEntry, Node, SourceLoc
1111const OPEN_TAG_RE = / < ( [ a - z A - Z ] [ a - z A - Z 0 - 9 - ] * ) ( \s | \/ | > ) / ;
1212const TAG_SPLIT_RE = / < \/ ? [ a - z A - Z ] [ ^ > ] * > | < ! - - [ \s \S ] * ?- - > / g;
1313
14+ // Elementos void/auto-fechados: não possuem conteúdo nem tag de fechamento.
15+ const VOID_ELEMENTS = new Set ( [
16+ 'area' , 'base' , 'br' , 'col' , 'embed' , 'hr' , 'img' , 'input' ,
17+ 'keygen' , 'link' , 'meta' , 'param' , 'source' , 'track' , 'wbr' ,
18+ ] ) ;
19+
20+ function locOfContributor ( c : Contributor ) : SourceLoc {
21+ if ( c . kind === 'var' ) return c . refLoc ;
22+ return c . loc ;
23+ }
24+
25+ // Dado o literal que abre uma tag HTML, varre o source à frente balanceando
26+ // abre/fecha da mesma tag para localizar o ponto onde o elemento termina.
27+ // Retorna a posição no source logo após o ">" do fechamento (ou o final do
28+ // source se não houver correspondência).
29+ function findElementEnd ( src : string , openLoc : SourceLoc , tagName : string ) : number {
30+ if ( VOID_ELEMENTS . has ( tagName ) ) return openLoc . end ;
31+ const re = new RegExp ( `<(/?)${ tagName } \\b[^>]*?(/?)>` , 'gi' ) ;
32+ re . lastIndex = openLoc . end ;
33+ let depth = 1 ;
34+ let m : RegExpExecArray | null ;
35+ while ( ( m = re . exec ( src ) ) !== null ) {
36+ const isClose = m [ 1 ] === '/' ;
37+ const selfClose = m [ 2 ] === '/' ;
38+ if ( isClose ) {
39+ depth -- ;
40+ if ( depth === 0 ) {
41+ let end = m . index + m [ 0 ] . length ;
42+ // Inclui a aspas de fechamento do literal, se presente, para que o
43+ // range cubra `"</tag>"` inteiro no source.
44+ if ( src [ end ] === '"' || src [ end ] === "'" ) end ++ ;
45+ return end ;
46+ }
47+ } else if ( ! selfClose ) {
48+ depth ++ ;
49+ }
50+ }
51+ return src . length ;
52+ }
53+
54+ function extractOpenTagName ( src : string , openLoc : SourceLoc ) : string | null {
55+ const content = src . slice ( openLoc . start + 1 , openLoc . end - 1 ) ;
56+ const m = OPEN_TAG_RE . exec ( content ) ;
57+ return m ? m [ 1 ] . toLowerCase ( ) : null ;
58+ }
59+
1460interface VarRecord {
1561 declLoc : SourceLoc ;
1662}
@@ -156,29 +202,6 @@ function collectContributors(
156202 }
157203}
158204
159- function rangeOf ( node : Node ) : SourceLoc | undefined {
160- if ( node . loc ) return node . loc ;
161- switch ( node . kind ) {
162- case 'binop' : {
163- const l = rangeOf ( node . left ) ;
164- const r = rangeOf ( node . right ) ;
165- if ( l && r ) return { start : l . start , end : r . end } ;
166- return l ?? r ;
167- }
168- case 'unop' :
169- return rangeOf ( node . arg ) ;
170- case 'call' :
171- if ( node . args . length > 0 ) {
172- const first = rangeOf ( node . args [ 0 ] ) ;
173- const last = rangeOf ( node . args [ node . args . length - 1 ] ) ;
174- if ( first && last ) return { start : first . start , end : last . end } ;
175- }
176- return undefined ;
177- default :
178- return undefined ;
179- }
180- }
181-
182205export function buildContributorIndex ( ast : Node , src : string ) : ContributorIndex {
183206 const index : ContributorIndex = { } ;
184207
@@ -189,12 +212,26 @@ export function buildContributorIndex(ast: Node, src: string): ContributorIndex
189212 if ( ! OPEN_TAG_RE . test ( n . value ) ) return ;
190213 const key = `${ n . loc . start } -${ n . loc . end } ` ;
191214 if ( index [ key ] ) return ; // already indexed (same literal seen via outer walk)
192- const items : Contributor [ ] = [ ] ;
193- collectContributors ( concatRoot , scopes , src , items ) ;
194- const root = rangeOf ( concatRoot ) ?? n . loc ;
215+
216+ // Coleta candidatos a partir da cadeia de concatenação inteira, mas
217+ // mantém apenas os que estão DENTRO do range de source do elemento
218+ // clicado — i.e., entre seu literal de abertura e o `</tag>` que o
219+ // fecha, balanceando aninhamento da mesma tag.
220+ const all : Contributor [ ] = [ ] ;
221+ collectContributors ( concatRoot , scopes , src , all ) ;
222+
223+ const tagName = extractOpenTagName ( src , n . loc ) ;
224+ const elemEnd = tagName ? findElementEnd ( src , n . loc , tagName ) : n . loc . end ;
225+ const elemLoc : SourceLoc = { start : n . loc . start , end : elemEnd } ;
226+
227+ const items = all . filter ( ( c ) => {
228+ const l = locOfContributor ( c ) ;
229+ return l . start >= elemLoc . start && l . end <= elemLoc . end ;
230+ } ) ;
231+
195232 const entry : ContributorsEntry = {
196- rootLoc : root ,
197- rootLine : lineOf ( src , root . start ) ,
233+ rootLoc : elemLoc ,
234+ rootLine : lineOf ( src , elemLoc . start ) ,
198235 items,
199236 } ;
200237 index [ key ] = entry ;
0 commit comments