@dcoder-x/plugin-shared 0.1.14 → 0.1.15
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.
|
@@ -14,17 +14,21 @@ const HtmlTagGuards_1 = require("../injection/HtmlTagGuards");
|
|
|
14
14
|
const IdStrategy_1 = require("../injection/IdStrategy");
|
|
15
15
|
/**
|
|
16
16
|
* A route is a usable module-graph entry point only when its filePath
|
|
17
|
-
* points to an actual page component, not a layout/guard/wrapper.
|
|
18
|
-
* Score 0 = structural wrapper → skip as entry point.
|
|
17
|
+
* points to an actual page component, not a layout/guard/wrapper/router file.
|
|
19
18
|
*/
|
|
20
19
|
function isPageRoute(route) {
|
|
21
20
|
if (route.isLayoutRoute)
|
|
22
21
|
return false;
|
|
23
22
|
const p = route.filePath.replace(/\\/g, '/').toLowerCase();
|
|
23
|
+
const base = path_1.default.basename(p).replace(/\.(tsx?|jsx?)$/, '');
|
|
24
24
|
if (p.includes('/pages/') || p.includes('/views/') || p.includes('/screens/'))
|
|
25
25
|
return true;
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// Directory-based structural patterns
|
|
27
|
+
if (/\/(contexts?|providers?|guards?|layouts?|wrappers?|hoc|utils?|helpers?)\//.test(p))
|
|
28
|
+
return false;
|
|
29
|
+
// Basename-based structural patterns: wrapper, router config, and provider files
|
|
30
|
+
if (/(provider|context|guard|layout|redirect|catchall|catch-all)/.test(base) ||
|
|
31
|
+
/^(router|routes|routerconfig|approuter|routesconfig|routingconfig|routeconfig|approviders|appwrapper)$/i.test(base))
|
|
28
32
|
return false;
|
|
29
33
|
return true;
|
|
30
34
|
}
|
|
@@ -413,10 +417,21 @@ function extractStaticProps(attributes) {
|
|
|
413
417
|
continue;
|
|
414
418
|
if (attr.value?.type === 'StringLiteral') {
|
|
415
419
|
props[key] = attr.value.value;
|
|
420
|
+
continue;
|
|
416
421
|
}
|
|
417
|
-
if (attr.value?.type === 'JSXExpressionContainer'
|
|
418
|
-
attr.value.expression
|
|
419
|
-
|
|
422
|
+
if (attr.value?.type === 'JSXExpressionContainer') {
|
|
423
|
+
const expr = attr.value.expression;
|
|
424
|
+
if (expr?.type === 'StringLiteral') {
|
|
425
|
+
props[key] = expr.value;
|
|
426
|
+
}
|
|
427
|
+
else if (expr?.type === 'TemplateLiteral' &&
|
|
428
|
+
expr.quasis?.length === 1 &&
|
|
429
|
+
expr.expressions?.length === 0) {
|
|
430
|
+
// Pure template literal with no interpolations: `to="/some/path"`
|
|
431
|
+
const raw = expr.quasis[0].value.cooked ?? expr.quasis[0].value.raw;
|
|
432
|
+
if (raw)
|
|
433
|
+
props[key] = raw;
|
|
434
|
+
}
|
|
420
435
|
}
|
|
421
436
|
}
|
|
422
437
|
return props;
|
|
@@ -14,6 +14,12 @@ export declare class FlowInferrer {
|
|
|
14
14
|
*/
|
|
15
15
|
private isCoherentChain;
|
|
16
16
|
private detectInteractionFlows;
|
|
17
|
+
/**
|
|
18
|
+
* Deduplicate interaction flows by (page, trigger event, effect target).
|
|
19
|
+
* Shared components used on multiple routes produce one flow per component
|
|
20
|
+
* interaction. After dedup, each distinct interaction appears once.
|
|
21
|
+
*/
|
|
22
|
+
private deduplicateInteractionFlows;
|
|
17
23
|
private buildChain;
|
|
18
24
|
private generateIntentPatterns;
|
|
19
25
|
/**
|
|
@@ -45,10 +45,9 @@ class FlowInferrer {
|
|
|
45
45
|
infer() {
|
|
46
46
|
const edges = this.buildEdgeGraph();
|
|
47
47
|
const routeFlows = this.detectLinearFlows(edges);
|
|
48
|
-
const interactionFlows = this.detectInteractionFlows();
|
|
48
|
+
const interactionFlows = this.deduplicateInteractionFlows(this.detectInteractionFlows());
|
|
49
49
|
const combined = [...routeFlows, ...interactionFlows];
|
|
50
|
-
//
|
|
51
|
-
// navigation chrome (sidebars, footers) rather than user intent signals
|
|
50
|
+
// Remove patterns that appear in too many flows — structural nav chrome
|
|
52
51
|
return this.deduplicateCrossFlowNoise(combined);
|
|
53
52
|
}
|
|
54
53
|
// ---------------------------------------------------------------------------
|
|
@@ -56,13 +55,21 @@ class FlowInferrer {
|
|
|
56
55
|
// ---------------------------------------------------------------------------
|
|
57
56
|
buildEdgeGraph() {
|
|
58
57
|
const edges = [];
|
|
58
|
+
// Index routes both with and without leading slash to handle mixed conventions
|
|
59
59
|
const routePaths = new Set(this.routes.map((r) => r.path));
|
|
60
|
+
const normalizeHref = (href) => href.replace(/^\//, '');
|
|
60
61
|
for (const el of this.elements) {
|
|
61
62
|
const isNav = el.tag === 'a' || el.isNavigationLink === true;
|
|
62
|
-
const
|
|
63
|
-
if (!isNav || !
|
|
63
|
+
const rawHref = el.staticProps['href'] || el.staticProps['to'];
|
|
64
|
+
if (!isNav || !rawHref)
|
|
64
65
|
continue;
|
|
65
|
-
|
|
66
|
+
// Match against both the raw href and the normalized (no leading slash) form
|
|
67
|
+
const href = routePaths.has(rawHref)
|
|
68
|
+
? rawHref
|
|
69
|
+
: routePaths.has(normalizeHref(rawHref))
|
|
70
|
+
? normalizeHref(rawHref)
|
|
71
|
+
: null;
|
|
72
|
+
if (!href)
|
|
66
73
|
continue;
|
|
67
74
|
const triggerText = el.staticProps['aria-label'] ||
|
|
68
75
|
el.label ||
|
|
@@ -162,6 +169,26 @@ class FlowInferrer {
|
|
|
162
169
|
return flows;
|
|
163
170
|
}
|
|
164
171
|
// ---------------------------------------------------------------------------
|
|
172
|
+
// Interaction flow deduplication
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
/**
|
|
175
|
+
* Deduplicate interaction flows by (page, trigger event, effect target).
|
|
176
|
+
* Shared components used on multiple routes produce one flow per component
|
|
177
|
+
* interaction. After dedup, each distinct interaction appears once.
|
|
178
|
+
*/
|
|
179
|
+
deduplicateInteractionFlows(flows) {
|
|
180
|
+
const seen = new Set();
|
|
181
|
+
return flows.filter((f) => {
|
|
182
|
+
const trigger = f.edges[0]?.trigger ?? '';
|
|
183
|
+
const step = f.steps[0] ?? '';
|
|
184
|
+
const sig = `${f.page}::${trigger}::${step}`;
|
|
185
|
+
if (seen.has(sig))
|
|
186
|
+
return false;
|
|
187
|
+
seen.add(sig);
|
|
188
|
+
return true;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
165
192
|
// Chain builder
|
|
166
193
|
// ---------------------------------------------------------------------------
|
|
167
194
|
buildChain(start, adjacency, seen) {
|
|
@@ -18,16 +18,20 @@ const traverse = typeof traverse_1.default === 'function' ? traverse_1.default :
|
|
|
18
18
|
/**
|
|
19
19
|
* Score a resolved file path: higher = more likely to be an actual page component.
|
|
20
20
|
* 3 = pages/views/screens directory → definitely a page
|
|
21
|
-
* 2 = named after a recognisable page pattern but not in a pages dir
|
|
22
21
|
* 1 = unknown / ambiguous
|
|
23
|
-
* 0 = structural
|
|
22
|
+
* 0 = structural file (provider, guard, layout, context, router config, …)
|
|
24
23
|
*/
|
|
25
24
|
function scoreFilePath(filePath) {
|
|
26
25
|
const p = filePath.replace(/\\/g, '/').toLowerCase();
|
|
26
|
+
const base = path_1.default.basename(p).replace(/\.(tsx?|jsx?)$/, '');
|
|
27
27
|
if (p.includes('/pages/') || p.includes('/views/') || p.includes('/screens/'))
|
|
28
28
|
return 3;
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
// Directory-based structural patterns
|
|
30
|
+
if (/\/(contexts?|providers?|guards?|layouts?|wrappers?|hoc|utils?|helpers?)\//.test(p))
|
|
31
|
+
return 0;
|
|
32
|
+
// Basename-based structural patterns: wrapper/config/router files
|
|
33
|
+
if (/(provider|context|guard|layout|redirect|catchall|catch-all)/.test(base) ||
|
|
34
|
+
/^(router|routes|routerconfig|approuter|routesconfig|routingconfig|routeconfig|approviders|appwrapper)$/i.test(base))
|
|
31
35
|
return 0;
|
|
32
36
|
return 1;
|
|
33
37
|
}
|