@veraxhq/verax 0.2.0 → 0.3.0
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.
- package/README.md +14 -18
- package/bin/verax.js +7 -0
- package/package.json +15 -5
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +323 -111
- package/src/cli/commands/doctor.js +36 -4
- package/src/cli/commands/ga.js +243 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +131 -2
- package/src/cli/commands/release-check.js +213 -0
- package/src/cli/commands/run.js +498 -103
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +305 -68
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +546 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/detection-engine.js +4 -3
- package/src/cli/util/determinism-runner.js +123 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/env-url.js +4 -0
- package/src/cli/util/events.js +76 -0
- package/src/cli/util/expectation-extractor.js +380 -74
- package/src/cli/util/findings-writer.js +126 -15
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observation-engine.js +69 -23
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +6 -14
- package/src/cli/util/project-discovery.js +23 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/redact.js +2 -2
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/runtime-budget.js +147 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +13 -1
- package/src/cli/util/svelte-navigation-detector.js +163 -0
- package/src/cli/util/svelte-network-detector.js +80 -0
- package/src/cli/util/svelte-sfc-extractor.js +147 -0
- package/src/cli/util/svelte-state-detector.js +243 -0
- package/src/cli/util/vue-navigation-detector.js +177 -0
- package/src/cli/util/vue-sfc-extractor.js +162 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/global.d.ts +28 -0
- package/src/types/ts-ast.d.ts +24 -0
- package/src/verax/cli/doctor.js +2 -2
- package/src/verax/cli/finding-explainer.js +56 -3
- package/src/verax/cli/init.js +1 -1
- package/src/verax/cli/url-safety.js +12 -2
- package/src/verax/cli/wizard.js +13 -2
- package/src/verax/core/artifacts/registry.js +154 -0
- package/src/verax/core/artifacts/verifier.js +980 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +231 -0
- package/src/verax/core/budget-engine.js +1 -1
- package/src/verax/core/capabilities/gates.js +499 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +137 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +79 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +484 -0
- package/src/verax/core/confidence-engine.js +486 -0
- package/src/verax/core/confidence-engine.js.backup +471 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +185 -0
- package/src/verax/core/contracts/validators.js +381 -0
- package/src/verax/core/decision-snapshot.js +31 -4
- package/src/verax/core/decisions/decision.trace.js +276 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +364 -0
- package/src/verax/core/determinism/engine.js +221 -0
- package/src/verax/core/determinism/finding-identity.js +148 -0
- package/src/verax/core/determinism/normalize.js +438 -0
- package/src/verax/core/determinism/report-writer.js +92 -0
- package/src/verax/core/determinism/run-fingerprint.js +118 -0
- package/src/verax/core/determinism-model.js +35 -6
- package/src/verax/core/dynamic-route-intelligence.js +528 -0
- package/src/verax/core/evidence/evidence-capture-service.js +307 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +190 -0
- package/src/verax/core/failures/exit-codes.js +86 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +132 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +434 -0
- package/src/verax/core/ga/ga.enforcer.js +86 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +83 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/incremental-store.js +15 -7
- package/src/verax/core/observe/run-timeline.js +316 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +198 -0
- package/src/verax/core/pipeline-tracker.js +238 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +271 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +159 -0
- package/src/verax/core/release/reproducibility.check.js +221 -0
- package/src/verax/core/release/sbom.builder.js +283 -0
- package/src/verax/core/replay-validator.js +4 -4
- package/src/verax/core/replay.js +1 -1
- package/src/verax/core/report/cross-index.js +192 -0
- package/src/verax/core/report/human-summary.js +222 -0
- package/src/verax/core/route-intelligence.js +419 -0
- package/src/verax/core/security/secrets.scan.js +326 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +124 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +326 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/silence-impact.js +1 -1
- package/src/verax/core/silence-model.js +9 -7
- package/src/verax/core/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/comparison.js +8 -3
- package/src/verax/detect/confidence-engine.js +645 -57
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +19 -2
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/evidence-index.js +15 -65
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +56 -3
- package/src/verax/detect/explanation-helpers.js +1 -1
- package/src/verax/detect/finding-detector.js +2 -2
- package/src/verax/detect/findings-writer.js +149 -20
- package/src/verax/detect/flow-detector.js +4 -4
- package/src/verax/detect/index.js +265 -15
- package/src/verax/detect/interactive-findings.js +3 -4
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/signal-mapper.js +2 -2
- package/src/verax/detect/skip-classifier.js +4 -4
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +61 -9
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +3 -2
- package/src/verax/flow/flow-spec.js +1 -2
- package/src/verax/index.js +413 -33
- package/src/verax/intel/effect-detector.js +1 -1
- package/src/verax/intel/index.js +2 -2
- package/src/verax/intel/route-extractor.js +3 -3
- package/src/verax/intel/vue-navigation-extractor.js +81 -18
- package/src/verax/intel/vue-router-extractor.js +4 -2
- package/src/verax/learn/action-contract-extractor.js +684 -66
- package/src/verax/learn/ast-contract-extractor.js +53 -1
- package/src/verax/learn/index.js +36 -2
- package/src/verax/learn/manifest-writer.js +28 -14
- package/src/verax/learn/route-extractor.js +1 -1
- package/src/verax/learn/route-validator.js +12 -8
- package/src/verax/learn/state-extractor.js +1 -1
- package/src/verax/learn/static-extractor-navigation.js +1 -1
- package/src/verax/learn/static-extractor-validation.js +2 -2
- package/src/verax/learn/static-extractor.js +8 -7
- package/src/verax/learn/ts-contract-resolver.js +14 -12
- package/src/verax/observe/browser.js +22 -3
- package/src/verax/observe/console-sensor.js +2 -2
- package/src/verax/observe/expectation-executor.js +2 -1
- package/src/verax/observe/focus-sensor.js +1 -1
- package/src/verax/observe/human-driver.js +29 -10
- package/src/verax/observe/index.js +92 -844
- package/src/verax/observe/interaction-discovery.js +27 -15
- package/src/verax/observe/interaction-runner.js +31 -14
- package/src/verax/observe/loading-sensor.js +6 -0
- package/src/verax/observe/navigation-sensor.js +1 -1
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +191 -0
- package/src/verax/observe/observe-runner.js +226 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/settle.js +1 -0
- package/src/verax/observe/state-sensor.js +8 -4
- package/src/verax/observe/state-ui-sensor.js +7 -1
- package/src/verax/observe/traces-writer.js +27 -16
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +155 -2
- package/src/verax/scan-summary-writer.js +46 -9
- package/src/verax/shared/artifact-manager.js +9 -6
- package/src/verax/shared/budget-profiles.js +2 -2
- package/src/verax/shared/caching.js +1 -1
- package/src/verax/shared/config-loader.js +1 -2
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/dynamic-route-utils.js +12 -6
- package/src/verax/shared/retry-policy.js +1 -6
- package/src/verax/shared/root-artifacts.js +1 -1
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +1 -0
- package/src/verax/validate/context-validator.js +1 -1
- package/src/verax/observe/index.js.backup +0 -1
- package/src/verax/validate/context-validator.js.bak +0 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 20 — Svelte State Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects state mutations (reactive stores, assignments) in Svelte components.
|
|
5
|
+
* Only emits state promises if state is user-visible (used in markup bindings).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { extractSvelteSFC, extractTemplateBindings } from './svelte-sfc-extractor.js';
|
|
9
|
+
import { parse } from '@babel/parser';
|
|
10
|
+
import traverse from '@babel/traverse';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Detect state promises in Svelte SFC
|
|
14
|
+
*
|
|
15
|
+
* @param {string} filePath - Path to .svelte file
|
|
16
|
+
* @param {string} content - Full file content
|
|
17
|
+
* @param {string} projectRoot - Project root directory
|
|
18
|
+
* @returns {Array} Array of state expectations
|
|
19
|
+
*/
|
|
20
|
+
export function detectSvelteState(filePath, content, projectRoot) {
|
|
21
|
+
const expectations = [];
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const sfc = extractSvelteSFC(content);
|
|
25
|
+
const { scriptBlocks, markup } = sfc;
|
|
26
|
+
|
|
27
|
+
// Extract template bindings to identify user-visible state
|
|
28
|
+
const templateBindings = markup ? extractTemplateBindings(markup.content) : { bindings: [], reactiveStatements: [] };
|
|
29
|
+
|
|
30
|
+
// Collect all state variables used in template
|
|
31
|
+
const templateStateVars = new Set();
|
|
32
|
+
|
|
33
|
+
// From bindings: bind:value="count"
|
|
34
|
+
templateBindings.bindings.forEach(binding => {
|
|
35
|
+
templateStateVars.add(binding.variable);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// From reactive statements: $: doubled = count * 2
|
|
39
|
+
templateBindings.reactiveStatements.forEach(stmt => {
|
|
40
|
+
// Extract variable names from reactive statements
|
|
41
|
+
const varMatch = stmt.statement.match(/^\s*(\w+)\s*=/);
|
|
42
|
+
if (varMatch) {
|
|
43
|
+
templateStateVars.add(varMatch[1]);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// From markup: {count}, {#if isOpen}, etc.
|
|
48
|
+
if (markup && markup.content) {
|
|
49
|
+
// Extract {variable} patterns
|
|
50
|
+
const varPattern = /\{([a-zA-Z_$][a-zA-Z0-9_$]*)\}/g;
|
|
51
|
+
let varMatch;
|
|
52
|
+
while ((varMatch = varPattern.exec(markup.content)) !== null) {
|
|
53
|
+
templateStateVars.add(varMatch[1]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Extract {#if variable} patterns
|
|
57
|
+
const ifPattern = /\{#if\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\}/g;
|
|
58
|
+
let ifMatch;
|
|
59
|
+
while ((ifMatch = ifPattern.exec(markup.content)) !== null) {
|
|
60
|
+
templateStateVars.add(ifMatch[1]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Process script blocks to find state mutations
|
|
65
|
+
for (const scriptBlock of scriptBlocks) {
|
|
66
|
+
if (!scriptBlock.content) continue;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const ast = parse(scriptBlock.content, {
|
|
70
|
+
sourceType: 'module',
|
|
71
|
+
plugins: ['typescript', 'jsx'],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Track reactive store declarations
|
|
75
|
+
const reactiveStores = new Map();
|
|
76
|
+
|
|
77
|
+
traverse.default(ast, {
|
|
78
|
+
// Detect reactive store declarations: $store, writable(), readable()
|
|
79
|
+
VariableDeclarator(path) {
|
|
80
|
+
const { node } = path;
|
|
81
|
+
if (node.init) {
|
|
82
|
+
// Detect writable() stores
|
|
83
|
+
if (
|
|
84
|
+
node.init.type === 'CallExpression' &&
|
|
85
|
+
node.init.callee.name === 'writable'
|
|
86
|
+
) {
|
|
87
|
+
const storeName = node.id.name;
|
|
88
|
+
reactiveStores.set(storeName, 'writable');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Detect readable() stores
|
|
92
|
+
if (
|
|
93
|
+
node.init.type === 'CallExpression' &&
|
|
94
|
+
node.init.callee.name === 'readable'
|
|
95
|
+
) {
|
|
96
|
+
const storeName = node.id.name;
|
|
97
|
+
reactiveStores.set(storeName, 'readable');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
// Detect store mutations: $store = value, store.set(value), store.update(fn)
|
|
103
|
+
AssignmentExpression(path) {
|
|
104
|
+
const { node } = path;
|
|
105
|
+
|
|
106
|
+
// Detect direct assignments: count = 5
|
|
107
|
+
if (node.left.type === 'Identifier') {
|
|
108
|
+
const varName = node.left.name;
|
|
109
|
+
|
|
110
|
+
// Only emit if variable is used in template
|
|
111
|
+
if (templateStateVars.has(varName)) {
|
|
112
|
+
const location = node.loc;
|
|
113
|
+
const line = scriptBlock.startLine + (location ? location.start.line - 1 : 0);
|
|
114
|
+
|
|
115
|
+
expectations.push({
|
|
116
|
+
type: 'state',
|
|
117
|
+
expectedTarget: varName,
|
|
118
|
+
context: 'assignment',
|
|
119
|
+
sourceRef: {
|
|
120
|
+
file: filePath,
|
|
121
|
+
line,
|
|
122
|
+
snippet: scriptBlock.content.substring(
|
|
123
|
+
node.start - (ast.program.body[0]?.start || 0),
|
|
124
|
+
node.end - (ast.program.body[0]?.start || 0)
|
|
125
|
+
),
|
|
126
|
+
},
|
|
127
|
+
proof: 'PROVEN_EXPECTATION',
|
|
128
|
+
metadata: {
|
|
129
|
+
templateUsage: Array.from(templateStateVars).filter(v => v === varName).length,
|
|
130
|
+
stateType: 'variable',
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Detect store assignments: $store = value
|
|
137
|
+
if (
|
|
138
|
+
node.left.type === 'Identifier' &&
|
|
139
|
+
node.left.name.startsWith('$') &&
|
|
140
|
+
reactiveStores.has(node.left.name.substring(1))
|
|
141
|
+
) {
|
|
142
|
+
const storeName = node.left.name.substring(1);
|
|
143
|
+
const location = node.loc;
|
|
144
|
+
const line = scriptBlock.startLine + (location ? location.start.line - 1 : 0);
|
|
145
|
+
|
|
146
|
+
expectations.push({
|
|
147
|
+
type: 'state',
|
|
148
|
+
expectedTarget: storeName,
|
|
149
|
+
context: 'store-assignment',
|
|
150
|
+
sourceRef: {
|
|
151
|
+
file: filePath,
|
|
152
|
+
line,
|
|
153
|
+
snippet: scriptBlock.content.substring(
|
|
154
|
+
node.start - (ast.program.body[0]?.start || 0),
|
|
155
|
+
node.end - (ast.program.body[0]?.start || 0)
|
|
156
|
+
),
|
|
157
|
+
},
|
|
158
|
+
proof: 'PROVEN_EXPECTATION',
|
|
159
|
+
metadata: {
|
|
160
|
+
stateType: 'store',
|
|
161
|
+
storeType: reactiveStores.get(storeName),
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
// Detect store.set() calls
|
|
168
|
+
CallExpression(path) {
|
|
169
|
+
const { node } = path;
|
|
170
|
+
|
|
171
|
+
if (
|
|
172
|
+
node.callee.type === 'MemberExpression' &&
|
|
173
|
+
node.callee.property.name === 'set' &&
|
|
174
|
+
node.callee.object.type === 'Identifier' &&
|
|
175
|
+
reactiveStores.has(node.callee.object.name)
|
|
176
|
+
) {
|
|
177
|
+
const storeName = node.callee.object.name;
|
|
178
|
+
const location = node.loc;
|
|
179
|
+
const line = scriptBlock.startLine + (location ? location.start.line - 1 : 0);
|
|
180
|
+
|
|
181
|
+
expectations.push({
|
|
182
|
+
type: 'state',
|
|
183
|
+
expectedTarget: storeName,
|
|
184
|
+
context: 'store-set',
|
|
185
|
+
sourceRef: {
|
|
186
|
+
file: filePath,
|
|
187
|
+
line,
|
|
188
|
+
snippet: scriptBlock.content.substring(
|
|
189
|
+
node.start - (ast.program.body[0]?.start || 0),
|
|
190
|
+
node.end - (ast.program.body[0]?.start || 0)
|
|
191
|
+
),
|
|
192
|
+
},
|
|
193
|
+
proof: 'PROVEN_EXPECTATION',
|
|
194
|
+
metadata: {
|
|
195
|
+
stateType: 'store',
|
|
196
|
+
storeType: reactiveStores.get(storeName),
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Detect store.update() calls
|
|
202
|
+
if (
|
|
203
|
+
node.callee.type === 'MemberExpression' &&
|
|
204
|
+
node.callee.property.name === 'update' &&
|
|
205
|
+
node.callee.object.type === 'Identifier' &&
|
|
206
|
+
reactiveStores.has(node.callee.object.name)
|
|
207
|
+
) {
|
|
208
|
+
const storeName = node.callee.object.name;
|
|
209
|
+
const location = node.loc;
|
|
210
|
+
const line = scriptBlock.startLine + (location ? location.start.line - 1 : 0);
|
|
211
|
+
|
|
212
|
+
expectations.push({
|
|
213
|
+
type: 'state',
|
|
214
|
+
expectedTarget: storeName,
|
|
215
|
+
context: 'store-update',
|
|
216
|
+
sourceRef: {
|
|
217
|
+
file: filePath,
|
|
218
|
+
line,
|
|
219
|
+
snippet: scriptBlock.content.substring(
|
|
220
|
+
node.start - (ast.program.body[0]?.start || 0),
|
|
221
|
+
node.end - (ast.program.body[0]?.start || 0)
|
|
222
|
+
),
|
|
223
|
+
},
|
|
224
|
+
proof: 'PROVEN_EXPECTATION',
|
|
225
|
+
metadata: {
|
|
226
|
+
stateType: 'store',
|
|
227
|
+
storeType: reactiveStores.get(storeName),
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
} catch (parseError) {
|
|
234
|
+
// Skip if parsing fails
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} catch (error) {
|
|
238
|
+
// Skip if extraction fails
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return expectations;
|
|
242
|
+
}
|
|
243
|
+
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 20 — Vue Navigation Promise Detection
|
|
3
|
+
*
|
|
4
|
+
* Detects Vue Router navigation promises:
|
|
5
|
+
* - router.push('/path'), router.replace('/path')
|
|
6
|
+
* - router.push({ name: 'X', params: { id: 1 }}) -> mark as dynamic/ambiguous
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { parse } from '@babel/parser';
|
|
10
|
+
import _traverse from '@babel/traverse';
|
|
11
|
+
|
|
12
|
+
const traverse = _traverse.default || _traverse;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* PHASE 20: Detect Vue navigation promises
|
|
16
|
+
*
|
|
17
|
+
* @param {string} scriptContent - Script block content
|
|
18
|
+
* @param {string} filePath - File path
|
|
19
|
+
* @param {string} relPath - Relative path
|
|
20
|
+
* @param {Object} scriptBlock - Script block metadata
|
|
21
|
+
* @param {Object} templateBindings - Template bindings (optional)
|
|
22
|
+
* @returns {Array} Navigation promises
|
|
23
|
+
*/
|
|
24
|
+
export function detectVueNavigationPromises(scriptContent, filePath, relPath, scriptBlock, templateBindings) {
|
|
25
|
+
const promises = [];
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const ast = parse(scriptContent, {
|
|
29
|
+
sourceType: 'module',
|
|
30
|
+
plugins: [
|
|
31
|
+
'typescript',
|
|
32
|
+
'classProperties',
|
|
33
|
+
'optionalChaining',
|
|
34
|
+
'nullishCoalescingOperator',
|
|
35
|
+
'dynamicImport',
|
|
36
|
+
'topLevelAwait',
|
|
37
|
+
'objectRestSpread',
|
|
38
|
+
],
|
|
39
|
+
errorRecovery: true,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const lines = scriptContent.split('\n');
|
|
43
|
+
|
|
44
|
+
traverse(ast, {
|
|
45
|
+
CallExpression(path) {
|
|
46
|
+
const node = path.node;
|
|
47
|
+
const callee = node.callee;
|
|
48
|
+
|
|
49
|
+
// Detect router.push() and router.replace()
|
|
50
|
+
if (callee.type === 'MemberExpression') {
|
|
51
|
+
const object = callee.object;
|
|
52
|
+
const property = callee.property;
|
|
53
|
+
|
|
54
|
+
if (property.name === 'push' || property.name === 'replace') {
|
|
55
|
+
// Check if object is 'router' or 'this.$router' or 'useRouter()'
|
|
56
|
+
const isRouter =
|
|
57
|
+
(object.type === 'Identifier' && object.name === 'router') ||
|
|
58
|
+
(object.type === 'MemberExpression' &&
|
|
59
|
+
object.object.type === 'ThisExpression' &&
|
|
60
|
+
object.property.name === '$router') ||
|
|
61
|
+
(object.type === 'CallExpression' &&
|
|
62
|
+
callee.type === 'Identifier' &&
|
|
63
|
+
callee.name === 'useRouter');
|
|
64
|
+
|
|
65
|
+
if (isRouter && node.arguments.length > 0) {
|
|
66
|
+
const arg = node.arguments[0];
|
|
67
|
+
let targetPath = null;
|
|
68
|
+
let isDynamic = false;
|
|
69
|
+
|
|
70
|
+
// String literal: router.push('/path')
|
|
71
|
+
if (arg.type === 'StringLiteral') {
|
|
72
|
+
targetPath = arg.value;
|
|
73
|
+
}
|
|
74
|
+
// Object literal: router.push({ name: 'X', params: {} })
|
|
75
|
+
else if (arg.type === 'ObjectExpression') {
|
|
76
|
+
const nameProp = arg.properties.find(p =>
|
|
77
|
+
p.key && p.key.name === 'name'
|
|
78
|
+
);
|
|
79
|
+
const pathProp = arg.properties.find(p =>
|
|
80
|
+
p.key && p.key.name === 'path'
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (pathProp && pathProp.value && pathProp.value.type === 'StringLiteral') {
|
|
84
|
+
targetPath = pathProp.value.value;
|
|
85
|
+
} else if (nameProp) {
|
|
86
|
+
// Named route - mark as dynamic/ambiguous
|
|
87
|
+
isDynamic = true;
|
|
88
|
+
targetPath = '<named-route>';
|
|
89
|
+
} else {
|
|
90
|
+
isDynamic = true;
|
|
91
|
+
targetPath = '<dynamic>';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Template literal or other dynamic
|
|
95
|
+
else {
|
|
96
|
+
isDynamic = true;
|
|
97
|
+
targetPath = '<dynamic>';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (targetPath) {
|
|
101
|
+
const loc = node.loc;
|
|
102
|
+
const line = loc ? loc.start.line : 1;
|
|
103
|
+
const column = loc ? loc.start.column : 0;
|
|
104
|
+
|
|
105
|
+
// Extract AST source
|
|
106
|
+
const astSource = lines.slice(line - 1, loc ? loc.end.line : line)
|
|
107
|
+
.join('\n')
|
|
108
|
+
.substring(0, 200);
|
|
109
|
+
|
|
110
|
+
// Build context chain
|
|
111
|
+
const context = buildContext(path);
|
|
112
|
+
|
|
113
|
+
promises.push({
|
|
114
|
+
type: 'navigation',
|
|
115
|
+
promise: {
|
|
116
|
+
kind: 'navigate',
|
|
117
|
+
value: targetPath,
|
|
118
|
+
isDynamic,
|
|
119
|
+
},
|
|
120
|
+
source: {
|
|
121
|
+
file: relPath,
|
|
122
|
+
line,
|
|
123
|
+
column,
|
|
124
|
+
context,
|
|
125
|
+
astSource,
|
|
126
|
+
},
|
|
127
|
+
confidence: isDynamic ? 0.7 : 1.0,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
} catch (error) {
|
|
136
|
+
// Parse error - skip
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return promises;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Build context chain from AST path
|
|
144
|
+
*/
|
|
145
|
+
function buildContext(path) {
|
|
146
|
+
const context = [];
|
|
147
|
+
let current = path;
|
|
148
|
+
|
|
149
|
+
while (current) {
|
|
150
|
+
if (current.isFunctionDeclaration()) {
|
|
151
|
+
context.push({
|
|
152
|
+
type: 'function',
|
|
153
|
+
name: current.node.id?.name || '<anonymous>',
|
|
154
|
+
});
|
|
155
|
+
} else if (current.isArrowFunctionExpression()) {
|
|
156
|
+
context.push({
|
|
157
|
+
type: 'arrow-function',
|
|
158
|
+
name: '<arrow>',
|
|
159
|
+
});
|
|
160
|
+
} else if (current.isMethodDefinition()) {
|
|
161
|
+
context.push({
|
|
162
|
+
type: 'method',
|
|
163
|
+
name: current.node.key?.name || '<method>',
|
|
164
|
+
});
|
|
165
|
+
} else if (current.isObjectProperty()) {
|
|
166
|
+
context.push({
|
|
167
|
+
type: 'property',
|
|
168
|
+
name: current.node.key?.name || '<property>',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
current = current.parentPath;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return context.reverse().map(c => `${c.type}:${c.name}`).join(' > ');
|
|
176
|
+
}
|
|
177
|
+
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 20 — Vue SFC (Single File Component) Extractor
|
|
3
|
+
*
|
|
4
|
+
* Extracts <script>, <script setup>, and <template> content from .vue files.
|
|
5
|
+
* Deterministic and robust (no external runtime execution).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* PHASE 20: Extract Vue SFC blocks
|
|
10
|
+
*
|
|
11
|
+
* @param {string} content - Full .vue file content
|
|
12
|
+
* @param {string} filePath - Path to the .vue file (for context)
|
|
13
|
+
* @returns {Object} { scriptBlocks: [{content, lang, startLine}], template: {content, startLine} }
|
|
14
|
+
*/
|
|
15
|
+
export function extractVueSFC(content) {
|
|
16
|
+
const scriptBlocks = [];
|
|
17
|
+
let template = null;
|
|
18
|
+
|
|
19
|
+
// Extract <script> blocks (including <script setup>)
|
|
20
|
+
const scriptRegex = /<script(?:\s+setup)?(?:\s+lang=["']([^"']+)["'])?[^>]*>([\s\S]*?)<\/script>/gi;
|
|
21
|
+
let scriptMatch;
|
|
22
|
+
let lineOffset = 1;
|
|
23
|
+
|
|
24
|
+
while ((scriptMatch = scriptRegex.exec(content)) !== null) {
|
|
25
|
+
const isSetup = scriptMatch[0].includes('setup');
|
|
26
|
+
const lang = scriptMatch[1] || 'js';
|
|
27
|
+
const scriptContent = scriptMatch[2];
|
|
28
|
+
|
|
29
|
+
// Calculate start line
|
|
30
|
+
const beforeMatch = content.substring(0, scriptMatch.index);
|
|
31
|
+
const startLine = (beforeMatch.match(/\n/g) || []).length + 1;
|
|
32
|
+
|
|
33
|
+
scriptBlocks.push({
|
|
34
|
+
content: scriptContent.trim(),
|
|
35
|
+
lang: lang.toLowerCase(),
|
|
36
|
+
startLine,
|
|
37
|
+
isSetup,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Extract <template> block
|
|
42
|
+
const templateRegex = /<template[^>]*>([\s\S]*?)<\/template>/i;
|
|
43
|
+
const templateMatch = content.match(templateRegex);
|
|
44
|
+
|
|
45
|
+
if (templateMatch) {
|
|
46
|
+
const beforeTemplate = content.substring(0, templateMatch.index);
|
|
47
|
+
const templateStartLine = (beforeTemplate.match(/\n/g) || []).length + 1;
|
|
48
|
+
|
|
49
|
+
template = {
|
|
50
|
+
content: templateMatch[1].trim(),
|
|
51
|
+
startLine: templateStartLine,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
scriptBlocks,
|
|
57
|
+
template,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* PHASE 20: Extract template bindings and references
|
|
63
|
+
*
|
|
64
|
+
* @param {string} templateContent - Template content
|
|
65
|
+
* @returns {Object} { bindings: string[], routerLinks: Array, eventHandlers: Array }
|
|
66
|
+
*/
|
|
67
|
+
export function extractTemplateBindings(templateContent) {
|
|
68
|
+
const bindings = [];
|
|
69
|
+
const routerLinks = [];
|
|
70
|
+
const eventHandlers = [];
|
|
71
|
+
|
|
72
|
+
// Extract variable bindings: {{ var }}, :prop="var", v-if="var", etc.
|
|
73
|
+
const bindingPatterns = [
|
|
74
|
+
/\{\{\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\}\}/g, // {{ var }}
|
|
75
|
+
/:([a-zA-Z-]+)=["']([^"']+)["']/g, // :prop="value"
|
|
76
|
+
/v-if=["']([^"']+)["']/g, // v-if="condition"
|
|
77
|
+
/v-show=["']([^"']+)["']/g, // v-show="condition"
|
|
78
|
+
/v-model=["']([^"']+)["']/g, // v-model="value"
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
for (const pattern of bindingPatterns) {
|
|
82
|
+
let match;
|
|
83
|
+
while ((match = pattern.exec(templateContent)) !== null) {
|
|
84
|
+
const binding = match[1] || match[2];
|
|
85
|
+
if (binding && !bindings.includes(binding)) {
|
|
86
|
+
bindings.push(binding);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Extract <router-link> usage
|
|
92
|
+
const routerLinkRegex = /<router-link[^>]*\s+to=["']([^"']+)["'][^>]*>/gi;
|
|
93
|
+
let routerLinkMatch;
|
|
94
|
+
while ((routerLinkMatch = routerLinkRegex.exec(templateContent)) !== null) {
|
|
95
|
+
routerLinks.push({
|
|
96
|
+
to: routerLinkMatch[1],
|
|
97
|
+
fullMatch: routerLinkMatch[0],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Extract event handlers: @click="handler", @submit.prevent="handler"
|
|
102
|
+
const eventHandlerRegex = /@([a-z]+)(?:\.([a-z]+)*)?=["']([^"']+)["']/gi;
|
|
103
|
+
let eventMatch;
|
|
104
|
+
while ((eventMatch = eventHandlerRegex.exec(templateContent)) !== null) {
|
|
105
|
+
eventHandlers.push({
|
|
106
|
+
event: eventMatch[1],
|
|
107
|
+
modifiers: eventMatch[2] ? eventMatch[2].split('.') : [],
|
|
108
|
+
handler: eventMatch[3],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
bindings,
|
|
114
|
+
routerLinks,
|
|
115
|
+
eventHandlers,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* PHASE 20: Map template handlers to script functions
|
|
121
|
+
*
|
|
122
|
+
* @param {Object} templateBindings - Result from extractTemplateBindings
|
|
123
|
+
* @param {Array} scriptBlocks - Script blocks from extractVueSFC
|
|
124
|
+
* @returns {Map} handlerName -> { scriptBlock, functionInfo }
|
|
125
|
+
*/
|
|
126
|
+
export function mapTemplateHandlersToScript(templateBindings, scriptBlocks) {
|
|
127
|
+
const handlerMap = new Map();
|
|
128
|
+
|
|
129
|
+
for (const handler of templateBindings.eventHandlers) {
|
|
130
|
+
const handlerName = handler.handler;
|
|
131
|
+
|
|
132
|
+
// Find handler in script blocks
|
|
133
|
+
for (const scriptBlock of scriptBlocks) {
|
|
134
|
+
const scriptContent = scriptBlock.content;
|
|
135
|
+
|
|
136
|
+
// Look for function declarations: function handlerName() {}
|
|
137
|
+
const functionRegex = new RegExp(`(?:function|const|let|var)\\s+${handlerName}\\s*[=(]`, 'g');
|
|
138
|
+
if (functionRegex.test(scriptContent)) {
|
|
139
|
+
handlerMap.set(handlerName, {
|
|
140
|
+
scriptBlock,
|
|
141
|
+
handler,
|
|
142
|
+
type: 'function',
|
|
143
|
+
});
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Look for method definitions: methods: { handlerName() {} }
|
|
148
|
+
const methodRegex = new RegExp(`(?:methods|setup)\\s*:\\s*\\{[^}]*${handlerName}\\s*[:(]`, 's');
|
|
149
|
+
if (methodRegex.test(scriptContent)) {
|
|
150
|
+
handlerMap.set(handlerName, {
|
|
151
|
+
scriptBlock,
|
|
152
|
+
handler,
|
|
153
|
+
type: 'method',
|
|
154
|
+
});
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return handlerMap;
|
|
161
|
+
}
|
|
162
|
+
|