@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
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { readdirSync, readFileSync, statSync } from 'fs';
|
|
2
2
|
import { join, relative, resolve } from 'path';
|
|
3
3
|
import { expIdFromHash, compareExpectations } from './idgen.js';
|
|
4
|
+
import { detectNetworkCallsAST } from './ast-network-detector.js';
|
|
5
|
+
import { detectUseStatePromises } from './ast-usestate-detector.js';
|
|
6
|
+
import { detectInteractiveElementsAST } from './ast-interactive-detector.js';
|
|
7
|
+
import { extractVueSFC, extractTemplateBindings, mapTemplateHandlersToScript } from './vue-sfc-extractor.js';
|
|
8
|
+
import { detectVueNavigationPromises } from './vue-navigation-detector.js';
|
|
9
|
+
import { detectVueStatePromises } from './vue-state-detector.js';
|
|
10
|
+
import { extractSvelteSFC, extractTemplateBindings as extractSvelteTemplateBindings, mapTemplateHandlersToScript as mapSvelteTemplateHandlersToScript } from './svelte-sfc-extractor.js';
|
|
11
|
+
import { detectSvelteNavigation } from './svelte-navigation-detector.js';
|
|
12
|
+
import { detectSvelteNetwork } from './svelte-network-detector.js';
|
|
13
|
+
import { detectSvelteState } from './svelte-state-detector.js';
|
|
14
|
+
import { extractAngularComponent, extractTemplateBindings as extractAngularTemplateBindings, mapTemplateHandlersToClass } from './angular-component-extractor.js';
|
|
15
|
+
import { detectAngularNavigation } from './angular-navigation-detector.js';
|
|
16
|
+
import { detectAngularNetwork } from './angular-network-detector.js';
|
|
17
|
+
import { detectAngularState } from './angular-state-detector.js';
|
|
4
18
|
|
|
5
19
|
/**
|
|
6
20
|
* Static Expectation Extractor
|
|
@@ -148,8 +162,8 @@ function shouldSkipDirectory(name) {
|
|
|
148
162
|
* Check if file should be scanned
|
|
149
163
|
*/
|
|
150
164
|
function shouldScanFile(name) {
|
|
151
|
-
const extensions = ['.js', '.jsx', '.ts', '.tsx', '.html', '.mjs'];
|
|
152
|
-
return extensions.some(ext => name.endsWith(ext));
|
|
165
|
+
const extensions = ['.js', '.jsx', '.ts', '.tsx', '.html', '.mjs', '.vue', '.svelte'];
|
|
166
|
+
return extensions.some(ext => name.endsWith(ext)) || (name.endsWith('.component.ts') || name.endsWith('.service.ts'));
|
|
153
167
|
}
|
|
154
168
|
|
|
155
169
|
/**
|
|
@@ -165,6 +179,15 @@ function scanFile(filePath, sourceRoot, skipped) {
|
|
|
165
179
|
if (filePath.endsWith('.html')) {
|
|
166
180
|
const htmlExpectations = extractHtmlExpectations(content, filePath, relPath);
|
|
167
181
|
expectations.push(...htmlExpectations);
|
|
182
|
+
} else if (filePath.endsWith('.vue')) {
|
|
183
|
+
const vueExpectations = extractVueExpectations(content, filePath, relPath, skipped);
|
|
184
|
+
expectations.push(...vueExpectations);
|
|
185
|
+
} else if (filePath.endsWith('.svelte')) {
|
|
186
|
+
const svelteExpectations = extractSvelteExpectations(content, filePath, relPath, skipped);
|
|
187
|
+
expectations.push(...svelteExpectations);
|
|
188
|
+
} else if (filePath.endsWith('.component.ts') || (filePath.endsWith('.ts') && content.includes('@Component'))) {
|
|
189
|
+
const angularExpectations = extractAngularExpectations(content, filePath, relPath, skipped);
|
|
190
|
+
expectations.push(...angularExpectations);
|
|
168
191
|
} else {
|
|
169
192
|
const jsExpectations = extractJsExpectations(content, filePath, relPath, skipped);
|
|
170
193
|
expectations.push(...jsExpectations);
|
|
@@ -178,9 +201,11 @@ function scanFile(filePath, sourceRoot, skipped) {
|
|
|
178
201
|
|
|
179
202
|
/**
|
|
180
203
|
* Extract expectations from HTML files
|
|
204
|
+
* PHASE 9: Enhanced to extract network calls from <script> tags using AST
|
|
181
205
|
*/
|
|
182
206
|
function extractHtmlExpectations(content, filePath, relPath) {
|
|
183
207
|
const expectations = [];
|
|
208
|
+
const skipped = { dynamic: 0, computed: 0, external: 0, parseError: 0, other: 0 };
|
|
184
209
|
|
|
185
210
|
// Extract <a href="/path"> links
|
|
186
211
|
const hrefRegex = /<a\s+[^>]*href=["']([^"']+)["']/gi;
|
|
@@ -208,6 +233,72 @@ function extractHtmlExpectations(content, filePath, relPath) {
|
|
|
208
233
|
}
|
|
209
234
|
}
|
|
210
235
|
|
|
236
|
+
// PHASE 9: Extract JavaScript from <script> tags and detect network calls
|
|
237
|
+
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
|
238
|
+
let scriptMatch;
|
|
239
|
+
let scriptIndex = 0;
|
|
240
|
+
|
|
241
|
+
while ((scriptMatch = scriptRegex.exec(content)) !== null) {
|
|
242
|
+
const scriptContent = scriptMatch[1].trim();
|
|
243
|
+
const scriptStartIndex = scriptMatch.index;
|
|
244
|
+
const scriptStartLine = content.substring(0, scriptStartIndex).split('\n').length;
|
|
245
|
+
|
|
246
|
+
// Skip empty scripts and external scripts
|
|
247
|
+
if (!scriptContent || scriptMatch[0].includes('src=')) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Use AST-based network detection on script content
|
|
252
|
+
try {
|
|
253
|
+
const networkCalls = detectNetworkCallsAST(scriptContent, filePath, relPath);
|
|
254
|
+
|
|
255
|
+
for (const call of networkCalls) {
|
|
256
|
+
const url = call.url;
|
|
257
|
+
|
|
258
|
+
// Skip dynamic URLs but count them
|
|
259
|
+
if (url === '<dynamic>') {
|
|
260
|
+
skipped.dynamic++;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Only extract absolute URLs (http/https) or relative API paths
|
|
265
|
+
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('/api/')) {
|
|
266
|
+
// Calculate correct line number in HTML file
|
|
267
|
+
const callLineInScript = call.location.line || 1;
|
|
268
|
+
const htmlLineNumber = scriptStartLine + callLineInScript - 1;
|
|
269
|
+
|
|
270
|
+
expectations.push({
|
|
271
|
+
type: 'network',
|
|
272
|
+
promise: {
|
|
273
|
+
kind: 'request',
|
|
274
|
+
value: url,
|
|
275
|
+
method: call.method,
|
|
276
|
+
},
|
|
277
|
+
source: {
|
|
278
|
+
file: relPath,
|
|
279
|
+
line: htmlLineNumber,
|
|
280
|
+
column: call.location.column || 0,
|
|
281
|
+
context: call.context,
|
|
282
|
+
astSource: call.astSource || null,
|
|
283
|
+
},
|
|
284
|
+
confidence: call.isUIBound ? 1.0 : 0.9,
|
|
285
|
+
metadata: {
|
|
286
|
+
networkKind: call.kind,
|
|
287
|
+
isUIBound: call.isUIBound || false,
|
|
288
|
+
astSource: call.astSource || null,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
} else {
|
|
292
|
+
skipped.external++;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} catch (error) {
|
|
296
|
+
skipped.parseError++;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
scriptIndex++;
|
|
300
|
+
}
|
|
301
|
+
|
|
211
302
|
return expectations;
|
|
212
303
|
}
|
|
213
304
|
|
|
@@ -218,76 +309,178 @@ function extractJsExpectations(content, filePath, relPath, skipped) {
|
|
|
218
309
|
const expectations = [];
|
|
219
310
|
const lines = content.split('\n');
|
|
220
311
|
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (line.trim().startsWith('//') || line.trim().startsWith('*')) {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Extract Next.js <Link href="/path">
|
|
230
|
-
const linkRegex = /<Link\s+[^>]*href=["']([^"']+)["']/g;
|
|
231
|
-
let match;
|
|
232
|
-
while ((match = linkRegex.exec(line)) !== null) {
|
|
233
|
-
const href = match[1];
|
|
312
|
+
// PHASE 9: AST-based network detection (handles nested contexts)
|
|
313
|
+
const networkCalls = detectNetworkCallsAST(content, filePath, relPath);
|
|
314
|
+
for (const call of networkCalls) {
|
|
315
|
+
const url = call.url;
|
|
234
316
|
|
|
235
|
-
// Skip dynamic
|
|
236
|
-
if (
|
|
317
|
+
// Skip dynamic URLs but count them
|
|
318
|
+
if (url === '<dynamic>') {
|
|
319
|
+
skipped.dynamic++;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Only extract absolute URLs (http/https)
|
|
324
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
237
325
|
expectations.push({
|
|
238
|
-
type: '
|
|
326
|
+
type: 'network',
|
|
239
327
|
promise: {
|
|
240
|
-
kind: '
|
|
241
|
-
value:
|
|
328
|
+
kind: 'request',
|
|
329
|
+
value: url,
|
|
330
|
+
method: call.method,
|
|
242
331
|
},
|
|
243
332
|
source: {
|
|
244
333
|
file: relPath,
|
|
245
|
-
line:
|
|
246
|
-
column:
|
|
334
|
+
line: call.location.line,
|
|
335
|
+
column: call.location.column,
|
|
336
|
+
context: call.context,
|
|
337
|
+
// PHASE 9: Include AST source code for evidence
|
|
338
|
+
astSource: call.astSource || null,
|
|
339
|
+
},
|
|
340
|
+
confidence: call.isUIBound ? 1.0 : 0.9, // Higher confidence for UI-bound handlers
|
|
341
|
+
metadata: {
|
|
342
|
+
networkKind: call.kind,
|
|
343
|
+
isUIBound: call.isUIBound || false,
|
|
344
|
+
// PHASE 9: Include AST source in metadata for evidence generation
|
|
345
|
+
astSource: call.astSource || null,
|
|
247
346
|
},
|
|
248
|
-
confidence: 1.0,
|
|
249
347
|
});
|
|
250
348
|
} else {
|
|
251
|
-
|
|
349
|
+
// Relative or non-http URLs
|
|
350
|
+
skipped.external++;
|
|
252
351
|
}
|
|
253
352
|
}
|
|
353
|
+
|
|
354
|
+
// PHASE 10: AST-based useState detection (state-driven UI promises)
|
|
355
|
+
const statePromises = detectUseStatePromises(content, filePath, relPath);
|
|
356
|
+
for (const statePromise of statePromises) {
|
|
357
|
+
// PHASE 10: Extract context and AST source from first setter call
|
|
358
|
+
const firstSetterCall = statePromise.metadata.setterCalls?.[0];
|
|
359
|
+
const context = firstSetterCall?.context || `component:${statePromise.componentName}`;
|
|
360
|
+
const astSource = firstSetterCall?.astSource || null;
|
|
361
|
+
const isUIBound = firstSetterCall?.isUIBound || false;
|
|
254
362
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
363
|
+
expectations.push({
|
|
364
|
+
type: 'state',
|
|
365
|
+
promise: {
|
|
366
|
+
kind: 'ui_state_change',
|
|
367
|
+
value: `${statePromise.stateName} in ${statePromise.componentName}`,
|
|
368
|
+
stateName: statePromise.stateName,
|
|
369
|
+
setterName: statePromise.setterName,
|
|
370
|
+
},
|
|
371
|
+
source: {
|
|
372
|
+
file: relPath,
|
|
373
|
+
line: statePromise.location.line,
|
|
374
|
+
column: statePromise.location.column,
|
|
375
|
+
context: context, // PHASE 10: Enhanced context (handler/hook/function)
|
|
376
|
+
astSource: astSource, // PHASE 10: AST source for evidence
|
|
377
|
+
},
|
|
378
|
+
confidence: isUIBound ? 1.0 : 0.9, // PHASE 10: Higher confidence for UI-bound handlers
|
|
379
|
+
metadata: {
|
|
380
|
+
componentName: statePromise.componentName,
|
|
381
|
+
setterCallCount: statePromise.setterCallCount,
|
|
382
|
+
jsxUsageCount: statePromise.jsxUsageCount,
|
|
383
|
+
usageTypes: statePromise.usageTypes,
|
|
384
|
+
hasUpdaterFunction: statePromise.metadata.hasUpdaterFunction,
|
|
385
|
+
// PHASE 10: Include all setter calls with context and AST source
|
|
386
|
+
setterCalls: statePromise.metadata.setterCalls || [],
|
|
387
|
+
isUIBound: isUIBound,
|
|
388
|
+
astSource: astSource, // PHASE 10: AST source in metadata for evidence generation
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// PHASE 11: AST-based interactive element detection (elements without href)
|
|
394
|
+
const interactiveElements = detectInteractiveElementsAST(content, filePath, relPath);
|
|
395
|
+
for (const element of interactiveElements) {
|
|
396
|
+
// Only create expectations for elements with navigation promises
|
|
397
|
+
if (element.navigationPromise) {
|
|
398
|
+
const promise = element.navigationPromise;
|
|
259
399
|
|
|
260
|
-
|
|
400
|
+
// Skip dynamic targets
|
|
401
|
+
if (promise.target === '<dynamic>') {
|
|
402
|
+
skipped.dynamic++;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
expectations.push({
|
|
407
|
+
type: 'navigation',
|
|
408
|
+
promise: {
|
|
409
|
+
kind: 'navigate',
|
|
410
|
+
value: promise.target,
|
|
411
|
+
method: promise.method || 'push',
|
|
412
|
+
},
|
|
413
|
+
source: {
|
|
414
|
+
file: relPath,
|
|
415
|
+
line: element.location.line,
|
|
416
|
+
column: element.location.column,
|
|
417
|
+
context: element.context,
|
|
418
|
+
astSource: element.astSource || promise.astSource || null,
|
|
419
|
+
},
|
|
420
|
+
confidence: element.isUIBound ? 1.0 : 0.9,
|
|
421
|
+
metadata: {
|
|
422
|
+
tagName: element.tagName,
|
|
423
|
+
role: element.role,
|
|
424
|
+
hasOnClick: element.hasOnClick,
|
|
425
|
+
hasOnSubmit: element.hasOnSubmit,
|
|
426
|
+
isRouterLink: element.isRouterLink,
|
|
427
|
+
navigationType: promise.type,
|
|
428
|
+
selectorHint: element.selectorHint,
|
|
429
|
+
isUIBound: element.isUIBound,
|
|
430
|
+
astSource: element.astSource || promise.astSource || null,
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
} else if (element.isRouterLink && (element.linkTo || element.linkHref)) {
|
|
434
|
+
// Router Link component with static href/to
|
|
435
|
+
const target = element.linkTo || element.linkHref;
|
|
436
|
+
|
|
437
|
+
if (target && target !== '<dynamic>') {
|
|
261
438
|
expectations.push({
|
|
262
439
|
type: 'navigation',
|
|
263
440
|
promise: {
|
|
264
441
|
kind: 'navigate',
|
|
265
|
-
value:
|
|
442
|
+
value: target,
|
|
266
443
|
},
|
|
267
444
|
source: {
|
|
268
445
|
file: relPath,
|
|
269
|
-
line:
|
|
270
|
-
column:
|
|
446
|
+
line: element.location.line,
|
|
447
|
+
column: element.location.column,
|
|
448
|
+
context: element.context,
|
|
449
|
+
astSource: element.astSource || null,
|
|
271
450
|
},
|
|
272
451
|
confidence: 1.0,
|
|
452
|
+
metadata: {
|
|
453
|
+
tagName: element.tagName,
|
|
454
|
+
isRouterLink: true,
|
|
455
|
+
selectorHint: element.selectorHint,
|
|
456
|
+
astSource: element.astSource || null,
|
|
457
|
+
},
|
|
273
458
|
});
|
|
274
|
-
} else {
|
|
275
|
-
skipped.dynamic++;
|
|
276
459
|
}
|
|
277
460
|
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
lines.forEach((line, lineIdx) => {
|
|
464
|
+
const lineNum = lineIdx + 1;
|
|
465
|
+
|
|
466
|
+
// Skip comments
|
|
467
|
+
if (line.trim().startsWith('//') || line.trim().startsWith('*')) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
278
470
|
|
|
279
|
-
// Extract
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
471
|
+
// Extract Next.js <Link href="/path">
|
|
472
|
+
const linkRegex = /<Link\s+[^>]*href=["']([^"']+)["']/g;
|
|
473
|
+
let match;
|
|
474
|
+
while ((match = linkRegex.exec(line)) !== null) {
|
|
475
|
+
const href = match[1];
|
|
283
476
|
|
|
284
|
-
//
|
|
285
|
-
if (
|
|
477
|
+
// Skip dynamic hrefs
|
|
478
|
+
if (!href.includes('${') && !href.includes('+') && !href.includes('`')) {
|
|
286
479
|
expectations.push({
|
|
287
|
-
type: '
|
|
480
|
+
type: 'navigation',
|
|
288
481
|
promise: {
|
|
289
|
-
kind: '
|
|
290
|
-
value:
|
|
482
|
+
kind: 'navigate',
|
|
483
|
+
value: href,
|
|
291
484
|
},
|
|
292
485
|
source: {
|
|
293
486
|
file: relPath,
|
|
@@ -296,24 +489,22 @@ function extractJsExpectations(content, filePath, relPath, skipped) {
|
|
|
296
489
|
},
|
|
297
490
|
confidence: 1.0,
|
|
298
491
|
});
|
|
299
|
-
} else if (!url.includes('${') && !url.includes('+') && !url.includes('`')) {
|
|
300
|
-
skipped.external++;
|
|
301
492
|
} else {
|
|
302
493
|
skipped.dynamic++;
|
|
303
494
|
}
|
|
304
495
|
}
|
|
305
496
|
|
|
306
|
-
// Extract
|
|
307
|
-
const
|
|
308
|
-
while ((match =
|
|
309
|
-
const
|
|
497
|
+
// Extract router.push("/path")
|
|
498
|
+
const routerPushRegex = /router\.push\(["']([^"']+)["']\)/g;
|
|
499
|
+
while ((match = routerPushRegex.exec(line)) !== null) {
|
|
500
|
+
const path = match[1];
|
|
310
501
|
|
|
311
|
-
if (
|
|
502
|
+
if (!path.includes('${') && !path.includes('+') && !path.includes('`')) {
|
|
312
503
|
expectations.push({
|
|
313
|
-
type: '
|
|
504
|
+
type: 'navigation',
|
|
314
505
|
promise: {
|
|
315
|
-
kind: '
|
|
316
|
-
value:
|
|
506
|
+
kind: 'navigate',
|
|
507
|
+
value: path,
|
|
317
508
|
},
|
|
318
509
|
source: {
|
|
319
510
|
file: relPath,
|
|
@@ -322,31 +513,11 @@ function extractJsExpectations(content, filePath, relPath, skipped) {
|
|
|
322
513
|
},
|
|
323
514
|
confidence: 1.0,
|
|
324
515
|
});
|
|
325
|
-
} else if (!url.includes('${') && !url.includes('+') && !url.includes('`')) {
|
|
326
|
-
skipped.external++;
|
|
327
516
|
} else {
|
|
328
517
|
skipped.dynamic++;
|
|
329
518
|
}
|
|
330
519
|
}
|
|
331
520
|
|
|
332
|
-
// Extract useState setters
|
|
333
|
-
const useStateRegex = /useState\([^)]*\)/g;
|
|
334
|
-
if ((match = useStateRegex.exec(line)) !== null) {
|
|
335
|
-
expectations.push({
|
|
336
|
-
type: 'state',
|
|
337
|
-
promise: {
|
|
338
|
-
kind: 'state_mutation',
|
|
339
|
-
value: 'state management',
|
|
340
|
-
},
|
|
341
|
-
source: {
|
|
342
|
-
file: relPath,
|
|
343
|
-
line: lineNum,
|
|
344
|
-
column: match.index,
|
|
345
|
-
},
|
|
346
|
-
confidence: 0.8,
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
|
|
350
521
|
// Extract Redux dispatch calls
|
|
351
522
|
const dispatchRegex = /dispatch\(\{/g;
|
|
352
523
|
if ((match = dispatchRegex.exec(line)) !== null) {
|
|
@@ -386,3 +557,128 @@ function extractJsExpectations(content, filePath, relPath, skipped) {
|
|
|
386
557
|
|
|
387
558
|
return expectations;
|
|
388
559
|
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* PHASE 20: Extract expectations from Vue SFC files
|
|
563
|
+
*/
|
|
564
|
+
function extractVueExpectations(content, filePath, relPath, skipped) {
|
|
565
|
+
const expectations = [];
|
|
566
|
+
|
|
567
|
+
try {
|
|
568
|
+
// Extract SFC blocks
|
|
569
|
+
const sfc = extractVueSFC(content);
|
|
570
|
+
|
|
571
|
+
// Extract template bindings
|
|
572
|
+
let templateBindings = null;
|
|
573
|
+
if (sfc.template) {
|
|
574
|
+
templateBindings = extractTemplateBindings(sfc.template.content);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Process each script block
|
|
578
|
+
for (const scriptBlock of sfc.scriptBlocks) {
|
|
579
|
+
const scriptContent = scriptBlock.content;
|
|
580
|
+
const startLine = scriptBlock.startLine;
|
|
581
|
+
|
|
582
|
+
// Map template handlers to script functions
|
|
583
|
+
let handlerMap = new Map();
|
|
584
|
+
if (templateBindings) {
|
|
585
|
+
handlerMap = mapTemplateHandlersToScript(templateBindings, [scriptBlock]);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// PHASE 20: Detect Vue navigation promises (router-link, router.push/replace)
|
|
589
|
+
const navigationPromises = detectVueNavigationPromises(
|
|
590
|
+
scriptContent,
|
|
591
|
+
filePath,
|
|
592
|
+
relPath,
|
|
593
|
+
scriptBlock,
|
|
594
|
+
templateBindings
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
for (const navPromise of navigationPromises) {
|
|
598
|
+
// Adjust line numbers for SFC
|
|
599
|
+
navPromise.source.line = startLine + (navPromise.source.line || 1) - 1;
|
|
600
|
+
expectations.push(navPromise);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// PHASE 20: Detect network calls in Vue script blocks
|
|
604
|
+
const networkCalls = detectNetworkCallsAST(scriptContent, filePath, relPath);
|
|
605
|
+
for (const call of networkCalls) {
|
|
606
|
+
const url = call.url;
|
|
607
|
+
|
|
608
|
+
if (url === '<dynamic>') {
|
|
609
|
+
skipped.dynamic++;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Check if handler is UI-bound via template
|
|
614
|
+
const isUIBound = handlerMap.has(call.handlerName || '');
|
|
615
|
+
|
|
616
|
+
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('/api/')) {
|
|
617
|
+
expectations.push({
|
|
618
|
+
type: 'network',
|
|
619
|
+
promise: {
|
|
620
|
+
kind: 'request',
|
|
621
|
+
value: url,
|
|
622
|
+
method: call.method,
|
|
623
|
+
},
|
|
624
|
+
source: {
|
|
625
|
+
file: relPath,
|
|
626
|
+
line: startLine + (call.location.line || 1) - 1,
|
|
627
|
+
column: call.location.column || 0,
|
|
628
|
+
context: call.context,
|
|
629
|
+
astSource: call.astSource || null,
|
|
630
|
+
},
|
|
631
|
+
confidence: isUIBound ? 1.0 : 0.9,
|
|
632
|
+
metadata: {
|
|
633
|
+
networkKind: call.kind,
|
|
634
|
+
isUIBound: isUIBound || false,
|
|
635
|
+
astSource: call.astSource || null,
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
} else {
|
|
639
|
+
skipped.external++;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// PHASE 20: Detect Vue state promises (ref/reactive)
|
|
644
|
+
if (templateBindings) {
|
|
645
|
+
const statePromises = detectVueStatePromises(
|
|
646
|
+
scriptContent,
|
|
647
|
+
filePath,
|
|
648
|
+
relPath,
|
|
649
|
+
scriptBlock,
|
|
650
|
+
templateBindings
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
for (const statePromise of statePromises) {
|
|
654
|
+
statePromise.source.line = startLine + (statePromise.source.line || 1) - 1;
|
|
655
|
+
expectations.push(statePromise);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// PHASE 20: Detect interactive elements from template
|
|
660
|
+
if (templateBindings) {
|
|
661
|
+
for (const routerLink of templateBindings.routerLinks) {
|
|
662
|
+
expectations.push({
|
|
663
|
+
type: 'navigation',
|
|
664
|
+
promise: {
|
|
665
|
+
kind: 'navigate',
|
|
666
|
+
value: routerLink.to,
|
|
667
|
+
},
|
|
668
|
+
source: {
|
|
669
|
+
file: relPath,
|
|
670
|
+
line: sfc.template.startLine,
|
|
671
|
+
column: 0,
|
|
672
|
+
context: 'template',
|
|
673
|
+
},
|
|
674
|
+
confidence: 1.0,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
} catch (error) {
|
|
680
|
+
skipped.parseError++;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return expectations;
|
|
684
|
+
}
|