@vibecheckai/cli 3.5.0 → 3.5.2
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/bin/registry.js +214 -237
- package/bin/runners/cli-utils.js +33 -2
- package/bin/runners/context/analyzer.js +52 -1
- package/bin/runners/context/generators/cursor.js +2 -49
- package/bin/runners/context/git-context.js +3 -1
- package/bin/runners/context/team-conventions.js +33 -7
- package/bin/runners/lib/analysis-core.js +25 -5
- package/bin/runners/lib/analyzers.js +431 -481
- package/bin/runners/lib/default-config.js +127 -0
- package/bin/runners/lib/doctor/modules/security.js +3 -1
- package/bin/runners/lib/engine/ast-cache.js +210 -0
- package/bin/runners/lib/engine/auth-extractor.js +211 -0
- package/bin/runners/lib/engine/billing-extractor.js +112 -0
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
- package/bin/runners/lib/engine/env-extractor.js +207 -0
- package/bin/runners/lib/engine/express-extractor.js +208 -0
- package/bin/runners/lib/engine/extractors.js +849 -0
- package/bin/runners/lib/engine/index.js +207 -0
- package/bin/runners/lib/engine/repo-index.js +514 -0
- package/bin/runners/lib/engine/types.js +124 -0
- package/bin/runners/lib/engines/accessibility-engine.js +18 -218
- package/bin/runners/lib/engines/api-consistency-engine.js +30 -335
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +27 -292
- package/bin/runners/lib/engines/empty-catch-engine.js +17 -127
- package/bin/runners/lib/engines/mock-data-engine.js +10 -53
- package/bin/runners/lib/engines/performance-issues-engine.js +36 -176
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +54 -382
- package/bin/runners/lib/engines/type-aware-engine.js +39 -263
- package/bin/runners/lib/engines/vibecheck-engines/index.js +13 -122
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +73 -373
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/entitlements-v2.js +73 -97
- package/bin/runners/lib/error-handler.js +44 -3
- package/bin/runners/lib/error-messages.js +289 -0
- package/bin/runners/lib/evidence-pack.js +7 -1
- package/bin/runners/lib/finding-id.js +69 -0
- package/bin/runners/lib/finding-sorter.js +89 -0
- package/bin/runners/lib/html-proof-report.js +700 -350
- package/bin/runners/lib/missions/plan.js +6 -46
- package/bin/runners/lib/missions/templates.js +0 -232
- package/bin/runners/lib/next-action.js +560 -0
- package/bin/runners/lib/prerequisites.js +149 -0
- package/bin/runners/lib/route-detection.js +137 -68
- package/bin/runners/lib/scan-output.js +91 -76
- package/bin/runners/lib/scan-runner.js +135 -0
- package/bin/runners/lib/schemas/ajv-validator.js +464 -0
- package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
- package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
- package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
- package/bin/runners/lib/schemas/run-request.schema.json +108 -0
- package/bin/runners/lib/schemas/validator.js +27 -0
- package/bin/runners/lib/schemas/verdict.schema.json +140 -0
- package/bin/runners/lib/ship-output-enterprise.js +23 -23
- package/bin/runners/lib/ship-output.js +75 -31
- package/bin/runners/lib/terminal-ui.js +6 -113
- package/bin/runners/lib/truth.js +351 -10
- package/bin/runners/lib/unified-cli-output.js +430 -603
- package/bin/runners/lib/unified-output.js +13 -9
- package/bin/runners/runAIAgent.js +10 -5
- package/bin/runners/runAgent.js +0 -3
- package/bin/runners/runAllowlist.js +389 -0
- package/bin/runners/runApprove.js +0 -33
- package/bin/runners/runAuth.js +73 -45
- package/bin/runners/runCheckpoint.js +51 -11
- package/bin/runners/runClassify.js +85 -21
- package/bin/runners/runContext.js +0 -3
- package/bin/runners/runDoctor.js +41 -28
- package/bin/runners/runEvidencePack.js +362 -0
- package/bin/runners/runFirewall.js +0 -3
- package/bin/runners/runFirewallHook.js +0 -3
- package/bin/runners/runFix.js +66 -76
- package/bin/runners/runGuard.js +18 -411
- package/bin/runners/runInit.js +113 -30
- package/bin/runners/runLabs.js +424 -0
- package/bin/runners/runMcp.js +19 -25
- package/bin/runners/runPolish.js +64 -240
- package/bin/runners/runPromptFirewall.js +12 -5
- package/bin/runners/runProve.js +57 -22
- package/bin/runners/runQuickstart.js +531 -0
- package/bin/runners/runReality.js +59 -68
- package/bin/runners/runReport.js +38 -33
- package/bin/runners/runRuntime.js +8 -5
- package/bin/runners/runScan.js +1413 -190
- package/bin/runners/runShip.js +113 -719
- package/bin/runners/runTruth.js +0 -3
- package/bin/runners/runValidate.js +13 -9
- package/bin/runners/runWatch.js +23 -14
- package/bin/scan.js +6 -1
- package/bin/vibecheck.js +204 -185
- package/mcp-server/deprecation-middleware.js +282 -0
- package/mcp-server/handlers/index.ts +15 -0
- package/mcp-server/handlers/tool-handler.ts +554 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index.js +210 -238
- package/mcp-server/lib/cache-wrapper.cjs +383 -0
- package/mcp-server/lib/error-envelope.js +138 -0
- package/mcp-server/lib/executor.ts +499 -0
- package/mcp-server/lib/index.ts +19 -0
- package/mcp-server/lib/rate-limiter.js +166 -0
- package/mcp-server/lib/sandbox.test.ts +519 -0
- package/mcp-server/lib/sandbox.ts +395 -0
- package/mcp-server/lib/types.ts +267 -0
- package/mcp-server/package.json +12 -3
- package/mcp-server/registry/tool-registry.js +794 -0
- package/mcp-server/registry/tools.json +605 -0
- package/mcp-server/registry.test.ts +334 -0
- package/mcp-server/tests/tier-gating.test.js +297 -0
- package/mcp-server/tier-auth.js +378 -45
- package/mcp-server/tools-v3.js +353 -442
- package/mcp-server/tsconfig.json +37 -0
- package/mcp-server/vibecheck-2.0-tools.js +14 -1
- package/package.json +1 -1
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
- package/bin/runners/lib/audit-logger.js +0 -532
- package/bin/runners/lib/authority/authorities/architecture.js +0 -364
- package/bin/runners/lib/authority/authorities/compliance.js +0 -341
- package/bin/runners/lib/authority/authorities/human.js +0 -343
- package/bin/runners/lib/authority/authorities/quality.js +0 -420
- package/bin/runners/lib/authority/authorities/security.js +0 -228
- package/bin/runners/lib/authority/index.js +0 -293
- package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
- package/bin/runners/lib/cli-charts.js +0 -368
- package/bin/runners/lib/cli-config-display.js +0 -405
- package/bin/runners/lib/cli-demo.js +0 -275
- package/bin/runners/lib/cli-errors.js +0 -438
- package/bin/runners/lib/cli-help-formatter.js +0 -439
- package/bin/runners/lib/cli-interactive-menu.js +0 -509
- package/bin/runners/lib/cli-prompts.js +0 -441
- package/bin/runners/lib/cli-scan-cards.js +0 -362
- package/bin/runners/lib/compliance-reporter.js +0 -710
- package/bin/runners/lib/conductor/index.js +0 -671
- package/bin/runners/lib/easy/README.md +0 -123
- package/bin/runners/lib/easy/index.js +0 -140
- package/bin/runners/lib/easy/interactive-wizard.js +0 -788
- package/bin/runners/lib/easy/one-click-firewall.js +0 -564
- package/bin/runners/lib/easy/zero-config-reality.js +0 -714
- package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
- package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
- package/bin/runners/lib/engines/confidence-scoring.js +0 -276
- package/bin/runners/lib/engines/context-detection.js +0 -264
- package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
- package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
- package/bin/runners/lib/engines/env-variables-engine.js +0 -458
- package/bin/runners/lib/engines/error-handling-engine.js +0 -437
- package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
- package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
- package/bin/runners/lib/engines/framework-detection.js +0 -508
- package/bin/runners/lib/engines/import-order-engine.js +0 -429
- package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
- package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
- package/bin/runners/lib/engines/orchestrator.js +0 -334
- package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
- package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
- package/bin/runners/lib/enhanced-features/index.js +0 -305
- package/bin/runners/lib/enhanced-output.js +0 -631
- package/bin/runners/lib/enterprise.js +0 -300
- package/bin/runners/lib/firewall/command-validator.js +0 -351
- package/bin/runners/lib/firewall/config.js +0 -341
- package/bin/runners/lib/firewall/content-validator.js +0 -519
- package/bin/runners/lib/firewall/index.js +0 -101
- package/bin/runners/lib/firewall/path-validator.js +0 -256
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
- package/bin/runners/lib/mcp-utils.js +0 -425
- package/bin/runners/lib/output/index.js +0 -1022
- package/bin/runners/lib/policy-engine.js +0 -652
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
- package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
- package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
- package/bin/runners/lib/polish/autofix/index.js +0 -200
- package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
- package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
- package/bin/runners/lib/polish/backend-checks.js +0 -148
- package/bin/runners/lib/polish/documentation-checks.js +0 -111
- package/bin/runners/lib/polish/frontend-checks.js +0 -168
- package/bin/runners/lib/polish/index.js +0 -71
- package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
- package/bin/runners/lib/polish/library-detection.js +0 -175
- package/bin/runners/lib/polish/performance-checks.js +0 -100
- package/bin/runners/lib/polish/security-checks.js +0 -148
- package/bin/runners/lib/polish/utils.js +0 -203
- package/bin/runners/lib/prompt-builder.js +0 -540
- package/bin/runners/lib/proof-certificate.js +0 -634
- package/bin/runners/lib/reality/accessibility-audit.js +0 -946
- package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
- package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
- package/bin/runners/lib/reality/performance-tracker.js +0 -1077
- package/bin/runners/lib/reality/scenario-generator.js +0 -1404
- package/bin/runners/lib/reality/visual-regression.js +0 -852
- package/bin/runners/lib/reality-profiler.js +0 -717
- package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
- package/bin/runners/lib/review/ai-code-review.js +0 -832
- package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
- package/bin/runners/lib/sbom-generator.js +0 -641
- package/bin/runners/lib/scan-output-enhanced.js +0 -512
- package/bin/runners/lib/security/owasp-scanner.js +0 -939
- package/bin/runners/lib/validators/contract-validator.js +0 -283
- package/bin/runners/lib/validators/dead-export-detector.js +0 -279
- package/bin/runners/lib/validators/dep-audit.js +0 -245
- package/bin/runners/lib/validators/env-validator.js +0 -319
- package/bin/runners/lib/validators/index.js +0 -120
- package/bin/runners/lib/validators/license-checker.js +0 -252
- package/bin/runners/lib/validators/route-validator.js +0 -290
- package/bin/runners/runAuthority.js +0 -528
- package/bin/runners/runConductor.js +0 -772
- package/bin/runners/runContainer.js +0 -366
- package/bin/runners/runEasy.js +0 -410
- package/bin/runners/runIaC.js +0 -372
- package/bin/runners/runVibe.js +0 -791
- package/mcp-server/tools.js +0 -495
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// bin/runners/lib/engine/types.js
|
|
2
|
+
// JSDoc type definitions for the engine
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} FileRecord
|
|
6
|
+
* @property {string} abs - Absolute path
|
|
7
|
+
* @property {string} rel - Relative path from repo root (forward slashes)
|
|
8
|
+
* @property {number} size - File size in bytes
|
|
9
|
+
* @property {number} mtime - Last modified time (ms since epoch)
|
|
10
|
+
* @property {string} hash - Content hash (sha256)
|
|
11
|
+
* @property {string} [ext] - File extension (e.g., ".ts", ".js")
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} RepoSignals
|
|
16
|
+
* @property {boolean} hasPackageJson
|
|
17
|
+
* @property {boolean} hasRequirementsTxt
|
|
18
|
+
* @property {boolean} hasPyprojectToml
|
|
19
|
+
* @property {boolean} hasGoMod
|
|
20
|
+
* @property {boolean} hasGemfile
|
|
21
|
+
* @property {boolean} hasNextApp - has app/ directory
|
|
22
|
+
* @property {boolean} hasNextPages - has pages/ directory
|
|
23
|
+
* @property {boolean} hasOpenAPI
|
|
24
|
+
* @property {boolean} hasGraphQL
|
|
25
|
+
* @property {Set<string>} detectedFrameworks
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {Object} RepoIndexResult
|
|
30
|
+
* @property {FileRecord[]} files - All indexed files
|
|
31
|
+
* @property {RepoSignals} signals - Detected signals
|
|
32
|
+
* @property {Map<string, string>} contentCache - abs -> content
|
|
33
|
+
* @property {Map<string, Set<string>>} tokenIndex - token -> Set<abs>
|
|
34
|
+
* @property {Object} stats
|
|
35
|
+
* @property {number} stats.totalFiles
|
|
36
|
+
* @property {number} stats.totalSize
|
|
37
|
+
* @property {number} stats.indexTimeMs
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {Object} Evidence
|
|
42
|
+
* @property {string} id - Unique evidence ID (ev_xxxx)
|
|
43
|
+
* @property {string} file - Relative file path
|
|
44
|
+
* @property {string} lines - Line range (e.g., "10-15")
|
|
45
|
+
* @property {string} snippetHash - SHA256 of snippet
|
|
46
|
+
* @property {string} reason - Why this is evidence
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @typedef {Object} RouteFact
|
|
51
|
+
* @property {string} method - HTTP method (GET, POST, *, etc.)
|
|
52
|
+
* @property {string} path - Canonicalized route path
|
|
53
|
+
* @property {string} handler - Relative file path
|
|
54
|
+
* @property {string} framework - Source framework
|
|
55
|
+
* @property {"high"|"med"|"low"} confidence
|
|
56
|
+
* @property {Evidence[]} evidence
|
|
57
|
+
* @property {string[]} [hooks] - Fastify hooks if present
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @typedef {Object} ClientRefFact
|
|
62
|
+
* @property {string} method - HTTP method or "*"
|
|
63
|
+
* @property {string} path - Canonicalized path
|
|
64
|
+
* @property {string} source - Relative file path
|
|
65
|
+
* @property {"high"|"med"|"low"} confidence
|
|
66
|
+
* @property {string} kind - fetch, axios_member, axios_config, useSWR, useQuery
|
|
67
|
+
* @property {Evidence[]} evidence
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @typedef {Object} EnvFact
|
|
72
|
+
* @property {string} name - Environment variable name
|
|
73
|
+
* @property {string[]} files - Files where it's referenced
|
|
74
|
+
* @property {boolean} hasDefault - Whether a default value exists
|
|
75
|
+
* @property {string} [defaultValue] - Default value if any
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @typedef {Object} Gap
|
|
80
|
+
* @property {string} kind - Gap type (e.g., "fastify_plugin_unresolved")
|
|
81
|
+
* @property {string} [file] - Related file
|
|
82
|
+
* @property {string} [spec] - Module specifier
|
|
83
|
+
* @property {string} [name] - Identifier name
|
|
84
|
+
* @property {string} [note] - Additional notes
|
|
85
|
+
* @property {string} [error] - Error message if from catch
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @typedef {Object} AdapterResult
|
|
90
|
+
* @property {RouteFact[]} routes
|
|
91
|
+
* @property {ClientRefFact[]} clientRefs
|
|
92
|
+
* @property {Gap[]} gaps
|
|
93
|
+
* @property {Object} stats
|
|
94
|
+
* @property {number} stats.filesScanned
|
|
95
|
+
* @property {number} stats.parseErrors
|
|
96
|
+
* @property {number} [quality] - 0-1 extraction quality score
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @typedef {Object} Finding
|
|
101
|
+
* @property {string} id - Stable finding ID
|
|
102
|
+
* @property {string} title
|
|
103
|
+
* @property {string} description
|
|
104
|
+
* @property {"info"|"warn"|"block"} severity
|
|
105
|
+
* @property {"high"|"med"|"low"} confidence
|
|
106
|
+
* @property {Evidence[]} evidence
|
|
107
|
+
* @property {Gap[]} [gaps]
|
|
108
|
+
* @property {string} [suggestedFix]
|
|
109
|
+
* @property {string} [category]
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @typedef {Object} ScanResult
|
|
114
|
+
* @property {Finding[]} findings
|
|
115
|
+
* @property {Object} truthpack
|
|
116
|
+
* @property {Object} stats
|
|
117
|
+
* @property {number} stats.scanTimeMs
|
|
118
|
+
* @property {number} stats.filesIndexed
|
|
119
|
+
* @property {number} stats.routesFound
|
|
120
|
+
* @property {number} stats.clientRefsFound
|
|
121
|
+
* @property {number} stats.gapsFound
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
module.exports = {};
|
|
@@ -1,115 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Accessibility Analysis Engine
|
|
3
3
|
* Detects accessibility issues in React/JSX code
|
|
4
|
-
* Enhanced with smarter detection:
|
|
5
|
-
* - Distinguishes decorative vs informative images (empty alt="" is valid)
|
|
6
|
-
* - Checks for visible text content in buttons/links
|
|
7
|
-
* - Detects Next.js Image component and accessible patterns
|
|
8
|
-
* - Supports aria-hidden for decorative elements
|
|
9
4
|
*/
|
|
10
5
|
|
|
11
6
|
const { getAST } = require("./ast-cache");
|
|
12
7
|
const traverse = require("@babel/traverse").default;
|
|
13
8
|
const t = require("@babel/types");
|
|
14
9
|
|
|
15
|
-
/**
|
|
16
|
-
* Get attribute value from JSX element
|
|
17
|
-
*/
|
|
18
|
-
function getAttributeValue(attributes, name) {
|
|
19
|
-
const attr = attributes.find(a =>
|
|
20
|
-
t.isJSXAttribute(a) && a.name && a.name.name === name
|
|
21
|
-
);
|
|
22
|
-
if (!attr) return undefined;
|
|
23
|
-
|
|
24
|
-
if (t.isStringLiteral(attr.value)) {
|
|
25
|
-
return attr.value.value;
|
|
26
|
-
}
|
|
27
|
-
if (t.isJSXExpressionContainer(attr.value)) {
|
|
28
|
-
const expr = attr.value.expression;
|
|
29
|
-
if (t.isStringLiteral(expr)) return expr.value;
|
|
30
|
-
if (t.isTemplateLiteral(expr) && expr.quasis.length === 1) {
|
|
31
|
-
return expr.quasis[0].value.raw;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return null; // Has attribute but value is dynamic
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Check if element has visible text content
|
|
39
|
-
*/
|
|
40
|
-
function hasVisibleTextContent(element) {
|
|
41
|
-
if (!element.children) return false;
|
|
42
|
-
|
|
43
|
-
for (const child of element.children) {
|
|
44
|
-
// Text node
|
|
45
|
-
if (t.isJSXText(child) && child.value.trim()) {
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
// Expression that might be text
|
|
49
|
-
if (t.isJSXExpressionContainer(child)) {
|
|
50
|
-
const expr = child.expression;
|
|
51
|
-
if (t.isStringLiteral(expr) && expr.value.trim()) return true;
|
|
52
|
-
if (t.isTemplateLiteral(expr)) return true;
|
|
53
|
-
if (t.isIdentifier(expr)) return true; // Variable might be text
|
|
54
|
-
}
|
|
55
|
-
// Nested element - recursively check
|
|
56
|
-
if (t.isJSXElement(child)) {
|
|
57
|
-
if (hasVisibleTextContent(child)) return true;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Check if image is decorative (intentionally hidden from screen readers)
|
|
65
|
-
*/
|
|
66
|
-
function isDecorativeImage(attributes) {
|
|
67
|
-
// Check for aria-hidden="true"
|
|
68
|
-
const ariaHidden = getAttributeValue(attributes, "aria-hidden");
|
|
69
|
-
if (ariaHidden === "true") return true;
|
|
70
|
-
|
|
71
|
-
// Check for role="presentation" or role="none"
|
|
72
|
-
const role = getAttributeValue(attributes, "role");
|
|
73
|
-
if (role === "presentation" || role === "none") return true;
|
|
74
|
-
|
|
75
|
-
// Empty alt="" is valid for decorative images
|
|
76
|
-
const alt = getAttributeValue(attributes, "alt");
|
|
77
|
-
if (alt === "") return true;
|
|
78
|
-
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Check if element is likely a decorative icon
|
|
84
|
-
*/
|
|
85
|
-
function isLikelyIcon(attributes, tagName) {
|
|
86
|
-
// Common icon patterns
|
|
87
|
-
const className = getAttributeValue(attributes, "className") || "";
|
|
88
|
-
const iconPatterns = [
|
|
89
|
-
/icon/i,
|
|
90
|
-
/fa-/i, // FontAwesome
|
|
91
|
-
/bi-/i, // Bootstrap Icons
|
|
92
|
-
/lucide/i, // Lucide icons
|
|
93
|
-
/heroicon/i, // Heroicons
|
|
94
|
-
/material/i, // Material icons
|
|
95
|
-
/mdi-/i, // Material Design Icons
|
|
96
|
-
];
|
|
97
|
-
|
|
98
|
-
if (iconPatterns.some(p => p.test(className))) return true;
|
|
99
|
-
|
|
100
|
-
// SVG icons
|
|
101
|
-
if (tagName === "svg") {
|
|
102
|
-
const width = getAttributeValue(attributes, "width");
|
|
103
|
-
const height = getAttributeValue(attributes, "height");
|
|
104
|
-
// Small dimensions often indicate icons
|
|
105
|
-
if ((width && parseInt(width) <= 32) || (height && parseInt(height) <= 32)) {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
10
|
/**
|
|
114
11
|
* Analyze accessibility issues
|
|
115
12
|
*/
|
|
@@ -127,40 +24,12 @@ function analyzeAccessibility(code, filePath) {
|
|
|
127
24
|
JSXElement(path) {
|
|
128
25
|
const node = path.node;
|
|
129
26
|
const openingElement = node.openingElement;
|
|
27
|
+
const tagName = openingElement.name.name;
|
|
130
28
|
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
} else if (t.isJSXMemberExpression(openingElement.name)) {
|
|
136
|
-
tagName = openingElement.name.property.name;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (!tagName) return;
|
|
140
|
-
|
|
141
|
-
const attributes = openingElement.attributes || [];
|
|
142
|
-
|
|
143
|
-
// Missing alt text on images (including Next.js Image component)
|
|
144
|
-
if (tagName === "img" || tagName === "Image") {
|
|
145
|
-
// Skip decorative images
|
|
146
|
-
if (isDecorativeImage(attributes)) {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Skip if likely an icon with aria-hidden parent
|
|
151
|
-
if (isLikelyIcon(attributes, tagName)) {
|
|
152
|
-
// Check if parent has aria-hidden
|
|
153
|
-
const parent = path.findParent(p => t.isJSXElement(p.node));
|
|
154
|
-
if (parent) {
|
|
155
|
-
const parentAttrs = parent.node.openingElement.attributes || [];
|
|
156
|
-
if (getAttributeValue(parentAttrs, "aria-hidden") === "true") {
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const hasAlt = attributes.some(attr =>
|
|
163
|
-
t.isJSXAttribute(attr) && attr.name && attr.name.name === "alt"
|
|
29
|
+
// Missing alt text on images
|
|
30
|
+
if (tagName === "img") {
|
|
31
|
+
const hasAlt = openingElement.attributes.some(attr =>
|
|
32
|
+
t.isJSXAttribute(attr) && attr.name.name === "alt"
|
|
164
33
|
);
|
|
165
34
|
|
|
166
35
|
if (!hasAlt) {
|
|
@@ -173,70 +42,32 @@ function analyzeAccessibility(code, filePath) {
|
|
|
173
42
|
line,
|
|
174
43
|
column: openingElement.loc.start.column,
|
|
175
44
|
title: "Image missing alt text",
|
|
176
|
-
message: "Images must have alt text
|
|
45
|
+
message: "Images must have alt text for screen readers",
|
|
177
46
|
codeSnippet: lines[line - 1]?.trim(),
|
|
178
47
|
confidence: "high",
|
|
179
|
-
fixHint: "Add alt=\"description\" for informative images or alt=\"\" for decorative ones",
|
|
180
48
|
});
|
|
181
49
|
}
|
|
182
50
|
}
|
|
183
51
|
|
|
184
52
|
// Interactive elements without accessible labels
|
|
185
|
-
const interactiveElements = ["button", "a", "input", "select", "textarea"
|
|
53
|
+
const interactiveElements = ["button", "a", "input", "select", "textarea"];
|
|
186
54
|
if (interactiveElements.includes(tagName)) {
|
|
187
|
-
const
|
|
188
|
-
t.isJSXAttribute(attr) &&
|
|
55
|
+
const hasLabel = openingElement.attributes.some(attr =>
|
|
56
|
+
t.isJSXAttribute(attr) &&
|
|
189
57
|
(attr.name.name === "aria-label" ||
|
|
190
58
|
attr.name.name === "aria-labelledby" ||
|
|
191
59
|
attr.name.name === "title")
|
|
192
60
|
);
|
|
193
61
|
|
|
194
|
-
// Check for visible text content (most common way to label buttons/links)
|
|
195
|
-
const hasTextContent = hasVisibleTextContent(node);
|
|
196
|
-
|
|
197
62
|
// Check for associated label element
|
|
198
63
|
const hasAssociatedLabel = path.findParent(p => {
|
|
199
64
|
if (t.isJSXElement(p.node)) {
|
|
200
|
-
|
|
201
|
-
? p.node.openingElement.name.name
|
|
202
|
-
: null;
|
|
203
|
-
return parentName === "label";
|
|
204
|
-
}
|
|
205
|
-
return false;
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
// Check for sr-only children (screen reader only text)
|
|
209
|
-
const hasSrOnlyText = node.children && node.children.some(child => {
|
|
210
|
-
if (t.isJSXElement(child)) {
|
|
211
|
-
const childAttrs = child.openingElement.attributes || [];
|
|
212
|
-
const className = getAttributeValue(childAttrs, "className") || "";
|
|
213
|
-
return /sr-only|visually-hidden|screen-reader/i.test(className);
|
|
65
|
+
return p.node.openingElement.name.name === "label";
|
|
214
66
|
}
|
|
215
67
|
return false;
|
|
216
68
|
});
|
|
217
69
|
|
|
218
|
-
|
|
219
|
-
if (tagName === "a" || tagName === "Link" || tagName === "button" || tagName === "Button") {
|
|
220
|
-
if (hasTextContent || hasAriaLabel || hasSrOnlyText) {
|
|
221
|
-
// Has accessible name, skip
|
|
222
|
-
} else {
|
|
223
|
-
const line = openingElement.loc.start.line;
|
|
224
|
-
findings.push({
|
|
225
|
-
type: "missing_accessible_label",
|
|
226
|
-
severity: "WARN",
|
|
227
|
-
category: "Accessibility",
|
|
228
|
-
file: filePath,
|
|
229
|
-
line,
|
|
230
|
-
column: openingElement.loc.start.column,
|
|
231
|
-
title: `${tagName} element missing accessible name`,
|
|
232
|
-
message: `Add visible text, aria-label, or sr-only text for screen readers`,
|
|
233
|
-
codeSnippet: lines[line - 1]?.trim(),
|
|
234
|
-
confidence: "med",
|
|
235
|
-
fixHint: "Add visible text content or aria-label for accessibility",
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
} else if (!hasAriaLabel && !hasAssociatedLabel) {
|
|
239
|
-
// Form inputs need labels
|
|
70
|
+
if (!hasLabel && !hasAssociatedLabel && tagName !== "a") {
|
|
240
71
|
const line = openingElement.loc.start.line;
|
|
241
72
|
findings.push({
|
|
242
73
|
type: "missing_accessible_label",
|
|
@@ -249,7 +80,6 @@ function analyzeAccessibility(code, filePath) {
|
|
|
249
80
|
message: `Add aria-label, aria-labelledby, or wrap in <label>`,
|
|
250
81
|
codeSnippet: lines[line - 1]?.trim(),
|
|
251
82
|
confidence: "med",
|
|
252
|
-
fixHint: "Add aria-label or associate with a <label> element using htmlFor",
|
|
253
83
|
});
|
|
254
84
|
}
|
|
255
85
|
}
|
|
@@ -292,27 +122,16 @@ function analyzeAccessibility(code, filePath) {
|
|
|
292
122
|
|
|
293
123
|
// Missing keyboard handlers on interactive elements
|
|
294
124
|
if (tagName === "div" || tagName === "span") {
|
|
295
|
-
const hasOnClick = attributes.some(attr =>
|
|
296
|
-
t.isJSXAttribute(attr) &&
|
|
297
|
-
attr.name.name === "onClick"
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
const hasKeyHandler = attributes.some(attr =>
|
|
301
|
-
t.isJSXAttribute(attr) && attr.name &&
|
|
302
|
-
(attr.name.name === "onKeyDown" ||
|
|
303
|
-
attr.name.name === "onKeyUp" ||
|
|
304
|
-
attr.name.name === "onKeyPress")
|
|
125
|
+
const hasOnClick = openingElement.attributes.some(attr =>
|
|
126
|
+
t.isJSXAttribute(attr) &&
|
|
127
|
+
(attr.name.name === "onClick" || attr.name.name === "onKeyDown")
|
|
305
128
|
);
|
|
306
129
|
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const hasTabIndex = attributes.some(attr =>
|
|
311
|
-
t.isJSXAttribute(attr) && attr.name && attr.name.name === "tabIndex"
|
|
130
|
+
const hasRole = openingElement.attributes.some(attr =>
|
|
131
|
+
t.isJSXAttribute(attr) && attr.name.name === "role"
|
|
312
132
|
);
|
|
313
133
|
|
|
314
|
-
|
|
315
|
-
if (hasOnClick && !hasKeyHandler && !hasInteractiveRole) {
|
|
134
|
+
if (hasOnClick && !hasRole) {
|
|
316
135
|
const line = openingElement.loc.start.line;
|
|
317
136
|
findings.push({
|
|
318
137
|
type: "missing_keyboard_handler",
|
|
@@ -322,28 +141,9 @@ function analyzeAccessibility(code, filePath) {
|
|
|
322
141
|
line,
|
|
323
142
|
column: openingElement.loc.start.column,
|
|
324
143
|
title: "Interactive element missing keyboard support",
|
|
325
|
-
message: "Elements with onClick should have
|
|
326
|
-
codeSnippet: lines[line - 1]?.trim(),
|
|
327
|
-
confidence: "med",
|
|
328
|
-
fixHint: "Add role=\"button\" tabIndex={0} onKeyDown={(e) => e.key === 'Enter' && handleClick()}",
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Also warn if has role but no tabIndex (not focusable)
|
|
333
|
-
if (hasInteractiveRole && !hasTabIndex) {
|
|
334
|
-
const line = openingElement.loc.start.line;
|
|
335
|
-
findings.push({
|
|
336
|
-
type: "missing_tabindex",
|
|
337
|
-
severity: "WARN",
|
|
338
|
-
category: "Accessibility",
|
|
339
|
-
file: filePath,
|
|
340
|
-
line,
|
|
341
|
-
column: openingElement.loc.start.column,
|
|
342
|
-
title: "Interactive element not focusable",
|
|
343
|
-
message: `Element with role="${role}" should have tabIndex={0} to be keyboard focusable`,
|
|
144
|
+
message: "Elements with onClick should have onKeyDown and proper role",
|
|
344
145
|
codeSnippet: lines[line - 1]?.trim(),
|
|
345
146
|
confidence: "med",
|
|
346
|
-
fixHint: "Add tabIndex={0} to make the element keyboard focusable",
|
|
347
147
|
});
|
|
348
148
|
}
|
|
349
149
|
}
|