@veraxhq/verax 0.2.1 → 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 +3 -3
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +79 -25
- 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 +246 -35
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +304 -67
- 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/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/expectation-extractor.js +369 -73
- package/src/cli/util/findings-writer.js +126 -16
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +3 -12
- package/src/cli/util/project-discovery.js +3 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +1 -0
- 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/verax/cli/finding-explainer.js +56 -3
- 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/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 +30 -3
- 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/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/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/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/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/confidence-engine.js +628 -40
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +18 -1
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +3 -1
- package/src/verax/detect/findings-writer.js +141 -5
- package/src/verax/detect/index.js +229 -5
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +57 -3
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/index.js +413 -45
- package/src/verax/learn/action-contract-extractor.js +682 -64
- package/src/verax/learn/route-validator.js +4 -1
- package/src/verax/observe/index.js +88 -843
- package/src/verax/observe/interaction-runner.js +25 -8
- 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/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +148 -2
- package/src/verax/scan-summary-writer.js +42 -8
- package/src/verax/shared/artifact-manager.js +8 -5
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/view-switch-rules.js +208 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 20 — Svelte SFC (Single File Component) Extractor
|
|
3
|
+
*
|
|
4
|
+
* Extracts <script>, <script context="module">, and markup content from .svelte files.
|
|
5
|
+
* Deterministic and robust (no external runtime execution).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* PHASE 20: Extract Svelte SFC blocks
|
|
10
|
+
*
|
|
11
|
+
* @param {string} content - Full .svelte file content
|
|
12
|
+
* @param {string} filePath - Path to the .svelte file (for context)
|
|
13
|
+
* @returns {Object} { scriptBlocks: [{content, lang, startLine, isModule}], markup: {content, startLine} }
|
|
14
|
+
*/
|
|
15
|
+
export function extractSvelteSFC(content) {
|
|
16
|
+
const scriptBlocks = [];
|
|
17
|
+
let markup = null;
|
|
18
|
+
|
|
19
|
+
// Extract <script> blocks (including <script context="module">)
|
|
20
|
+
const scriptRegex = /<script(?:\s+context=["']module["'])?(?:\s+lang=["']([^"']+)["'])?[^>]*>([\s\S]*?)<\/script>/gi;
|
|
21
|
+
let scriptMatch;
|
|
22
|
+
|
|
23
|
+
while ((scriptMatch = scriptRegex.exec(content)) !== null) {
|
|
24
|
+
const isModule = scriptMatch[0].includes('context="module"') || scriptMatch[0].includes("context='module'");
|
|
25
|
+
const lang = scriptMatch[1] || 'js';
|
|
26
|
+
const scriptContent = scriptMatch[2];
|
|
27
|
+
|
|
28
|
+
// Calculate start line
|
|
29
|
+
const beforeMatch = content.substring(0, scriptMatch.index);
|
|
30
|
+
const startLine = (beforeMatch.match(/\n/g) || []).length + 1;
|
|
31
|
+
|
|
32
|
+
scriptBlocks.push({
|
|
33
|
+
content: scriptContent.trim(),
|
|
34
|
+
lang: lang.toLowerCase(),
|
|
35
|
+
startLine,
|
|
36
|
+
isModule,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Extract markup (everything outside script/style tags)
|
|
41
|
+
// Svelte markup is the template content
|
|
42
|
+
const styleRegex = /<style[^>]*>[\s\S]*?<\/style>/gi;
|
|
43
|
+
const allScriptRegex = /<script[^>]*>[\s\S]*?<\/script>/gi;
|
|
44
|
+
|
|
45
|
+
let markupContent = content;
|
|
46
|
+
// Remove style blocks
|
|
47
|
+
markupContent = markupContent.replace(styleRegex, '');
|
|
48
|
+
// Remove script blocks
|
|
49
|
+
markupContent = markupContent.replace(allScriptRegex, '');
|
|
50
|
+
|
|
51
|
+
// Find first non-whitespace line for markup
|
|
52
|
+
const lines = content.split('\n');
|
|
53
|
+
let markupStartLine = 1;
|
|
54
|
+
for (let i = 0; i < lines.length; i++) {
|
|
55
|
+
const line = lines[i];
|
|
56
|
+
if (line.trim() && !line.trim().startsWith('<script') && !line.trim().startsWith('<style')) {
|
|
57
|
+
markupStartLine = i + 1;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (markupContent.trim()) {
|
|
63
|
+
markup = {
|
|
64
|
+
content: markupContent.trim(),
|
|
65
|
+
startLine: markupStartLine,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
scriptBlocks,
|
|
71
|
+
markup,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Extract template bindings from Svelte markup
|
|
77
|
+
* Detects reactive statements, event handlers, and bindings
|
|
78
|
+
*
|
|
79
|
+
* @param {string} markupContent - Svelte markup content
|
|
80
|
+
* @returns {Object} { reactiveStatements: [], eventHandlers: [], bindings: [] }
|
|
81
|
+
*/
|
|
82
|
+
export function extractTemplateBindings(markupContent) {
|
|
83
|
+
const reactiveStatements = [];
|
|
84
|
+
const eventHandlers = [];
|
|
85
|
+
const bindings = [];
|
|
86
|
+
|
|
87
|
+
// Extract reactive statements: $: statements
|
|
88
|
+
const reactiveRegex = /\$:\s*([^;]+);/g;
|
|
89
|
+
let reactiveMatch;
|
|
90
|
+
while ((reactiveMatch = reactiveRegex.exec(markupContent)) !== null) {
|
|
91
|
+
reactiveStatements.push({
|
|
92
|
+
statement: reactiveMatch[1].trim(),
|
|
93
|
+
line: (markupContent.substring(0, reactiveMatch.index).match(/\n/g) || []).length + 1,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Extract event handlers: on:click, on:submit, etc.
|
|
98
|
+
const eventHandlerRegex = /on:(\w+)=["']([^"']+)["']/g;
|
|
99
|
+
let handlerMatch;
|
|
100
|
+
while ((handlerMatch = eventHandlerRegex.exec(markupContent)) !== null) {
|
|
101
|
+
eventHandlers.push({
|
|
102
|
+
event: handlerMatch[1],
|
|
103
|
+
handler: handlerMatch[2],
|
|
104
|
+
line: (markupContent.substring(0, handlerMatch.index).match(/\n/g) || []).length + 1,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Extract bindings: bind:value, bind:checked, etc.
|
|
109
|
+
const bindingRegex = /bind:(\w+)=["']([^"']+)["']/g;
|
|
110
|
+
let bindingMatch;
|
|
111
|
+
while ((bindingMatch = bindingRegex.exec(markupContent)) !== null) {
|
|
112
|
+
bindings.push({
|
|
113
|
+
property: bindingMatch[1],
|
|
114
|
+
variable: bindingMatch[2],
|
|
115
|
+
line: (markupContent.substring(0, bindingMatch.index).match(/\n/g) || []).length + 1,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
reactiveStatements,
|
|
121
|
+
eventHandlers,
|
|
122
|
+
bindings,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Map template handlers to script functions
|
|
128
|
+
* Helps identify which handlers are UI-bound
|
|
129
|
+
*
|
|
130
|
+
* @param {Array} eventHandlers - Event handlers from template
|
|
131
|
+
* @param {string} scriptContent - Script block content
|
|
132
|
+
* @returns {Array} Mapped handlers with function references
|
|
133
|
+
*/
|
|
134
|
+
export function mapTemplateHandlersToScript(eventHandlers, scriptContent) {
|
|
135
|
+
return eventHandlers.map(handler => {
|
|
136
|
+
// Try to find function definition in script
|
|
137
|
+
const functionRegex = new RegExp(`(?:function\\s+${handler.handler}|const\\s+${handler.handler}\\s*=\\s*[^(]*\\(|${handler.handler}\\s*=\\s*[^(]*\\(|export\\s+function\\s+${handler.handler})`, 'g');
|
|
138
|
+
const functionMatch = functionRegex.exec(scriptContent);
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
...handler,
|
|
142
|
+
functionFound: !!functionMatch,
|
|
143
|
+
functionLine: functionMatch ? (scriptContent.substring(0, functionMatch.index).match(/\n/g) || []).length + 1 : null,
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
@@ -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
|
+
|