Files
portal/app/vite-plugins/dev-indices/signature.ts
T

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}`;
}