80 lines
2.7 KiB
TypeScript
80 lines
2.7 KiB
TypeScript
const ALLOWED_ATTRS = ['data-dev-name', 'key', 'id', 'name', 'type', 'icon', 'role'];
|
|
const SNIPPET_MAX = 24;
|
|
|
|
export interface SignatureNodeChild {
|
|
type: 'text' | 'interpolation' | 'element' | 'comment';
|
|
content?: string;
|
|
}
|
|
|
|
export interface SignatureNodeProp {
|
|
type: 'attribute' | 'directive';
|
|
name: string;
|
|
value?: string;
|
|
arg?: string;
|
|
exp?: string;
|
|
}
|
|
|
|
export interface SignatureNode {
|
|
tag: string;
|
|
props: SignatureNodeProp[];
|
|
children: SignatureNodeChild[];
|
|
}
|
|
|
|
export interface SignatureContext {
|
|
filePath: string; // absolute or relative — anything load-time-stable
|
|
appRoot: string; // e.g. 'app/', used by normalizeFilePath
|
|
ancestorChain: string[]; // ['DeclaringComponent', 'div', 'nav']
|
|
sameTagOrdinal: number; // 0-based among siblings of same tag in same parent
|
|
}
|
|
|
|
export function normalizeFilePath(filePath: string, appRoot: string): string {
|
|
const normalized = filePath.replace(/\\/g, '/').replace(/\.vue$/, '');
|
|
const root = appRoot.replace(/\\/g, '/').replace(/\/$/, '') + '/';
|
|
return normalized.startsWith(root) ? normalized.slice(root.length) : normalized;
|
|
}
|
|
|
|
export function extractStaticText(node: SignatureNode): string | null {
|
|
const parts: string[] = [];
|
|
for (const child of node.children) {
|
|
if (child.type === 'text' && child.content) {
|
|
parts.push(child.content);
|
|
}
|
|
}
|
|
if (parts.length === 0) return null;
|
|
const merged = parts.join(' ').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
if (!merged) return null;
|
|
return merged.slice(0, SNIPPET_MAX);
|
|
}
|
|
|
|
export function extractDistinctiveAttrs(node: SignatureNode): string {
|
|
const pairs: string[] = [];
|
|
for (const prop of node.props) {
|
|
if (prop.type !== 'attribute') continue; // skip directives / v-bind
|
|
if (!ALLOWED_ATTRS.includes(prop.name)) continue;
|
|
if (prop.value == null) continue;
|
|
pairs.push(`${prop.name}=${prop.value}`);
|
|
}
|
|
return pairs.sort().join(',');
|
|
}
|
|
|
|
function findDevName(node: SignatureNode): string | null {
|
|
for (const prop of node.props) {
|
|
if (prop.type === 'attribute' && prop.name === 'data-dev-name' && prop.value) {
|
|
return prop.value;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function computeSignature(node: SignatureNode, ctx: SignatureContext): string {
|
|
const path = normalizeFilePath(ctx.filePath, ctx.appRoot);
|
|
const devName = findDevName(node);
|
|
if (devName) {
|
|
return `${path}::data-dev-name::${devName}`;
|
|
}
|
|
const ancestors = ctx.ancestorChain.join('>');
|
|
const attrs = extractDistinctiveAttrs(node);
|
|
const text = extractStaticText(node) ?? '';
|
|
return `${path}::${ancestors}::${node.tag}[${attrs}]::${text}::${ctx.sameTagOrdinal}`;
|
|
}
|