Skip to content

Commit afdc45b

Browse files
committed
fix(visual-edits): limita contribuintes ao escopo do elemento clicado
Ao clicar num elemento no preview, o dropdown listava todos os contribuintes da cadeia de concatenação inteira (ex.: o VAR completo onde a expressão estava), incluindo divs/spans irmãos que não fazem parte do trecho clicado. Agora, ao indexar contribuintes, balanceamos abre/fecha da tag no source a partir do literal de abertura para delimitar o range exato do elemento. Apenas literais e refs de VAR cujo refLoc/loc cai dentro desse range são mantidos. O `rootLoc` (usado por "Trecho HTML final") também passa a apontar para o range do elemento, não para a cadeia inteira.
1 parent dc05f0f commit afdc45b

1 file changed

Lines changed: 65 additions & 28 deletions

File tree

src/lib/daxParser/varDependencies.ts

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,52 @@ import type { Contributor, ContributorIndex, ContributorsEntry, Node, SourceLoc
1111
const OPEN_TAG_RE = /<([a-zA-Z][a-zA-Z0-9-]*)(\s|\/|>)/;
1212
const TAG_SPLIT_RE = /<\/?[a-zA-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+
1460
interface 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-
182205
export 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

Comments
 (0)