@dcoder-x/plugin-shared 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -19,13 +19,32 @@ class ComponentExtractor {
|
|
|
19
19
|
}
|
|
20
20
|
async extract() {
|
|
21
21
|
const elements = [];
|
|
22
|
+
// Build a direct filePath → route.path index so that files which are
|
|
23
|
+
// themselves route entries get their own route, not whichever layout or
|
|
24
|
+
// parent route happened to import them first.
|
|
25
|
+
const fileRouteIndex = new Map();
|
|
26
|
+
for (const route of this.routes) {
|
|
27
|
+
const relFwd = route.filePath.replace(/\\/g, '/');
|
|
28
|
+
fileRouteIndex.set(relFwd, route.path);
|
|
29
|
+
// Also index by just the basename-less relative path variations
|
|
30
|
+
fileRouteIndex.set(route.filePath, route.path);
|
|
31
|
+
}
|
|
32
|
+
const seen = new Set(); // deduplicate across routes
|
|
22
33
|
for (const route of this.routes) {
|
|
23
34
|
const moduleFiles = this.getModuleFilesForRoute(route.filePath);
|
|
24
35
|
for (const filePath of moduleFiles) {
|
|
36
|
+
if (seen.has(filePath))
|
|
37
|
+
continue;
|
|
38
|
+
seen.add(filePath);
|
|
25
39
|
const source = this.readSource(filePath);
|
|
26
40
|
if (!source)
|
|
27
41
|
continue;
|
|
28
|
-
|
|
42
|
+
// Prefer the route this file is directly registered as; fall back to
|
|
43
|
+
// the route whose module graph we're walking (the layout/parent route).
|
|
44
|
+
const assignedRoute = fileRouteIndex.get(filePath) ??
|
|
45
|
+
fileRouteIndex.get(filePath.replace(/\\/g, '/')) ??
|
|
46
|
+
route.path;
|
|
47
|
+
elements.push(...this.extractFromSource(source, filePath, assignedRoute));
|
|
29
48
|
}
|
|
30
49
|
}
|
|
31
50
|
return elements;
|
|
@@ -38,12 +57,24 @@ class ComponentExtractor {
|
|
|
38
57
|
const components = [];
|
|
39
58
|
const contextResolver = new ComponentContextResolver_1.ComponentContextResolver();
|
|
40
59
|
const graphExtractor = new InteractionGraphExtractor_1.InteractionGraphExtractor();
|
|
60
|
+
const fileRouteIndex = new Map();
|
|
61
|
+
for (const route of this.routes) {
|
|
62
|
+
fileRouteIndex.set(route.filePath, route.path);
|
|
63
|
+
fileRouteIndex.set(route.filePath.replace(/\\/g, '/'), route.path);
|
|
64
|
+
}
|
|
65
|
+
const seen = new Set();
|
|
41
66
|
for (const route of this.routes) {
|
|
42
67
|
const moduleFiles = this.getModuleFilesForRoute(route.filePath);
|
|
43
68
|
for (const filePath of moduleFiles) {
|
|
69
|
+
if (seen.has(filePath))
|
|
70
|
+
continue;
|
|
71
|
+
seen.add(filePath);
|
|
44
72
|
const source = this.readSource(filePath);
|
|
45
73
|
if (!source)
|
|
46
74
|
continue;
|
|
75
|
+
const assignedRoute = fileRouteIndex.get(filePath) ??
|
|
76
|
+
fileRouteIndex.get(filePath.replace(/\\/g, '/')) ??
|
|
77
|
+
route.path;
|
|
47
78
|
let ast;
|
|
48
79
|
try {
|
|
49
80
|
ast = (0, parser_1.parse)(source, { sourceType: 'module', plugins: ['typescript', 'jsx'] });
|
|
@@ -61,7 +92,7 @@ class ComponentExtractor {
|
|
|
61
92
|
components.push({
|
|
62
93
|
name: componentName,
|
|
63
94
|
filePath,
|
|
64
|
-
route:
|
|
95
|
+
route: assignedRoute,
|
|
65
96
|
stateVariables: context.stateVariables,
|
|
66
97
|
interactions,
|
|
67
98
|
});
|
|
@@ -79,7 +110,7 @@ class ComponentExtractor {
|
|
|
79
110
|
components.push({
|
|
80
111
|
name: componentName,
|
|
81
112
|
filePath,
|
|
82
|
-
route:
|
|
113
|
+
route: assignedRoute,
|
|
83
114
|
stateVariables: context.stateVariables,
|
|
84
115
|
interactions,
|
|
85
116
|
});
|
|
@@ -416,8 +447,39 @@ function deriveLabel(tag, props, nearbyText) {
|
|
|
416
447
|
return props['placeholder'];
|
|
417
448
|
if (props['name'])
|
|
418
449
|
return props['name'];
|
|
450
|
+
// Last resort: extract meaningful words from className for icon-only elements
|
|
451
|
+
// (e.g. password-toggle, close-modal buttons that contain only an SVG).
|
|
452
|
+
const className = props['className'] || props['class'] || '';
|
|
453
|
+
if (className) {
|
|
454
|
+
const meaningful = classNameToLabel(className);
|
|
455
|
+
if (meaningful)
|
|
456
|
+
return meaningful;
|
|
457
|
+
}
|
|
419
458
|
return null;
|
|
420
459
|
}
|
|
460
|
+
// Utility tokens that describe layout/spacing/color but not element purpose.
|
|
461
|
+
const TAILWIND_NOISE = new Set([
|
|
462
|
+
'flex', 'grid', 'block', 'inline', 'hidden', 'relative', 'absolute', 'fixed', 'sticky',
|
|
463
|
+
'items', 'justify', 'content', 'self', 'grow', 'shrink', 'order',
|
|
464
|
+
'inset', 'top', 'right', 'bottom', 'left', 'z',
|
|
465
|
+
'w', 'h', 'min', 'max', 'p', 'px', 'py', 'pt', 'pb', 'pl', 'pr', 'm', 'mx', 'my', 'mt', 'mb', 'ml', 'mr',
|
|
466
|
+
'gap', 'space', 'divide', 'overflow', 'truncate', 'whitespace', 'break',
|
|
467
|
+
'text', 'font', 'leading', 'tracking', 'uppercase', 'lowercase', 'capitalize', 'normal',
|
|
468
|
+
'bg', 'border', 'rounded', 'shadow', 'ring', 'outline', 'opacity', 'transition', 'duration',
|
|
469
|
+
'hover', 'focus', 'active', 'disabled', 'group', 'peer', 'placeholder',
|
|
470
|
+
'sr', 'not', 'dark', 'sm', 'md', 'lg', 'xl', '2xl',
|
|
471
|
+
]);
|
|
472
|
+
function classNameToLabel(className) {
|
|
473
|
+
const words = className
|
|
474
|
+
.split(/[\s\-_/]+/)
|
|
475
|
+
.map((w) => w.replace(/[^a-zA-Z]/g, '').toLowerCase())
|
|
476
|
+
.filter((w) => w.length > 2 && !TAILWIND_NOISE.has(w));
|
|
477
|
+
if (words.length === 0)
|
|
478
|
+
return null;
|
|
479
|
+
// Deduplicate, keep first 3 meaningful words
|
|
480
|
+
const unique = [...new Set(words)].slice(0, 3);
|
|
481
|
+
return unique.join(' ');
|
|
482
|
+
}
|
|
421
483
|
function inferSemanticRole(tag, props, nearbyText) {
|
|
422
484
|
const label = props['aria-label'] || props['placeholder'] || nearbyText[0] || '';
|
|
423
485
|
return `${label} ${tag}`.trim().toLowerCase();
|
package/dist/types.d.ts
CHANGED
|
@@ -96,6 +96,7 @@ export interface PolicySelectorEntry {
|
|
|
96
96
|
tag: string;
|
|
97
97
|
component: string;
|
|
98
98
|
label?: string;
|
|
99
|
+
nearbyText?: string[];
|
|
99
100
|
route: string;
|
|
100
101
|
filePath: string;
|
|
101
102
|
attributes: Array<{
|
|
@@ -142,6 +143,7 @@ export interface SelectorManifestEntry {
|
|
|
142
143
|
component: string;
|
|
143
144
|
tag: string;
|
|
144
145
|
label?: string;
|
|
146
|
+
nearbyText?: string[];
|
|
145
147
|
routes: string[];
|
|
146
148
|
}
|
|
147
149
|
export interface SelectorManifest {
|
|
@@ -83,6 +83,7 @@ class PackageBuilder {
|
|
|
83
83
|
component: entry.component,
|
|
84
84
|
tag: entry.tag,
|
|
85
85
|
label: entry.label,
|
|
86
|
+
nearbyText: entry.nearbyText,
|
|
86
87
|
routes: [entry.route],
|
|
87
88
|
});
|
|
88
89
|
}
|
|
@@ -180,6 +181,7 @@ class PackageBuilder {
|
|
|
180
181
|
tag: element.tag,
|
|
181
182
|
component,
|
|
182
183
|
label: element.label,
|
|
184
|
+
nearbyText: element.nearbyText?.length ? element.nearbyText : undefined,
|
|
183
185
|
route: element.route,
|
|
184
186
|
filePath: projectRoot
|
|
185
187
|
? this.normalizePath(element.filePath, projectRoot)
|