@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,581 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import _traverse from '@babel/traverse';
|
|
3
|
+
import { readFileSync as _readFileSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
// Handle default export from @babel/traverse (CommonJS/ESM compatibility)
|
|
6
|
+
const traverse = _traverse.default || _traverse;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* PHASE H2/M2 — AST-Based Promise Extraction
|
|
10
|
+
*
|
|
11
|
+
* Extracts user promises from code using real AST parsing:
|
|
12
|
+
* - Button interactions (<button>, role="button", onClick)
|
|
13
|
+
* - Forms & submission (<form>, onSubmit, preventDefault)
|
|
14
|
+
* - Validation promises (required, disabled, validation branches)
|
|
15
|
+
* - UI feedback (toasts, alerts, aria-live, status banners)
|
|
16
|
+
*
|
|
17
|
+
* Evidence-first: Only emit promises we can prove exist in code
|
|
18
|
+
* Deterministic: Same code → same promises every run
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extract promises from JSX/TSX file using AST
|
|
23
|
+
*/
|
|
24
|
+
export function extractPromisesFromAST(content, filePath, relPath) {
|
|
25
|
+
const promises = [];
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const ast = parse(content, {
|
|
29
|
+
sourceType: 'module',
|
|
30
|
+
plugins: [
|
|
31
|
+
'jsx',
|
|
32
|
+
'typescript',
|
|
33
|
+
'classProperties',
|
|
34
|
+
'optionalChaining',
|
|
35
|
+
'nullishCoalescingOperator',
|
|
36
|
+
'dynamicImport',
|
|
37
|
+
['decorators', { decoratorsBeforeExport: true }],
|
|
38
|
+
'topLevelAwait',
|
|
39
|
+
'objectRestSpread',
|
|
40
|
+
'asyncGenerators',
|
|
41
|
+
],
|
|
42
|
+
errorRecovery: true,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const context = {
|
|
46
|
+
filePath,
|
|
47
|
+
relPath,
|
|
48
|
+
lines: content.split('\n'),
|
|
49
|
+
imports: trackImports(ast),
|
|
50
|
+
formElements: new Map(), // Track form refs for submit button association
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
traverse(ast, {
|
|
54
|
+
// Button interactions
|
|
55
|
+
JSXElement(path) {
|
|
56
|
+
extractButtonPromises(path, context, promises);
|
|
57
|
+
extractFormPromises(path, context, promises);
|
|
58
|
+
extractValidationInputPromises(path, context, promises);
|
|
59
|
+
extractFeedbackPromises(path, context, promises);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// Validation logic in handlers
|
|
63
|
+
FunctionDeclaration(path) {
|
|
64
|
+
extractValidationPromises(path, context, promises);
|
|
65
|
+
},
|
|
66
|
+
ArrowFunctionExpression(path) {
|
|
67
|
+
extractValidationPromises(path, context, promises);
|
|
68
|
+
},
|
|
69
|
+
FunctionExpression(path) {
|
|
70
|
+
extractValidationPromises(path, context, promises);
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
} catch (error) {
|
|
74
|
+
// Parse error - skip this file silently
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return promises;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Track imports for context (router, toast libraries, etc.)
|
|
82
|
+
*/
|
|
83
|
+
function trackImports(ast) {
|
|
84
|
+
const imports = {
|
|
85
|
+
router: new Set(),
|
|
86
|
+
navigation: new Set(),
|
|
87
|
+
toast: new Set(),
|
|
88
|
+
modal: new Set(),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
traverse(ast, {
|
|
92
|
+
ImportDeclaration(path) {
|
|
93
|
+
const source = path.node.source.value;
|
|
94
|
+
|
|
95
|
+
// React Router
|
|
96
|
+
if (source.includes('react-router')) {
|
|
97
|
+
path.node.specifiers.forEach((spec) => {
|
|
98
|
+
if (spec.type === 'ImportSpecifier' || spec.type === 'ImportDefaultSpecifier') {
|
|
99
|
+
imports.router.add(spec.local.name);
|
|
100
|
+
if (['useNavigate', 'useHistory'].includes(spec.imported?.name)) {
|
|
101
|
+
imports.navigation.add(spec.local.name);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Next.js
|
|
108
|
+
if (source.includes('next/')) {
|
|
109
|
+
path.node.specifiers.forEach((spec) => {
|
|
110
|
+
if (spec.type === 'ImportSpecifier' || spec.type === 'ImportDefaultSpecifier') {
|
|
111
|
+
if (source.includes('next/link')) imports.router.add(spec.local.name);
|
|
112
|
+
if (source.includes('next/navigation')) imports.navigation.add(spec.local.name);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Toast libraries (generic detection)
|
|
118
|
+
if (source.includes('toast') || source.includes('notification') ||
|
|
119
|
+
source.includes('alert') || source.includes('snackbar')) {
|
|
120
|
+
path.node.specifiers.forEach((spec) => {
|
|
121
|
+
if (spec.local) imports.toast.add(spec.local.name);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Modal libraries
|
|
126
|
+
if (source.includes('modal') || source.includes('dialog')) {
|
|
127
|
+
path.node.specifiers.forEach((spec) => {
|
|
128
|
+
if (spec.local) imports.modal.add(spec.local.name);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return imports;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Extract button interaction promises
|
|
139
|
+
*/
|
|
140
|
+
function extractButtonPromises(path, context, promises) {
|
|
141
|
+
const opening = path.node.openingElement;
|
|
142
|
+
const elementName = opening.name.name || opening.name.property?.name;
|
|
143
|
+
|
|
144
|
+
// Check if this is a button element
|
|
145
|
+
const isButton = elementName === 'button' ||
|
|
146
|
+
hasAttribute(opening, 'role', 'button') ||
|
|
147
|
+
elementName === 'Button'; // Common component name
|
|
148
|
+
|
|
149
|
+
if (!isButton) return;
|
|
150
|
+
|
|
151
|
+
// Look for onClick handler
|
|
152
|
+
const onClickAttr = opening.attributes.find(
|
|
153
|
+
attr => attr.type === 'JSXAttribute' && attr.name.name === 'onClick'
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (!onClickAttr) return;
|
|
157
|
+
|
|
158
|
+
const loc = path.node.loc;
|
|
159
|
+
if (!loc) return;
|
|
160
|
+
|
|
161
|
+
// Check if button has type="submit" - if so, skip (handled by form extraction)
|
|
162
|
+
const typeAttr = opening.attributes.find(
|
|
163
|
+
attr => attr.type === 'JSXAttribute' && attr.name.name === 'type'
|
|
164
|
+
);
|
|
165
|
+
if (typeAttr?.value?.value === 'submit') return;
|
|
166
|
+
|
|
167
|
+
// Extract button text/label for selector
|
|
168
|
+
const buttonText = extractElementText(path.node);
|
|
169
|
+
const selector = buttonText ? `button:contains("${buttonText}")` : 'button[onClick]';
|
|
170
|
+
|
|
171
|
+
// Check if handler contains navigation
|
|
172
|
+
const handlerSource = extractHandlerSource(onClickAttr.value, context);
|
|
173
|
+
const hasNavigationCall = detectNavigationInSource(handlerSource, context);
|
|
174
|
+
|
|
175
|
+
promises.push({
|
|
176
|
+
category: 'button',
|
|
177
|
+
type: 'interaction',
|
|
178
|
+
promise: {
|
|
179
|
+
kind: 'click',
|
|
180
|
+
value: buttonText || 'button click',
|
|
181
|
+
},
|
|
182
|
+
source: {
|
|
183
|
+
file: context.relPath,
|
|
184
|
+
line: loc.start.line,
|
|
185
|
+
column: loc.start.column,
|
|
186
|
+
},
|
|
187
|
+
selector,
|
|
188
|
+
action: 'click',
|
|
189
|
+
expectedOutcome: hasNavigationCall ? 'navigation' : 'ui-change',
|
|
190
|
+
confidenceHint: 'medium',
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Extract form submission promises
|
|
196
|
+
*/
|
|
197
|
+
function extractFormPromises(path, context, promises) {
|
|
198
|
+
const opening = path.node.openingElement;
|
|
199
|
+
const elementName = opening.name.name || opening.name.property?.name;
|
|
200
|
+
|
|
201
|
+
if (elementName !== 'form' && elementName !== 'Form') return;
|
|
202
|
+
|
|
203
|
+
// Look for onSubmit handler
|
|
204
|
+
const onSubmitAttr = opening.attributes.find(
|
|
205
|
+
attr => attr.type === 'JSXAttribute' && attr.name.name === 'onSubmit'
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
if (!onSubmitAttr) return;
|
|
209
|
+
|
|
210
|
+
const loc = path.node.loc;
|
|
211
|
+
if (!loc) return;
|
|
212
|
+
|
|
213
|
+
// Extract handler source
|
|
214
|
+
const handlerSource = extractHandlerSource(onSubmitAttr.value, context);
|
|
215
|
+
|
|
216
|
+
// Check for preventDefault (indicates form handling, not default browser submit)
|
|
217
|
+
const hasPreventDefault = handlerSource.includes('preventDefault');
|
|
218
|
+
|
|
219
|
+
// Check for validation
|
|
220
|
+
const hasValidation = detectValidationInSource(handlerSource);
|
|
221
|
+
|
|
222
|
+
// Check for navigation
|
|
223
|
+
const hasNavigation = detectNavigationInSource(handlerSource, context);
|
|
224
|
+
|
|
225
|
+
// Check for network calls
|
|
226
|
+
const hasNetwork = detectNetworkInSource(handlerSource);
|
|
227
|
+
|
|
228
|
+
// Determine expected outcome
|
|
229
|
+
let expectedOutcome = 'ui-change';
|
|
230
|
+
if (hasNetwork) expectedOutcome = 'network';
|
|
231
|
+
if (hasNavigation) expectedOutcome = 'navigation';
|
|
232
|
+
|
|
233
|
+
promises.push({
|
|
234
|
+
category: 'form',
|
|
235
|
+
type: 'interaction',
|
|
236
|
+
promise: {
|
|
237
|
+
kind: 'submit',
|
|
238
|
+
value: 'form submission',
|
|
239
|
+
},
|
|
240
|
+
source: {
|
|
241
|
+
file: context.relPath,
|
|
242
|
+
line: loc.start.line,
|
|
243
|
+
column: loc.start.column,
|
|
244
|
+
},
|
|
245
|
+
selector: 'form[onSubmit]',
|
|
246
|
+
action: 'submit',
|
|
247
|
+
expectedOutcome,
|
|
248
|
+
confidenceHint: hasPreventDefault ? 'medium' : 'low',
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// If validation detected, add validation promise
|
|
252
|
+
if (hasValidation) {
|
|
253
|
+
promises.push({
|
|
254
|
+
category: 'validation',
|
|
255
|
+
type: 'feedback',
|
|
256
|
+
promise: {
|
|
257
|
+
kind: 'validation',
|
|
258
|
+
value: 'input validation feedback',
|
|
259
|
+
},
|
|
260
|
+
source: {
|
|
261
|
+
file: context.relPath,
|
|
262
|
+
line: loc.start.line,
|
|
263
|
+
column: loc.start.column,
|
|
264
|
+
},
|
|
265
|
+
selector: 'form[onSubmit]',
|
|
266
|
+
action: 'observe',
|
|
267
|
+
expectedOutcome: 'feedback',
|
|
268
|
+
confidenceHint: 'medium',
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Extract validation promises from input elements with required attribute
|
|
275
|
+
*/
|
|
276
|
+
function extractValidationInputPromises(path, context, promises) {
|
|
277
|
+
const opening = path.node.openingElement;
|
|
278
|
+
const elementName = opening.name.name || opening.name.property?.name;
|
|
279
|
+
|
|
280
|
+
// Check if this is an input element
|
|
281
|
+
if (elementName !== 'input' && elementName !== 'Input') return;
|
|
282
|
+
|
|
283
|
+
// Check for required attribute
|
|
284
|
+
const hasRequired = opening.attributes.some(
|
|
285
|
+
attr => attr.type === 'JSXAttribute' && attr.name.name === 'required'
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
if (!hasRequired) return;
|
|
289
|
+
|
|
290
|
+
const loc = path.node.loc;
|
|
291
|
+
if (!loc) return;
|
|
292
|
+
|
|
293
|
+
// Extract name or id for selector
|
|
294
|
+
const nameAttr = opening.attributes.find(
|
|
295
|
+
attr => attr.type === 'JSXAttribute' && attr.name.name === 'name'
|
|
296
|
+
);
|
|
297
|
+
const idAttr = opening.attributes.find(
|
|
298
|
+
attr => attr.type === 'JSXAttribute' && attr.name.name === 'id'
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
let selector = 'input[required]';
|
|
302
|
+
if (nameAttr?.value?.value) {
|
|
303
|
+
selector = `input[name="${nameAttr.value.value}"]`;
|
|
304
|
+
} else if (idAttr?.value?.value) {
|
|
305
|
+
selector = `input#${idAttr.value.value}`;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
promises.push({
|
|
309
|
+
category: 'validation',
|
|
310
|
+
type: 'feedback',
|
|
311
|
+
promise: {
|
|
312
|
+
kind: 'validation',
|
|
313
|
+
value: 'required field validation',
|
|
314
|
+
},
|
|
315
|
+
source: {
|
|
316
|
+
file: context.relPath,
|
|
317
|
+
line: loc.start.line,
|
|
318
|
+
column: loc.start.column,
|
|
319
|
+
},
|
|
320
|
+
selector,
|
|
321
|
+
action: 'observe',
|
|
322
|
+
expectedOutcome: 'feedback',
|
|
323
|
+
confidenceHint: 'medium',
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Extract validation promises from code
|
|
329
|
+
*/
|
|
330
|
+
function extractValidationPromises(path, context, promises) {
|
|
331
|
+
const body = path.node.body;
|
|
332
|
+
if (!body) return;
|
|
333
|
+
|
|
334
|
+
const source = extractFunctionSource(path, context);
|
|
335
|
+
if (!source) return;
|
|
336
|
+
|
|
337
|
+
// Look for validation patterns
|
|
338
|
+
const hasValidationLogic = (
|
|
339
|
+
source.includes('required') ||
|
|
340
|
+
source.includes('validate') ||
|
|
341
|
+
source.includes('.length') ||
|
|
342
|
+
source.includes('isEmpty') ||
|
|
343
|
+
source.includes('isValid') ||
|
|
344
|
+
source.includes('error') && (source.includes('set') || source.includes('Error'))
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
if (!hasValidationLogic) return;
|
|
348
|
+
|
|
349
|
+
// Check if this function is used as a form handler or validator
|
|
350
|
+
const isValidationContext = (
|
|
351
|
+
path.node.id?.name?.toLowerCase().includes('valid') ||
|
|
352
|
+
path.node.id?.name?.toLowerCase().includes('check') ||
|
|
353
|
+
path.node.id?.name?.toLowerCase().includes('submit') ||
|
|
354
|
+
source.includes('onSubmit') ||
|
|
355
|
+
source.includes('onChange')
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
if (!isValidationContext) return;
|
|
359
|
+
|
|
360
|
+
const loc = path.node.loc;
|
|
361
|
+
if (!loc) return;
|
|
362
|
+
|
|
363
|
+
promises.push({
|
|
364
|
+
category: 'validation',
|
|
365
|
+
type: 'feedback',
|
|
366
|
+
promise: {
|
|
367
|
+
kind: 'validation',
|
|
368
|
+
value: 'validation feedback',
|
|
369
|
+
},
|
|
370
|
+
source: {
|
|
371
|
+
file: context.relPath,
|
|
372
|
+
line: loc.start.line,
|
|
373
|
+
column: loc.start.column,
|
|
374
|
+
},
|
|
375
|
+
selector: null, // Cannot determine selector from handler alone
|
|
376
|
+
action: 'observe',
|
|
377
|
+
expectedOutcome: 'feedback',
|
|
378
|
+
confidenceHint: 'low',
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Extract UI feedback promises
|
|
384
|
+
*/
|
|
385
|
+
function extractFeedbackPromises(path, context, promises) {
|
|
386
|
+
const opening = path.node.openingElement;
|
|
387
|
+
|
|
388
|
+
// Check for aria-live regions (explicit feedback promise)
|
|
389
|
+
const ariaLiveAttr = opening.attributes.find(
|
|
390
|
+
attr => attr.type === 'JSXAttribute' && attr.name.name === 'aria-live'
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
if (ariaLiveAttr) {
|
|
394
|
+
const loc = path.node.loc;
|
|
395
|
+
if (!loc) return;
|
|
396
|
+
|
|
397
|
+
promises.push({
|
|
398
|
+
category: 'feedback',
|
|
399
|
+
type: 'feedback',
|
|
400
|
+
promise: {
|
|
401
|
+
kind: 'ui-feedback',
|
|
402
|
+
value: 'live region update',
|
|
403
|
+
},
|
|
404
|
+
source: {
|
|
405
|
+
file: context.relPath,
|
|
406
|
+
line: loc.start.line,
|
|
407
|
+
column: loc.start.column,
|
|
408
|
+
},
|
|
409
|
+
selector: '[aria-live]',
|
|
410
|
+
action: 'observe',
|
|
411
|
+
expectedOutcome: 'feedback',
|
|
412
|
+
confidenceHint: 'medium',
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Check for role="status" or role="alert"
|
|
417
|
+
const roleAttr = opening.attributes.find(
|
|
418
|
+
attr => attr.type === 'JSXAttribute' && attr.name.name === 'role'
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
if (roleAttr?.value?.value === 'status' || roleAttr?.value?.value === 'alert') {
|
|
422
|
+
const loc = path.node.loc;
|
|
423
|
+
if (!loc) return;
|
|
424
|
+
|
|
425
|
+
promises.push({
|
|
426
|
+
category: 'feedback',
|
|
427
|
+
type: 'feedback',
|
|
428
|
+
promise: {
|
|
429
|
+
kind: 'ui-feedback',
|
|
430
|
+
value: `${roleAttr.value.value} message`,
|
|
431
|
+
},
|
|
432
|
+
source: {
|
|
433
|
+
file: context.relPath,
|
|
434
|
+
line: loc.start.line,
|
|
435
|
+
column: loc.start.column,
|
|
436
|
+
},
|
|
437
|
+
selector: `[role="${roleAttr.value.value}"]`,
|
|
438
|
+
action: 'observe',
|
|
439
|
+
expectedOutcome: 'feedback',
|
|
440
|
+
confidenceHint: 'medium',
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Extract handler source code from JSX attribute value
|
|
447
|
+
*/
|
|
448
|
+
function extractHandlerSource(value, context) {
|
|
449
|
+
if (!value) return '';
|
|
450
|
+
|
|
451
|
+
if (value.type === 'JSXExpressionContainer') {
|
|
452
|
+
const expr = value.expression;
|
|
453
|
+
|
|
454
|
+
// Inline arrow function
|
|
455
|
+
if (expr.type === 'ArrowFunctionExpression' || expr.type === 'FunctionExpression') {
|
|
456
|
+
const loc = expr.loc;
|
|
457
|
+
if (!loc) return '';
|
|
458
|
+
return context.lines.slice(loc.start.line - 1, loc.end.line).join('\n');
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Reference to function
|
|
462
|
+
if (expr.type === 'Identifier') {
|
|
463
|
+
return expr.name; // Return function name for pattern matching
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Member expression (e.g., this.handleClick)
|
|
467
|
+
if (expr.type === 'MemberExpression') {
|
|
468
|
+
return `${expr.object.name}.${expr.property.name}`;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return '';
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Extract function source code
|
|
477
|
+
*/
|
|
478
|
+
function extractFunctionSource(path, context) {
|
|
479
|
+
const loc = path.node.loc;
|
|
480
|
+
if (!loc) return '';
|
|
481
|
+
|
|
482
|
+
return context.lines.slice(loc.start.line - 1, loc.end.line).join('\n');
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Extract text content from JSX element
|
|
487
|
+
*/
|
|
488
|
+
function extractElementText(node) {
|
|
489
|
+
if (!node.children || node.children.length === 0) return '';
|
|
490
|
+
|
|
491
|
+
for (const child of node.children) {
|
|
492
|
+
if (child.type === 'JSXText') {
|
|
493
|
+
return child.value.trim();
|
|
494
|
+
}
|
|
495
|
+
if (child.type === 'JSXExpressionContainer' && child.expression.type === 'StringLiteral') {
|
|
496
|
+
return child.expression.value;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return '';
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Check if element has specific attribute with value
|
|
505
|
+
*/
|
|
506
|
+
function hasAttribute(opening, attrName, attrValue = null) {
|
|
507
|
+
const attr = opening.attributes.find(
|
|
508
|
+
a => a.type === 'JSXAttribute' && a.name.name === attrName
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
if (!attr) return false;
|
|
512
|
+
if (attrValue === null) return true;
|
|
513
|
+
|
|
514
|
+
return attr.value?.value === attrValue;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Detect navigation calls in source code
|
|
519
|
+
*/
|
|
520
|
+
function detectNavigationInSource(source, context) {
|
|
521
|
+
if (!source) return false;
|
|
522
|
+
|
|
523
|
+
return (
|
|
524
|
+
source.includes('.push(') ||
|
|
525
|
+
source.includes('.replace(') ||
|
|
526
|
+
source.includes('navigate(') ||
|
|
527
|
+
source.includes('router.') ||
|
|
528
|
+
source.includes('history.') ||
|
|
529
|
+
Array.from(context.imports.navigation).some(name => source.includes(name))
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Detect validation logic in source code
|
|
535
|
+
*/
|
|
536
|
+
function detectValidationInSource(source) {
|
|
537
|
+
if (!source) return false;
|
|
538
|
+
|
|
539
|
+
return (
|
|
540
|
+
source.includes('required') ||
|
|
541
|
+
source.includes('validate') ||
|
|
542
|
+
source.includes('isValid') ||
|
|
543
|
+
source.includes('isEmpty') ||
|
|
544
|
+
(source.includes('error') && (source.includes('set') || source.includes('Error'))) ||
|
|
545
|
+
source.includes('pattern') ||
|
|
546
|
+
source.includes('minLength') ||
|
|
547
|
+
source.includes('maxLength')
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Detect network calls in source code
|
|
553
|
+
*/
|
|
554
|
+
function detectNetworkInSource(source) {
|
|
555
|
+
if (!source) return false;
|
|
556
|
+
|
|
557
|
+
return (
|
|
558
|
+
source.includes('fetch(') ||
|
|
559
|
+
source.includes('axios.') ||
|
|
560
|
+
source.includes('.post(') ||
|
|
561
|
+
source.includes('.get(') ||
|
|
562
|
+
source.includes('.put(') ||
|
|
563
|
+
source.includes('.delete(') ||
|
|
564
|
+
source.includes('api.')
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Detect toast/notification calls in source code
|
|
570
|
+
*/
|
|
571
|
+
function _detectToastInSource(source, context) {
|
|
572
|
+
if (!source) return false;
|
|
573
|
+
|
|
574
|
+
return (
|
|
575
|
+
source.includes('toast.') ||
|
|
576
|
+
source.includes('notify(') ||
|
|
577
|
+
source.includes('alert(') ||
|
|
578
|
+
source.includes('showMessage') ||
|
|
579
|
+
Array.from(context.imports.toast).some(name => source.includes(name))
|
|
580
|
+
);
|
|
581
|
+
}
|