@veraxhq/verax 0.2.1 → 0.4.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 +10 -6
- package/bin/verax.js +11 -11
- package/package.json +29 -8
- package/src/cli/commands/baseline.js +103 -0
- package/src/cli/commands/default.js +51 -6
- package/src/cli/commands/doctor.js +29 -0
- package/src/cli/commands/ga.js +246 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +4 -2
- package/src/cli/commands/release-check.js +215 -0
- package/src/cli/commands/run.js +45 -6
- package/src/cli/commands/security-check.js +212 -0
- package/src/cli/commands/truth.js +113 -0
- package/src/cli/entry.js +30 -20
- 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 +544 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-promise-extractor.js +581 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/atomic-write.js +12 -1
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/console-reporter.js +72 -0
- package/src/cli/util/detection-engine.js +105 -41
- package/src/cli/util/determinism-runner.js +124 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/digest-engine.js +359 -0
- package/src/cli/util/dom-diff.js +226 -0
- package/src/cli/util/evidence-engine.js +287 -0
- package/src/cli/util/expectation-extractor.js +151 -5
- package/src/cli/util/findings-writer.js +3 -0
- package/src/cli/util/framework-detector.js +572 -0
- package/src/cli/util/idgen.js +1 -1
- package/src/cli/util/interaction-planner.js +529 -0
- package/src/cli/util/learn-writer.js +2 -0
- package/src/cli/util/ledger-writer.js +110 -0
- package/src/cli/util/monorepo-resolver.js +162 -0
- package/src/cli/util/observation-engine.js +127 -278
- package/src/cli/util/observe-writer.js +2 -0
- package/src/cli/util/project-discovery.js +284 -0
- package/src/cli/util/project-writer.js +2 -0
- package/src/cli/util/run-id.js +23 -27
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/run-result.js +778 -0
- package/src/cli/util/selector-resolver.js +235 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +2 -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 +146 -0
- package/src/cli/util/svelte-state-detector.js +242 -0
- package/src/cli/util/trust-activation-integration.js +496 -0
- package/src/cli/util/trust-activation-wrapper.js +85 -0
- package/src/cli/util/trust-integration-hooks.js +164 -0
- package/src/cli/util/types.js +153 -0
- package/src/cli/util/url-validation.js +40 -0
- package/src/cli/util/vue-navigation-detector.js +178 -0
- package/src/cli/util/vue-sfc-extractor.js +161 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/fs-augment.d.ts +23 -0
- package/src/types/global.d.ts +137 -0
- package/src/types/internal-types.d.ts +35 -0
- package/src/verax/cli/init.js +4 -18
- package/src/verax/core/action-classifier.js +4 -3
- package/src/verax/core/artifacts/registry.js +139 -0
- package/src/verax/core/artifacts/verifier.js +990 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +233 -0
- package/src/verax/core/capabilities/gates.js +505 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +144 -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 +80 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +489 -0
- package/src/verax/core/confidence-engine.js +625 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +186 -0
- package/src/verax/core/contracts/validators.js +456 -0
- package/src/verax/core/decisions/decision.trace.js +278 -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 +405 -0
- package/src/verax/core/determinism/engine.js +222 -0
- package/src/verax/core/determinism/finding-identity.js +149 -0
- package/src/verax/core/determinism/normalize.js +466 -0
- package/src/verax/core/determinism/report-writer.js +93 -0
- package/src/verax/core/determinism/run-fingerprint.js +123 -0
- package/src/verax/core/dynamic-route-intelligence.js +529 -0
- package/src/verax/core/evidence/evidence-capture-service.js +308 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +166 -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 +192 -0
- package/src/verax/core/failures/exit-codes.js +88 -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 +133 -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 +435 -0
- package/src/verax/core/ga/ga.enforcer.js +87 -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 +84 -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 +1 -0
- package/src/verax/core/integrity/budget.js +138 -0
- package/src/verax/core/integrity/determinism.js +342 -0
- package/src/verax/core/integrity/integrity.js +208 -0
- package/src/verax/core/integrity/poisoning.js +108 -0
- package/src/verax/core/integrity/transaction.js +140 -0
- package/src/verax/core/observe/run-timeline.js +318 -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 +200 -0
- package/src/verax/core/pipeline-tracker.js +243 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +130 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +164 -0
- package/src/verax/core/release/reproducibility.check.js +222 -0
- package/src/verax/core/release/sbom.builder.js +292 -0
- package/src/verax/core/replay-validator.js +2 -0
- package/src/verax/core/replay.js +4 -0
- package/src/verax/core/report/cross-index.js +195 -0
- package/src/verax/core/report/human-summary.js +362 -0
- package/src/verax/core/route-intelligence.js +420 -0
- package/src/verax/core/run-id.js +6 -3
- package/src/verax/core/run-manifest.js +4 -3
- package/src/verax/core/security/secrets.scan.js +329 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +128 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +334 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/truth/truth.certificate.js +252 -0
- package/src/verax/core/ui-feedback-intelligence.js +481 -0
- package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
- package/src/verax/detect/confidence-engine.js +62 -34
- package/src/verax/detect/confidence-helper.js +34 -0
- package/src/verax/detect/dynamic-route-findings.js +338 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +2 -2
- package/src/verax/detect/failure-cause-inference.js +293 -0
- package/src/verax/detect/findings-writer.js +131 -35
- package/src/verax/detect/flow-detector.js +2 -2
- package/src/verax/detect/form-silent-failure.js +98 -0
- package/src/verax/detect/index.js +46 -5
- package/src/verax/detect/invariants-enforcer.js +147 -0
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/navigation-silent-failure.js +82 -0
- package/src/verax/detect/problem-aggregator.js +361 -0
- package/src/verax/detect/route-findings.js +219 -0
- package/src/verax/detect/summary-writer.js +477 -0
- package/src/verax/detect/test-failure-cause-inference.js +314 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +2 -1
- package/src/verax/flow/flow-spec.js +0 -6
- package/src/verax/index.js +4 -0
- package/src/verax/intel/ts-program.js +1 -0
- package/src/verax/intel/vue-navigation-extractor.js +3 -0
- package/src/verax/learn/action-contract-extractor.js +3 -0
- package/src/verax/learn/ast-contract-extractor.js +1 -1
- package/src/verax/learn/flow-extractor.js +1 -0
- package/src/verax/learn/project-detector.js +5 -0
- package/src/verax/learn/react-router-extractor.js +2 -0
- package/src/verax/learn/source-instrumenter.js +1 -0
- package/src/verax/learn/state-extractor.js +2 -1
- package/src/verax/learn/static-extractor.js +1 -0
- package/src/verax/observe/coverage-gaps.js +132 -0
- package/src/verax/observe/expectation-handler.js +126 -0
- package/src/verax/observe/incremental-skip.js +46 -0
- package/src/verax/observe/index.js +51 -155
- package/src/verax/observe/interaction-executor.js +192 -0
- package/src/verax/observe/interaction-runner.js +782 -513
- package/src/verax/observe/network-firewall.js +86 -0
- package/src/verax/observe/observation-builder.js +169 -0
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +192 -0
- package/src/verax/observe/observe-runner.js +230 -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/page-traversal.js +138 -0
- package/src/verax/observe/snapshot-ops.js +94 -0
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/scan-summary-writer.js +2 -0
- package/src/verax/shared/artifact-manager.js +25 -5
- package/src/verax/shared/caching.js +1 -0
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/expectation-tracker.js +1 -0
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +6 -0
- package/src/verax/shared/config-loader.js +0 -169
- /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import _traverse from '@babel/traverse';
|
|
3
|
+
|
|
4
|
+
// Handle default export from @babel/traverse (CommonJS/ESM compatibility)
|
|
5
|
+
const traverse = _traverse.default || _traverse;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* PHASE 9 — AST-Based Network Detection
|
|
9
|
+
*
|
|
10
|
+
* Production-quality AST-based network call detection.
|
|
11
|
+
* Detects fetch/axios/XMLHttpRequest calls in nested contexts:
|
|
12
|
+
* - Event handlers (onClick, onSubmit, etc.)
|
|
13
|
+
* - React hooks (useEffect, useCallback, etc.)
|
|
14
|
+
* - Custom functions bound to UI interactions
|
|
15
|
+
*
|
|
16
|
+
* Features:
|
|
17
|
+
* - AST source code extraction for evidence
|
|
18
|
+
* - False-positive filtering (analytics)
|
|
19
|
+
* - UI-bound handler detection
|
|
20
|
+
* - Deterministic behavior
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Detect network calls in source code using AST parsing
|
|
25
|
+
* @param {string} content - File content
|
|
26
|
+
* @param {string} _filePath - Absolute file path (unused)
|
|
27
|
+
* @param {string} _relPath - Relative path from source root (unused)
|
|
28
|
+
* @returns {Array} Array of detected network calls with metadata including AST source
|
|
29
|
+
*/
|
|
30
|
+
export function detectNetworkCallsAST(content, _filePath, _relPath) {
|
|
31
|
+
const detections = [];
|
|
32
|
+
const lines = content.split('\n');
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Parse with comprehensive plugin support
|
|
36
|
+
const ast = parse(content, {
|
|
37
|
+
sourceType: 'module',
|
|
38
|
+
plugins: [
|
|
39
|
+
'jsx',
|
|
40
|
+
'typescript',
|
|
41
|
+
'classProperties',
|
|
42
|
+
'optionalChaining',
|
|
43
|
+
'nullishCoalescingOperator',
|
|
44
|
+
'dynamicImport',
|
|
45
|
+
['decorators', { decoratorsBeforeExport: true }],
|
|
46
|
+
'topLevelAwait',
|
|
47
|
+
'objectRestSpread',
|
|
48
|
+
'asyncGenerators',
|
|
49
|
+
'functionBind',
|
|
50
|
+
'exportDefaultFrom',
|
|
51
|
+
'exportNamespaceFrom',
|
|
52
|
+
],
|
|
53
|
+
errorRecovery: true,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Track axios imports and aliases
|
|
57
|
+
const axiosBindings = new Set();
|
|
58
|
+
|
|
59
|
+
traverse(ast, {
|
|
60
|
+
// Track axios imports
|
|
61
|
+
ImportDeclaration(path) {
|
|
62
|
+
if (path.node.source.value === 'axios') {
|
|
63
|
+
path.node.specifiers.forEach((spec) => {
|
|
64
|
+
if (spec.type === 'ImportDefaultSpecifier' ||
|
|
65
|
+
spec.type === 'ImportSpecifier') {
|
|
66
|
+
axiosBindings.add(spec.local.name);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// Detect require('axios')
|
|
73
|
+
VariableDeclarator(path) {
|
|
74
|
+
if (path.node.init?.type === 'CallExpression' &&
|
|
75
|
+
path.node.init.callee.name === 'require' &&
|
|
76
|
+
path.node.init.arguments[0]?.value === 'axios') {
|
|
77
|
+
if (path.node.id.type === 'Identifier') {
|
|
78
|
+
axiosBindings.add(path.node.id.name);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// Detect fetch() calls
|
|
84
|
+
CallExpression(path) {
|
|
85
|
+
const { node } = path;
|
|
86
|
+
const loc = node.loc;
|
|
87
|
+
|
|
88
|
+
// Check for fetch(...)
|
|
89
|
+
if (node.callee.type === 'Identifier' && node.callee.name === 'fetch') {
|
|
90
|
+
// Check if fetch is shadowed in local scope
|
|
91
|
+
if (path.scope.hasBinding('fetch')) {
|
|
92
|
+
return; // Shadowed by local variable/parameter
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const urlArg = node.arguments[0];
|
|
96
|
+
const initArg = node.arguments[1];
|
|
97
|
+
const url = extractUrl(urlArg);
|
|
98
|
+
|
|
99
|
+
// PHASE 9: Filter false positives (analytics calls)
|
|
100
|
+
if (isAnalyticsCall(url, path)) {
|
|
101
|
+
return; // Skip analytics - not a user-facing promise
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const context = inferContext(path);
|
|
105
|
+
const isUIBound = isUIBoundHandler(path);
|
|
106
|
+
|
|
107
|
+
const detection = {
|
|
108
|
+
kind: 'fetch',
|
|
109
|
+
url: url,
|
|
110
|
+
method: extractMethod(initArg, 'GET'),
|
|
111
|
+
location: {
|
|
112
|
+
line: loc?.start.line,
|
|
113
|
+
column: loc?.start.column,
|
|
114
|
+
},
|
|
115
|
+
context: context,
|
|
116
|
+
isUIBound: isUIBound,
|
|
117
|
+
astSource: extractASTSource(node, lines, loc),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
detections.push(detection);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check for globalThis.fetch(...)
|
|
124
|
+
if (node.callee.type === 'MemberExpression' &&
|
|
125
|
+
node.callee.object.name === 'globalThis' &&
|
|
126
|
+
node.callee.property.name === 'fetch') {
|
|
127
|
+
const urlArg = node.arguments[0];
|
|
128
|
+
const initArg = node.arguments[1];
|
|
129
|
+
const url = extractUrl(urlArg);
|
|
130
|
+
|
|
131
|
+
// PHASE 9: Filter false positives (analytics calls)
|
|
132
|
+
if (isAnalyticsCall(url, path)) {
|
|
133
|
+
return; // Skip analytics - not a user-facing promise
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const context = inferContext(path);
|
|
137
|
+
const isUIBound = isUIBoundHandler(path);
|
|
138
|
+
|
|
139
|
+
const detection = {
|
|
140
|
+
kind: 'fetch',
|
|
141
|
+
url: url,
|
|
142
|
+
method: extractMethod(initArg, 'GET'),
|
|
143
|
+
location: {
|
|
144
|
+
line: loc?.start.line,
|
|
145
|
+
column: loc?.start.column,
|
|
146
|
+
},
|
|
147
|
+
context: context,
|
|
148
|
+
isUIBound: isUIBound,
|
|
149
|
+
astSource: extractASTSource(node, lines, loc),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
detections.push(detection);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check for axios(...) or axios.get/post/etc(...)
|
|
156
|
+
if (isAxiosCall(node, axiosBindings)) {
|
|
157
|
+
const method = extractAxiosMethod(node);
|
|
158
|
+
const urlArg = getAxiosUrlArg(node, method);
|
|
159
|
+
const url = extractUrl(urlArg);
|
|
160
|
+
|
|
161
|
+
// PHASE 9: Filter false positives (analytics calls)
|
|
162
|
+
if (isAnalyticsCall(url, path)) {
|
|
163
|
+
return; // Skip analytics - not a user-facing promise
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const context = inferContext(path);
|
|
167
|
+
const isUIBound = isUIBoundHandler(path);
|
|
168
|
+
|
|
169
|
+
const detection = {
|
|
170
|
+
kind: 'axios',
|
|
171
|
+
url: url,
|
|
172
|
+
method: method.toUpperCase(),
|
|
173
|
+
location: {
|
|
174
|
+
line: loc?.start.line,
|
|
175
|
+
column: loc?.start.column,
|
|
176
|
+
},
|
|
177
|
+
context: context,
|
|
178
|
+
isUIBound: isUIBound,
|
|
179
|
+
astSource: extractASTSource(node, lines, loc),
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
detections.push(detection);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
// Detect new XMLHttpRequest()
|
|
187
|
+
NewExpression(path) {
|
|
188
|
+
const { node } = path;
|
|
189
|
+
const loc = node.loc;
|
|
190
|
+
|
|
191
|
+
if (node.callee.type === 'Identifier' &&
|
|
192
|
+
node.callee.name === 'XMLHttpRequest') {
|
|
193
|
+
|
|
194
|
+
// Try to find associated .open() call
|
|
195
|
+
const xhrDetails = findXhrOpen(path);
|
|
196
|
+
|
|
197
|
+
const url = xhrDetails.url || '<dynamic>';
|
|
198
|
+
|
|
199
|
+
// PHASE 9: Filter false positives (analytics calls)
|
|
200
|
+
if (isAnalyticsCall(url, path)) {
|
|
201
|
+
return; // Skip analytics - not a user-facing promise
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const context = inferContext(path);
|
|
205
|
+
const isUIBound = isUIBoundHandler(path);
|
|
206
|
+
|
|
207
|
+
const detection = {
|
|
208
|
+
kind: 'xhr',
|
|
209
|
+
url: url,
|
|
210
|
+
method: xhrDetails.method || 'GET',
|
|
211
|
+
location: {
|
|
212
|
+
line: loc?.start.line,
|
|
213
|
+
column: loc?.start.column,
|
|
214
|
+
},
|
|
215
|
+
context: context,
|
|
216
|
+
isUIBound: isUIBound,
|
|
217
|
+
astSource: extractASTSource(node, lines, loc),
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
detections.push(detection);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
} catch (error) {
|
|
226
|
+
// Parse errors are silently skipped (malformed code, etc.)
|
|
227
|
+
// In production, you might log these for debugging
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return detections;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Check if a call expression is an axios call
|
|
235
|
+
*/
|
|
236
|
+
function isAxiosCall(node, axiosBindings) {
|
|
237
|
+
// Direct axios call: axios(...)
|
|
238
|
+
if (node.callee.type === 'Identifier' &&
|
|
239
|
+
axiosBindings.has(node.callee.name)) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Method call: axios.get/post/etc(...)
|
|
244
|
+
if (node.callee.type === 'MemberExpression' &&
|
|
245
|
+
node.callee.object.type === 'Identifier' &&
|
|
246
|
+
axiosBindings.has(node.callee.object.name)) {
|
|
247
|
+
const method = node.callee.property.name;
|
|
248
|
+
return ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'request'].includes(method);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Extract HTTP method from axios call
|
|
256
|
+
*/
|
|
257
|
+
function extractAxiosMethod(node) {
|
|
258
|
+
if (node.callee.type === 'MemberExpression') {
|
|
259
|
+
return node.callee.property.name; // get, post, etc.
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Direct axios(...) call - check config.method
|
|
263
|
+
const configArg = node.arguments[0];
|
|
264
|
+
if (configArg?.type === 'ObjectExpression') {
|
|
265
|
+
const methodProp = configArg.properties.find(
|
|
266
|
+
p => p.key?.name === 'method'
|
|
267
|
+
);
|
|
268
|
+
if (methodProp?.value.type === 'StringLiteral') {
|
|
269
|
+
return methodProp.value.value;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return 'request'; // default for axios(config)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get URL argument for axios call
|
|
278
|
+
*/
|
|
279
|
+
function getAxiosUrlArg(node, method) {
|
|
280
|
+
// axios.get(url, ...) - URL is first arg
|
|
281
|
+
if (method !== 'request' && node.callee.type === 'MemberExpression') {
|
|
282
|
+
return node.arguments[0];
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// axios(config) - URL is in config.url
|
|
286
|
+
const configArg = node.arguments[0];
|
|
287
|
+
if (configArg?.type === 'ObjectExpression') {
|
|
288
|
+
const urlProp = configArg.properties.find(
|
|
289
|
+
p => p.key?.name === 'url'
|
|
290
|
+
);
|
|
291
|
+
return urlProp?.value;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Extract URL from argument node
|
|
299
|
+
*/
|
|
300
|
+
function extractUrl(urlArg) {
|
|
301
|
+
if (!urlArg) {
|
|
302
|
+
return '<dynamic>';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// String literal: "https://example.com"
|
|
306
|
+
if (urlArg.type === 'StringLiteral') {
|
|
307
|
+
return urlArg.value;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Template literal without expressions: `https://example.com`
|
|
311
|
+
if (urlArg.type === 'TemplateLiteral' &&
|
|
312
|
+
urlArg.expressions.length === 0) {
|
|
313
|
+
return urlArg.quasis[0].value.cooked;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Template literal with expressions: `https://example.com/${id}`
|
|
317
|
+
if (urlArg.type === 'TemplateLiteral' &&
|
|
318
|
+
urlArg.expressions.length > 0) {
|
|
319
|
+
return '<dynamic>';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Any other expression (variable, computation, etc.)
|
|
323
|
+
return '<dynamic>';
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Extract HTTP method from fetch init object
|
|
328
|
+
*/
|
|
329
|
+
function extractMethod(initArg, defaultMethod = 'GET') {
|
|
330
|
+
if (!initArg || initArg.type !== 'ObjectExpression') {
|
|
331
|
+
return defaultMethod;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const methodProp = initArg.properties.find(
|
|
335
|
+
p => p.key?.name === 'method'
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
if (methodProp?.value.type === 'StringLiteral') {
|
|
339
|
+
return methodProp.value.value.toUpperCase();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return defaultMethod;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Infer execution context (handler, hook, component, etc.)
|
|
347
|
+
*/
|
|
348
|
+
function inferContext(path) {
|
|
349
|
+
const contexts = [];
|
|
350
|
+
|
|
351
|
+
let current = path.parentPath;
|
|
352
|
+
while (current) {
|
|
353
|
+
const node = current.node;
|
|
354
|
+
|
|
355
|
+
// Event handler prop: onClick={() => ...}
|
|
356
|
+
if (current.isJSXAttribute()) {
|
|
357
|
+
const attrName = node.name.name;
|
|
358
|
+
if (attrName && attrName.startsWith('on')) {
|
|
359
|
+
contexts.push(`handler:${attrName}`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Hook: useEffect(() => ...), useCallback(() => ...)
|
|
364
|
+
if (current.isCallExpression() &&
|
|
365
|
+
current.node.callee.type === 'Identifier') {
|
|
366
|
+
const calleeName = current.node.callee.name;
|
|
367
|
+
if (calleeName.startsWith('use')) {
|
|
368
|
+
contexts.push(`hook:${calleeName}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Function/Arrow in component
|
|
373
|
+
if (current.isFunctionDeclaration() ||
|
|
374
|
+
current.isFunctionExpression() ||
|
|
375
|
+
current.isArrowFunctionExpression()) {
|
|
376
|
+
const funcName = getFunctionName(current);
|
|
377
|
+
if (funcName) {
|
|
378
|
+
// Check if it looks like a handler
|
|
379
|
+
if (funcName.startsWith('handle') || funcName.startsWith('on')) {
|
|
380
|
+
contexts.push(`handler:${funcName}`);
|
|
381
|
+
} else {
|
|
382
|
+
contexts.push(`function:${funcName}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
current = current.parentPath;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return contexts.length > 0 ? contexts.reverse().join(' > ') : 'top-level';
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Get function name from path
|
|
395
|
+
*/
|
|
396
|
+
function getFunctionName(path) {
|
|
397
|
+
const node = path.node;
|
|
398
|
+
|
|
399
|
+
// Named function
|
|
400
|
+
if (node.id?.name) {
|
|
401
|
+
return node.id.name;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Variable declarator: const handleClick = () => ...
|
|
405
|
+
const parent = path.parent;
|
|
406
|
+
if (parent.type === 'VariableDeclarator' && parent.id.name) {
|
|
407
|
+
return parent.id.name;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Object property: { onClick: () => ... }
|
|
411
|
+
if (parent.type === 'ObjectProperty' && parent.key.name) {
|
|
412
|
+
return parent.key.name;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Try to find xhr.open() call for an XMLHttpRequest instance
|
|
420
|
+
* This is a best-effort heuristic
|
|
421
|
+
*/
|
|
422
|
+
function findXhrOpen(newExprPath) {
|
|
423
|
+
const result = { url: null, method: null };
|
|
424
|
+
|
|
425
|
+
// Check if assigned to a variable
|
|
426
|
+
const parent = newExprPath.parent;
|
|
427
|
+
if (parent.type === 'VariableDeclarator' && parent.id.name) {
|
|
428
|
+
const xhrName = parent.id.name;
|
|
429
|
+
|
|
430
|
+
// Look for xhr.open(...) in the same scope
|
|
431
|
+
const binding = newExprPath.scope.getBinding(xhrName);
|
|
432
|
+
if (binding) {
|
|
433
|
+
// Scan references for .open() calls
|
|
434
|
+
for (const refPath of binding.referencePaths) {
|
|
435
|
+
const refParent = refPath.parent;
|
|
436
|
+
if (refParent.type === 'MemberExpression' &&
|
|
437
|
+
refParent.property.name === 'open') {
|
|
438
|
+
// Check if it's a call expression
|
|
439
|
+
const callParent = refPath.parentPath.parent;
|
|
440
|
+
if (callParent.type === 'CallExpression') {
|
|
441
|
+
// xhr.open(method, url, ...)
|
|
442
|
+
const methodArg = callParent.arguments[0];
|
|
443
|
+
const urlArg = callParent.arguments[1];
|
|
444
|
+
|
|
445
|
+
result.method = extractUrl(methodArg)?.toUpperCase() || 'GET';
|
|
446
|
+
result.url = extractUrl(urlArg);
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return result;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* PHASE 9: Extract AST source code snippet for evidence
|
|
459
|
+
* @param {Object} node - AST node
|
|
460
|
+
* @param {string[]} lines - File content split by lines
|
|
461
|
+
* @param {Object} loc - Location object with start/end
|
|
462
|
+
* @returns {string} Source code snippet
|
|
463
|
+
*/
|
|
464
|
+
function extractASTSource(node, lines, loc) {
|
|
465
|
+
if (!loc || !loc.start || !loc.end) {
|
|
466
|
+
return '';
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const startLine = loc.start.line - 1; // 0-indexed
|
|
470
|
+
const endLine = loc.end.line - 1;
|
|
471
|
+
|
|
472
|
+
if (startLine < 0 || endLine >= lines.length) {
|
|
473
|
+
return '';
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (startLine === endLine) {
|
|
477
|
+
// Single line - extract substring
|
|
478
|
+
const line = lines[startLine];
|
|
479
|
+
const startCol = loc.start.column;
|
|
480
|
+
const endCol = loc.end.column;
|
|
481
|
+
return line.substring(startCol, endCol).trim();
|
|
482
|
+
} else {
|
|
483
|
+
// Multi-line - extract full lines
|
|
484
|
+
const snippet = lines.slice(startLine, endLine + 1);
|
|
485
|
+
// Trim first line from start column, last line to end column
|
|
486
|
+
if (snippet.length > 0) {
|
|
487
|
+
snippet[0] = snippet[0].substring(loc.start.column);
|
|
488
|
+
snippet[snippet.length - 1] = snippet[snippet.length - 1].substring(0, loc.end.column);
|
|
489
|
+
}
|
|
490
|
+
return snippet.join('\n').trim();
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* PHASE 9: Check if network call is analytics (false positive trap)
|
|
496
|
+
* Analytics calls should NOT be reported as user-facing promises
|
|
497
|
+
* @param {string} url - Network call URL
|
|
498
|
+
* @param {Object} path - Babel path object
|
|
499
|
+
* @returns {boolean} True if this is an analytics call
|
|
500
|
+
*/
|
|
501
|
+
function isAnalyticsCall(url, path) {
|
|
502
|
+
// Check URL patterns
|
|
503
|
+
if (typeof url === 'string') {
|
|
504
|
+
const analyticsPatterns = [
|
|
505
|
+
'/api/analytics',
|
|
506
|
+
'/analytics',
|
|
507
|
+
'/track',
|
|
508
|
+
'/api/track',
|
|
509
|
+
'/api/event',
|
|
510
|
+
'/events',
|
|
511
|
+
'/beacon',
|
|
512
|
+
'/api/beacon',
|
|
513
|
+
];
|
|
514
|
+
|
|
515
|
+
for (const pattern of analyticsPatterns) {
|
|
516
|
+
if (url.includes(pattern)) {
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Check context for analytics-related function names
|
|
523
|
+
const context = inferContext(path);
|
|
524
|
+
const analyticsContexts = [
|
|
525
|
+
'track',
|
|
526
|
+
'analytics',
|
|
527
|
+
'beacon',
|
|
528
|
+
'telemetry',
|
|
529
|
+
'metrics',
|
|
530
|
+
];
|
|
531
|
+
|
|
532
|
+
for (const keyword of analyticsContexts) {
|
|
533
|
+
if (context.toLowerCase().includes(keyword)) {
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Check parent function/variable names
|
|
539
|
+
let current = path.parentPath;
|
|
540
|
+
while (current) {
|
|
541
|
+
const funcName = getFunctionName(current);
|
|
542
|
+
if (funcName) {
|
|
543
|
+
const lowerName = funcName.toLowerCase();
|
|
544
|
+
if (analyticsContexts.some(keyword => lowerName.includes(keyword))) {
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
current = current.parentPath;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* PHASE 9: Determine if handler is UI-bound (connected to user interaction)
|
|
556
|
+
* @param {Object} path - Babel path object
|
|
557
|
+
* @returns {boolean} True if handler is bound to UI interaction
|
|
558
|
+
*/
|
|
559
|
+
function isUIBoundHandler(path) {
|
|
560
|
+
const context = inferContext(path);
|
|
561
|
+
|
|
562
|
+
// Direct event handlers (onClick, onSubmit, etc.)
|
|
563
|
+
if (context.includes('handler:on')) {
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Handler functions (handleClick, handleSubmit, etc.)
|
|
568
|
+
if (context.includes('handler:handle')) {
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Check if function is referenced in JSX
|
|
573
|
+
let current = path.parentPath;
|
|
574
|
+
while (current) {
|
|
575
|
+
// Check if we're inside JSX attribute
|
|
576
|
+
if (current.isJSXAttribute()) {
|
|
577
|
+
const attrName = current.node.name?.name;
|
|
578
|
+
if (attrName && attrName.startsWith('on')) {
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Check if function is assigned to event handler
|
|
584
|
+
if (current.isVariableDeclarator()) {
|
|
585
|
+
const varName = current.node.id?.name;
|
|
586
|
+
if (varName && (varName.startsWith('handle') || varName.startsWith('on'))) {
|
|
587
|
+
// Check if this variable is used in JSX
|
|
588
|
+
const binding = current.scope.getBinding(varName);
|
|
589
|
+
if (binding) {
|
|
590
|
+
for (const refPath of binding.referencePaths) {
|
|
591
|
+
if (refPath.findParent(p => p.isJSXAttribute())) {
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
current = current.parentPath;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return false;
|
|
603
|
+
}
|