@vibecheckai/cli 3.3.0 → 3.5.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/bin/registry.js +389 -269
- package/bin/runners/cli-utils.js +2 -33
- package/bin/runners/context/generators/cursor.js +49 -2
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +849 -0
- package/bin/runners/lib/analyzers.js +599 -142
- package/bin/runners/lib/audit-logger.js +532 -0
- package/bin/runners/lib/authority/authorities/architecture.js +364 -0
- package/bin/runners/lib/authority/authorities/compliance.js +341 -0
- package/bin/runners/lib/authority/authorities/human.js +343 -0
- package/bin/runners/lib/authority/authorities/quality.js +420 -0
- package/bin/runners/lib/authority/authorities/security.js +228 -0
- package/bin/runners/lib/authority/index.js +293 -0
- package/bin/runners/lib/authority-badge.js +425 -425
- package/bin/runners/lib/bundle/bundle-intelligence.js +846 -0
- package/bin/runners/lib/cli-charts.js +368 -0
- package/bin/runners/lib/cli-config-display.js +405 -0
- package/bin/runners/lib/cli-demo.js +275 -0
- package/bin/runners/lib/cli-errors.js +438 -0
- package/bin/runners/lib/cli-help-formatter.js +439 -0
- package/bin/runners/lib/cli-interactive-menu.js +509 -0
- package/bin/runners/lib/cli-prompts.js +441 -0
- package/bin/runners/lib/cli-scan-cards.js +362 -0
- package/bin/runners/lib/compliance-reporter.js +710 -0
- package/bin/runners/lib/conductor/index.js +671 -0
- package/bin/runners/lib/easy/README.md +123 -0
- package/bin/runners/lib/easy/index.js +140 -0
- package/bin/runners/lib/easy/interactive-wizard.js +788 -0
- package/bin/runners/lib/easy/one-click-firewall.js +564 -0
- package/bin/runners/lib/easy/zero-config-reality.js +714 -0
- package/bin/runners/lib/engines/accessibility-engine.js +218 -18
- package/bin/runners/lib/engines/api-consistency-engine.js +335 -30
- package/bin/runners/lib/engines/async-patterns-engine.js +444 -0
- package/bin/runners/lib/engines/bundle-size-engine.js +433 -0
- package/bin/runners/lib/engines/confidence-scoring.js +276 -0
- package/bin/runners/lib/engines/context-detection.js +264 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +292 -27
- package/bin/runners/lib/engines/database-patterns-engine.js +429 -0
- package/bin/runners/lib/engines/duplicate-code-engine.js +354 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +127 -17
- package/bin/runners/lib/engines/env-variables-engine.js +458 -0
- package/bin/runners/lib/engines/error-handling-engine.js +437 -0
- package/bin/runners/lib/engines/false-positive-prevention.js +630 -0
- package/bin/runners/lib/engines/framework-adapters/index.js +607 -0
- package/bin/runners/lib/engines/framework-detection.js +508 -0
- package/bin/runners/lib/engines/import-order-engine.js +429 -0
- package/bin/runners/lib/engines/mock-data-engine.js +53 -10
- package/bin/runners/lib/engines/naming-conventions-engine.js +544 -0
- package/bin/runners/lib/engines/noise-reduction-engine.js +452 -0
- package/bin/runners/lib/engines/orchestrator.js +334 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +176 -36
- package/bin/runners/lib/engines/react-patterns-engine.js +457 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +382 -54
- package/bin/runners/lib/engines/type-aware-engine.js +263 -39
- package/bin/runners/lib/engines/vibecheck-engines/index.js +122 -13
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +806 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +373 -73
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +577 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +543 -0
- package/bin/runners/lib/engines/vibecheck-engines.js +514 -0
- package/bin/runners/lib/enhanced-features/index.js +305 -0
- package/bin/runners/lib/enhanced-output.js +631 -0
- package/bin/runners/lib/enterprise.js +300 -0
- package/bin/runners/lib/entitlements-v2.js +161 -478
- package/bin/runners/lib/firewall/command-validator.js +351 -0
- package/bin/runners/lib/firewall/config.js +341 -0
- package/bin/runners/lib/firewall/content-validator.js +519 -0
- package/bin/runners/lib/firewall/index.js +101 -0
- package/bin/runners/lib/firewall/path-validator.js +256 -0
- package/bin/runners/lib/html-proof-report.js +350 -700
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +817 -0
- package/bin/runners/lib/mcp-utils.js +425 -0
- package/bin/runners/lib/missions/plan.js +46 -6
- package/bin/runners/lib/missions/templates.js +232 -0
- package/bin/runners/lib/output/index.js +1022 -0
- package/bin/runners/lib/policy-engine.js +652 -0
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +333 -0
- package/bin/runners/lib/polish/autofix/async-handlers.js +273 -0
- package/bin/runners/lib/polish/autofix/dead-code.js +280 -0
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +344 -0
- package/bin/runners/lib/polish/autofix/index.js +200 -0
- package/bin/runners/lib/polish/autofix/remove-consoles.js +209 -0
- package/bin/runners/lib/polish/autofix/strengthen-types.js +245 -0
- package/bin/runners/lib/polish/backend-checks.js +148 -0
- package/bin/runners/lib/polish/documentation-checks.js +111 -0
- package/bin/runners/lib/polish/frontend-checks.js +168 -0
- package/bin/runners/lib/polish/index.js +71 -0
- package/bin/runners/lib/polish/infrastructure-checks.js +131 -0
- package/bin/runners/lib/polish/library-detection.js +175 -0
- package/bin/runners/lib/polish/performance-checks.js +100 -0
- package/bin/runners/lib/polish/security-checks.js +148 -0
- package/bin/runners/lib/polish/utils.js +203 -0
- package/bin/runners/lib/prompt-builder.js +540 -0
- package/bin/runners/lib/proof-certificate.js +634 -0
- package/bin/runners/lib/reality/accessibility-audit.js +946 -0
- package/bin/runners/lib/reality/api-contract-validator.js +1012 -0
- package/bin/runners/lib/reality/chaos-engineering.js +1084 -0
- package/bin/runners/lib/reality/performance-tracker.js +1077 -0
- package/bin/runners/lib/reality/scenario-generator.js +1404 -0
- package/bin/runners/lib/reality/visual-regression.js +852 -0
- package/bin/runners/lib/reality-profiler.js +717 -0
- package/bin/runners/lib/replay/flight-recorder-viewer.js +1160 -0
- package/bin/runners/lib/review/ai-code-review.js +832 -0
- package/bin/runners/lib/rules/custom-rule-engine.js +985 -0
- package/bin/runners/lib/sbom-generator.js +641 -0
- package/bin/runners/lib/scan-output-enhanced.js +512 -0
- package/bin/runners/lib/scan-output.js +65 -19
- package/bin/runners/lib/security/owasp-scanner.js +939 -0
- package/bin/runners/lib/ship-output.js +18 -25
- package/bin/runners/lib/terminal-ui.js +113 -1
- package/bin/runners/lib/unified-cli-output.js +603 -430
- package/bin/runners/lib/upsell.js +90 -338
- package/bin/runners/lib/validators/contract-validator.js +283 -0
- package/bin/runners/lib/validators/dead-export-detector.js +279 -0
- package/bin/runners/lib/validators/dep-audit.js +245 -0
- package/bin/runners/lib/validators/env-validator.js +319 -0
- package/bin/runners/lib/validators/index.js +120 -0
- package/bin/runners/lib/validators/license-checker.js +252 -0
- package/bin/runners/lib/validators/route-validator.js +290 -0
- package/bin/runners/runAIAgent.js +5 -10
- package/bin/runners/runAgent.js +3 -0
- package/bin/runners/runApprove.js +1233 -1200
- package/bin/runners/runAuth.js +22 -1
- package/bin/runners/runAuthority.js +528 -0
- package/bin/runners/runCheckpoint.js +4 -24
- package/bin/runners/runClassify.js +862 -859
- package/bin/runners/runConductor.js +772 -0
- package/bin/runners/runContainer.js +366 -0
- package/bin/runners/runContext.js +3 -0
- package/bin/runners/runDoctor.js +28 -41
- package/bin/runners/runEasy.js +410 -0
- package/bin/runners/runFirewall.js +3 -0
- package/bin/runners/runFirewallHook.js +3 -0
- package/bin/runners/runFix.js +76 -66
- package/bin/runners/runGuard.js +411 -18
- package/bin/runners/runIaC.js +372 -0
- package/bin/runners/runInit.js +10 -60
- package/bin/runners/runMcp.js +11 -12
- package/bin/runners/runPolish.js +240 -64
- package/bin/runners/runPromptFirewall.js +5 -12
- package/bin/runners/runProve.js +20 -55
- package/bin/runners/runReality.js +68 -59
- package/bin/runners/runReport.js +31 -5
- package/bin/runners/runRuntime.js +5 -8
- package/bin/runners/runScan.js +194 -1273
- package/bin/runners/runShip.js +695 -47
- package/bin/runners/runTruth.js +3 -0
- package/bin/runners/runValidate.js +7 -11
- package/bin/runners/runVibe.js +791 -0
- package/bin/runners/runWatch.js +14 -23
- package/bin/vibecheck.js +179 -65
- package/mcp-server/index.js +202 -636
- package/mcp-server/lib/api-client.cjs +7 -299
- package/mcp-server/package.json +1 -1
- package/mcp-server/tier-auth.js +175 -574
- package/mcp-server/tools-v3.js +800 -505
- package/mcp-server/tools.js +495 -0
- package/package.json +1 -1
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +0 -164
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +0 -291
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +0 -83
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +0 -198
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +0 -275
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +0 -167
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +0 -217
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +0 -140
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +0 -164
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +0 -234
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +0 -217
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +0 -78
- package/mcp-server/index-v1.js +0 -698
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import Order Engine
|
|
3
|
+
* Detects:
|
|
4
|
+
* - Inconsistent import ordering
|
|
5
|
+
* - Mixed import styles (require vs import)
|
|
6
|
+
* - Circular import potential
|
|
7
|
+
* - Unused imports (basic detection)
|
|
8
|
+
* - Side-effect imports
|
|
9
|
+
* - Type-only imports that should use 'import type'
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { getAST } = require("./ast-cache");
|
|
13
|
+
const traverse = require("@babel/traverse").default;
|
|
14
|
+
const t = require("@babel/types");
|
|
15
|
+
const { shouldExcludeFile, isTestContext, hasIgnoreDirective } = require("./file-filter");
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Import categories for ordering
|
|
19
|
+
*/
|
|
20
|
+
const IMPORT_CATEGORIES = {
|
|
21
|
+
BUILTIN: 1, // node:fs, fs, path, etc.
|
|
22
|
+
EXTERNAL: 2, // react, lodash, @org/pkg
|
|
23
|
+
INTERNAL: 3, // @/, ~/, #/
|
|
24
|
+
PARENT: 4, // ../
|
|
25
|
+
SIBLING: 5, // ./
|
|
26
|
+
INDEX: 6, // ./index, .
|
|
27
|
+
STYLE: 7, // .css, .scss, .less
|
|
28
|
+
TYPE: 8, // import type
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Node.js built-in modules
|
|
33
|
+
*/
|
|
34
|
+
const BUILTIN_MODULES = new Set([
|
|
35
|
+
"assert", "buffer", "child_process", "cluster", "console", "constants",
|
|
36
|
+
"crypto", "dgram", "dns", "domain", "events", "fs", "http", "https",
|
|
37
|
+
"module", "net", "os", "path", "punycode", "querystring", "readline",
|
|
38
|
+
"repl", "stream", "string_decoder", "sys", "timers", "tls", "tty",
|
|
39
|
+
"url", "util", "vm", "zlib", "process",
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Determine import category
|
|
44
|
+
*/
|
|
45
|
+
function getImportCategory(source, isTypeOnly = false) {
|
|
46
|
+
if (isTypeOnly) return IMPORT_CATEGORIES.TYPE;
|
|
47
|
+
|
|
48
|
+
// Style imports
|
|
49
|
+
if (/\.(css|scss|sass|less|styl)$/.test(source)) {
|
|
50
|
+
return IMPORT_CATEGORIES.STYLE;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Node built-in
|
|
54
|
+
if (source.startsWith("node:") || BUILTIN_MODULES.has(source)) {
|
|
55
|
+
return IMPORT_CATEGORIES.BUILTIN;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Internal aliases
|
|
59
|
+
if (source.startsWith("@/") || source.startsWith("~/") || source.startsWith("#/")) {
|
|
60
|
+
return IMPORT_CATEGORIES.INTERNAL;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Relative imports
|
|
64
|
+
if (source.startsWith("../")) {
|
|
65
|
+
return IMPORT_CATEGORIES.PARENT;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (source === "." || source === "./index" || source.startsWith("./index.")) {
|
|
69
|
+
return IMPORT_CATEGORIES.INDEX;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (source.startsWith("./")) {
|
|
73
|
+
return IMPORT_CATEGORIES.SIBLING;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// External packages
|
|
77
|
+
return IMPORT_CATEGORIES.EXTERNAL;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get category name for display
|
|
82
|
+
*/
|
|
83
|
+
function getCategoryName(category) {
|
|
84
|
+
const names = {
|
|
85
|
+
[IMPORT_CATEGORIES.BUILTIN]: "built-in",
|
|
86
|
+
[IMPORT_CATEGORIES.EXTERNAL]: "external",
|
|
87
|
+
[IMPORT_CATEGORIES.INTERNAL]: "internal alias",
|
|
88
|
+
[IMPORT_CATEGORIES.PARENT]: "parent",
|
|
89
|
+
[IMPORT_CATEGORIES.SIBLING]: "sibling",
|
|
90
|
+
[IMPORT_CATEGORIES.INDEX]: "index",
|
|
91
|
+
[IMPORT_CATEGORIES.STYLE]: "style",
|
|
92
|
+
[IMPORT_CATEGORIES.TYPE]: "type",
|
|
93
|
+
};
|
|
94
|
+
return names[category] || "unknown";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function snippetForLine(lines, line) {
|
|
98
|
+
return lines[line - 1] ? lines[line - 1].trim() : "";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Analyze import order and patterns
|
|
103
|
+
*/
|
|
104
|
+
function analyzeImportOrder(code, filePath) {
|
|
105
|
+
const findings = [];
|
|
106
|
+
|
|
107
|
+
if (shouldExcludeFile(filePath)) return findings;
|
|
108
|
+
if (isTestContext(code, filePath)) return findings;
|
|
109
|
+
if (hasIgnoreDirective(code, "import-order")) return findings;
|
|
110
|
+
|
|
111
|
+
const ast = getAST(code, filePath);
|
|
112
|
+
if (!ast) return findings;
|
|
113
|
+
|
|
114
|
+
const lines = code.split("\n");
|
|
115
|
+
|
|
116
|
+
// Collect all imports
|
|
117
|
+
const imports = [];
|
|
118
|
+
const requires = [];
|
|
119
|
+
const importedIdentifiers = new Set();
|
|
120
|
+
const usedIdentifiers = new Set();
|
|
121
|
+
let lastImportLine = 0;
|
|
122
|
+
let hasCodeBetweenImports = false;
|
|
123
|
+
|
|
124
|
+
traverse(ast, {
|
|
125
|
+
ImportDeclaration(path) {
|
|
126
|
+
const node = path.node;
|
|
127
|
+
const loc = node.loc?.start;
|
|
128
|
+
if (!loc) return;
|
|
129
|
+
|
|
130
|
+
const source = node.source.value;
|
|
131
|
+
const isTypeOnly = node.importKind === "type";
|
|
132
|
+
const category = getImportCategory(source, isTypeOnly);
|
|
133
|
+
|
|
134
|
+
// Track imported identifiers
|
|
135
|
+
for (const specifier of node.specifiers) {
|
|
136
|
+
if (t.isImportSpecifier(specifier) || t.isImportDefaultSpecifier(specifier)) {
|
|
137
|
+
importedIdentifiers.add(specifier.local.name);
|
|
138
|
+
} else if (t.isImportNamespaceSpecifier(specifier)) {
|
|
139
|
+
importedIdentifiers.add(specifier.local.name);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check for code between imports
|
|
144
|
+
if (lastImportLine > 0 && loc.line > lastImportLine + 1) {
|
|
145
|
+
// Check if there's actual code between (not just blank lines or comments)
|
|
146
|
+
const linesBetween = lines.slice(lastImportLine, loc.line - 1);
|
|
147
|
+
const hasCode = linesBetween.some(l => {
|
|
148
|
+
const trimmed = l.trim();
|
|
149
|
+
return trimmed && !trimmed.startsWith("//") && !trimmed.startsWith("/*");
|
|
150
|
+
});
|
|
151
|
+
if (hasCode) {
|
|
152
|
+
hasCodeBetweenImports = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
lastImportLine = loc.line;
|
|
156
|
+
|
|
157
|
+
imports.push({
|
|
158
|
+
source,
|
|
159
|
+
category,
|
|
160
|
+
line: loc.line,
|
|
161
|
+
isTypeOnly,
|
|
162
|
+
isSideEffect: node.specifiers.length === 0,
|
|
163
|
+
node,
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
// Track require() calls
|
|
168
|
+
CallExpression(path) {
|
|
169
|
+
const node = path.node;
|
|
170
|
+
|
|
171
|
+
if (t.isIdentifier(node.callee, { name: "require" }) &&
|
|
172
|
+
node.arguments.length > 0 &&
|
|
173
|
+
t.isStringLiteral(node.arguments[0])) {
|
|
174
|
+
|
|
175
|
+
const loc = node.loc?.start;
|
|
176
|
+
if (!loc) return;
|
|
177
|
+
|
|
178
|
+
requires.push({
|
|
179
|
+
source: node.arguments[0].value,
|
|
180
|
+
line: loc.line,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
// Track used identifiers
|
|
186
|
+
Identifier(path) {
|
|
187
|
+
// Skip if it's a declaration
|
|
188
|
+
if (path.isBindingIdentifier()) return;
|
|
189
|
+
|
|
190
|
+
// Skip property names
|
|
191
|
+
if (path.parentPath.isMemberExpression() &&
|
|
192
|
+
path.key === "property" &&
|
|
193
|
+
!path.parentPath.node.computed) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Skip import specifiers
|
|
198
|
+
if (path.parentPath.isImportSpecifier() ||
|
|
199
|
+
path.parentPath.isImportDefaultSpecifier() ||
|
|
200
|
+
path.parentPath.isImportNamespaceSpecifier()) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
usedIdentifiers.add(path.node.name);
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
// Track JSX usage
|
|
208
|
+
JSXIdentifier(path) {
|
|
209
|
+
if (path.parentPath.isJSXOpeningElement() || path.parentPath.isJSXClosingElement()) {
|
|
210
|
+
usedIdentifiers.add(path.node.name);
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Check import order
|
|
216
|
+
let lastCategory = 0;
|
|
217
|
+
let lastSource = "";
|
|
218
|
+
let orderViolations = [];
|
|
219
|
+
|
|
220
|
+
for (let i = 0; i < imports.length; i++) {
|
|
221
|
+
const imp = imports[i];
|
|
222
|
+
|
|
223
|
+
// Check category order
|
|
224
|
+
if (imp.category < lastCategory) {
|
|
225
|
+
orderViolations.push({
|
|
226
|
+
line: imp.line,
|
|
227
|
+
source: imp.source,
|
|
228
|
+
expectedBefore: getCategoryName(lastCategory),
|
|
229
|
+
actualCategory: getCategoryName(imp.category),
|
|
230
|
+
});
|
|
231
|
+
} else if (imp.category === lastCategory) {
|
|
232
|
+
// Within same category, check alphabetical order (optional)
|
|
233
|
+
// Skip this for now as it's too strict
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
lastCategory = imp.category;
|
|
237
|
+
lastSource = imp.source;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Report order violations (grouped)
|
|
241
|
+
if (orderViolations.length > 0) {
|
|
242
|
+
const firstViolation = orderViolations[0];
|
|
243
|
+
findings.push({
|
|
244
|
+
type: "import_order",
|
|
245
|
+
severity: "INFO",
|
|
246
|
+
category: "ImportOrder",
|
|
247
|
+
file: filePath,
|
|
248
|
+
line: firstViolation.line,
|
|
249
|
+
column: 0,
|
|
250
|
+
title: `Import order inconsistent (${orderViolations.length} issues)`,
|
|
251
|
+
message: `'${firstViolation.source}' (${firstViolation.actualCategory}) should come before ${firstViolation.expectedBefore} imports.`,
|
|
252
|
+
codeSnippet: snippetForLine(lines, firstViolation.line),
|
|
253
|
+
confidence: "low",
|
|
254
|
+
fixHint: "Order imports: built-in → external → internal → parent → sibling → index → styles",
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check for mixed import/require
|
|
259
|
+
if (imports.length > 0 && requires.length > 0) {
|
|
260
|
+
const firstRequire = requires[0];
|
|
261
|
+
findings.push({
|
|
262
|
+
type: "mixed_imports",
|
|
263
|
+
severity: "INFO",
|
|
264
|
+
category: "ImportOrder",
|
|
265
|
+
file: filePath,
|
|
266
|
+
line: firstRequire.line,
|
|
267
|
+
column: 0,
|
|
268
|
+
title: "Mixed import and require statements",
|
|
269
|
+
message: `File uses both ESM imports (${imports.length}) and CommonJS require (${requires.length}). Consider using consistent module syntax.`,
|
|
270
|
+
codeSnippet: snippetForLine(lines, firstRequire.line),
|
|
271
|
+
confidence: "low",
|
|
272
|
+
fixHint: "Convert to ESM: import x from 'y' instead of const x = require('y')",
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Check for code between imports
|
|
277
|
+
if (hasCodeBetweenImports) {
|
|
278
|
+
findings.push({
|
|
279
|
+
type: "code_between_imports",
|
|
280
|
+
severity: "INFO",
|
|
281
|
+
category: "ImportOrder",
|
|
282
|
+
file: filePath,
|
|
283
|
+
line: 1,
|
|
284
|
+
column: 0,
|
|
285
|
+
title: "Code between import statements",
|
|
286
|
+
message: "There is code between import statements. Keep all imports at the top of the file.",
|
|
287
|
+
codeSnippet: "",
|
|
288
|
+
confidence: "med",
|
|
289
|
+
fixHint: "Move all imports to the top of the file, before any other code",
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Check for side-effect imports (warn if not intentional)
|
|
294
|
+
const sideEffectImports = imports.filter(i => i.isSideEffect);
|
|
295
|
+
for (const imp of sideEffectImports) {
|
|
296
|
+
// Skip known side-effect imports
|
|
297
|
+
if (/\.(css|scss|sass|less)$/.test(imp.source)) continue;
|
|
298
|
+
if (imp.source.includes("polyfill")) continue;
|
|
299
|
+
if (imp.source.includes("register")) continue;
|
|
300
|
+
|
|
301
|
+
findings.push({
|
|
302
|
+
type: "side_effect_import",
|
|
303
|
+
severity: "INFO",
|
|
304
|
+
category: "ImportOrder",
|
|
305
|
+
file: filePath,
|
|
306
|
+
line: imp.line,
|
|
307
|
+
column: 0,
|
|
308
|
+
title: `Side-effect import: ${imp.source}`,
|
|
309
|
+
message: "Import doesn't import any values. If intentional, add a comment.",
|
|
310
|
+
codeSnippet: snippetForLine(lines, imp.line),
|
|
311
|
+
confidence: "low",
|
|
312
|
+
fixHint: "Add // side-effect import comment or import specific values",
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Check for potentially unused imports
|
|
317
|
+
const unusedImports = [];
|
|
318
|
+
for (const identifier of importedIdentifiers) {
|
|
319
|
+
if (!usedIdentifiers.has(identifier)) {
|
|
320
|
+
// Skip common false positives
|
|
321
|
+
if (identifier === "React") continue; // JSX transform
|
|
322
|
+
if (identifier.startsWith("_")) continue; // Intentionally unused
|
|
323
|
+
|
|
324
|
+
unusedImports.push(identifier);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (unusedImports.length > 0) {
|
|
329
|
+
// Find the line for the first unused import
|
|
330
|
+
let unusedLine = 1;
|
|
331
|
+
for (const imp of imports) {
|
|
332
|
+
for (const spec of imp.node.specifiers) {
|
|
333
|
+
if (unusedImports.includes(spec.local.name)) {
|
|
334
|
+
unusedLine = imp.line;
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
findings.push({
|
|
341
|
+
type: "unused_import",
|
|
342
|
+
severity: "INFO",
|
|
343
|
+
category: "ImportOrder",
|
|
344
|
+
file: filePath,
|
|
345
|
+
line: unusedLine,
|
|
346
|
+
column: 0,
|
|
347
|
+
title: `Potentially unused imports (${unusedImports.length})`,
|
|
348
|
+
message: `Imports may be unused: ${unusedImports.slice(0, 5).join(", ")}${unusedImports.length > 5 ? "..." : ""}`,
|
|
349
|
+
codeSnippet: snippetForLine(lines, unusedLine),
|
|
350
|
+
confidence: "low",
|
|
351
|
+
fixHint: "Remove unused imports or prefix with underscore if intentional",
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Check for imports that should use 'import type'
|
|
356
|
+
traverse(ast, {
|
|
357
|
+
ImportDeclaration(path) {
|
|
358
|
+
const node = path.node;
|
|
359
|
+
if (node.importKind === "type") return; // Already type import
|
|
360
|
+
|
|
361
|
+
const loc = node.loc?.start;
|
|
362
|
+
if (!loc) return;
|
|
363
|
+
|
|
364
|
+
// Check if all specifiers are only used as types
|
|
365
|
+
let allTypeOnly = true;
|
|
366
|
+
|
|
367
|
+
for (const specifier of node.specifiers) {
|
|
368
|
+
if (!t.isImportSpecifier(specifier)) {
|
|
369
|
+
allTypeOnly = false;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const name = specifier.local.name;
|
|
374
|
+
|
|
375
|
+
// Check if used as value anywhere
|
|
376
|
+
let usedAsValue = false;
|
|
377
|
+
traverse(ast, {
|
|
378
|
+
Identifier(innerPath) {
|
|
379
|
+
if (innerPath.node.name !== name) return;
|
|
380
|
+
if (innerPath.parentPath.isImportSpecifier()) return;
|
|
381
|
+
|
|
382
|
+
// Check if in type annotation
|
|
383
|
+
if (innerPath.findParent(p =>
|
|
384
|
+
p.isTSTypeAnnotation() ||
|
|
385
|
+
p.isTSTypeReference() ||
|
|
386
|
+
p.isTSInterfaceDeclaration() ||
|
|
387
|
+
p.isTSTypeAliasDeclaration()
|
|
388
|
+
)) {
|
|
389
|
+
return; // Type usage, OK
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
usedAsValue = true;
|
|
393
|
+
},
|
|
394
|
+
}, path.scope, path);
|
|
395
|
+
|
|
396
|
+
if (usedAsValue) {
|
|
397
|
+
allTypeOnly = false;
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (allTypeOnly && node.specifiers.length > 0) {
|
|
403
|
+
findings.push({
|
|
404
|
+
type: "should_be_type_import",
|
|
405
|
+
severity: "INFO",
|
|
406
|
+
category: "ImportOrder",
|
|
407
|
+
file: filePath,
|
|
408
|
+
line: loc.line,
|
|
409
|
+
column: 0,
|
|
410
|
+
title: "Import could use 'import type'",
|
|
411
|
+
message: "All imports from this module are only used as types.",
|
|
412
|
+
codeSnippet: snippetForLine(lines, loc.line),
|
|
413
|
+
confidence: "low",
|
|
414
|
+
fixHint: "Change to: import type { ... } from '...'",
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
return findings;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
module.exports = {
|
|
424
|
+
analyzeImportOrder,
|
|
425
|
+
getImportCategory,
|
|
426
|
+
getCategoryName,
|
|
427
|
+
IMPORT_CATEGORIES,
|
|
428
|
+
BUILTIN_MODULES,
|
|
429
|
+
};
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Mock Data Detection Engine
|
|
3
3
|
* Uses AST analysis to detect mock/test data patterns in production code
|
|
4
|
+
* Enhanced with context-aware detection to reduce false positives
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
const { getAST, parseCode } = require("./ast-cache");
|
|
7
8
|
const traverse = require("@babel/traverse").default;
|
|
8
9
|
const t = require("@babel/types");
|
|
9
10
|
const { shouldExcludeFile } = require("./file-filter");
|
|
11
|
+
const {
|
|
12
|
+
detectFalsePositive,
|
|
13
|
+
quickFalsePositiveCheck,
|
|
14
|
+
isTailwindPlaceholderModifier,
|
|
15
|
+
isLegitimateUILabel,
|
|
16
|
+
} = require("./context-detection");
|
|
10
17
|
|
|
11
18
|
/**
|
|
12
19
|
* Check if a string literal looks like mock/test data
|
|
@@ -106,6 +113,25 @@ function isSuspiciousSetTimeout(node) {
|
|
|
106
113
|
return false;
|
|
107
114
|
}
|
|
108
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Check if a finding should be skipped due to context
|
|
118
|
+
*/
|
|
119
|
+
function shouldSkipFinding(line, lineIndex, lines, patternType) {
|
|
120
|
+
// Quick pre-filter for common false positives
|
|
121
|
+
if (quickFalsePositiveCheck(line)) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Detailed context analysis
|
|
126
|
+
const result = detectFalsePositive(line, line, {
|
|
127
|
+
lineIndex,
|
|
128
|
+
allLines: lines,
|
|
129
|
+
patternType,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return result.isFalsePositive;
|
|
133
|
+
}
|
|
134
|
+
|
|
109
135
|
/**
|
|
110
136
|
* Analyze a file for mock data patterns
|
|
111
137
|
*/
|
|
@@ -125,10 +151,16 @@ function analyzeMockData(code, filePath) {
|
|
|
125
151
|
VariableDeclarator(path) {
|
|
126
152
|
const id = path.node.id;
|
|
127
153
|
const init = path.node.init;
|
|
154
|
+
const line = getLineNumber(path.node, code);
|
|
155
|
+
const lineContent = lines[line - 1] || "";
|
|
156
|
+
|
|
157
|
+
// Skip if this line is a false positive (CSS class, UI label, etc.)
|
|
158
|
+
if (shouldSkipFinding(lineContent, line - 1, lines, "mock")) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
128
161
|
|
|
129
162
|
// Check variable name
|
|
130
163
|
if (t.isIdentifier(id) && isMockVariableName(id.name)) {
|
|
131
|
-
const line = getLineNumber(path.node, code);
|
|
132
164
|
findings.push({
|
|
133
165
|
type: "mock_variable",
|
|
134
166
|
severity: "WARN",
|
|
@@ -138,14 +170,13 @@ function analyzeMockData(code, filePath) {
|
|
|
138
170
|
column: path.node.loc.start.column,
|
|
139
171
|
title: `Mock data variable: ${id.name}`,
|
|
140
172
|
message: `Variable name suggests mock/test data: ${id.name}`,
|
|
141
|
-
codeSnippet:
|
|
173
|
+
codeSnippet: lineContent.trim(),
|
|
142
174
|
confidence: "high",
|
|
143
175
|
});
|
|
144
176
|
}
|
|
145
177
|
|
|
146
178
|
// Check initializer value
|
|
147
179
|
if (init && t.isStringLiteral(init) && isMockString(init.value)) {
|
|
148
|
-
const line = getLineNumber(path.node, code);
|
|
149
180
|
findings.push({
|
|
150
181
|
type: "mock_string_literal",
|
|
151
182
|
severity: "WARN",
|
|
@@ -155,7 +186,7 @@ function analyzeMockData(code, filePath) {
|
|
|
155
186
|
column: path.node.loc.start.column,
|
|
156
187
|
title: "Mock/test string literal detected",
|
|
157
188
|
message: `String value appears to be mock data: "${init.value.substring(0, 50)}"`,
|
|
158
|
-
codeSnippet:
|
|
189
|
+
codeSnippet: lineContent.trim(),
|
|
159
190
|
confidence: "med",
|
|
160
191
|
});
|
|
161
192
|
}
|
|
@@ -165,9 +196,15 @@ function analyzeMockData(code, filePath) {
|
|
|
165
196
|
ObjectProperty(path) {
|
|
166
197
|
const key = path.node.key;
|
|
167
198
|
const value = path.node.value;
|
|
199
|
+
const line = getLineNumber(path.node, code);
|
|
200
|
+
const lineContent = lines[line - 1] || "";
|
|
201
|
+
|
|
202
|
+
// Skip if this line is a false positive (CSS class, UI label, etc.)
|
|
203
|
+
if (shouldSkipFinding(lineContent, line - 1, lines, "mock")) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
168
206
|
|
|
169
207
|
if (t.isIdentifier(key) && isMockVariableName(key.name)) {
|
|
170
|
-
const line = getLineNumber(path.node, code);
|
|
171
208
|
findings.push({
|
|
172
209
|
type: "mock_object_property",
|
|
173
210
|
severity: "WARN",
|
|
@@ -177,13 +214,12 @@ function analyzeMockData(code, filePath) {
|
|
|
177
214
|
column: path.node.loc.start.column,
|
|
178
215
|
title: `Mock data property: ${key.name}`,
|
|
179
216
|
message: `Object property name suggests mock data: ${key.name}`,
|
|
180
|
-
codeSnippet:
|
|
217
|
+
codeSnippet: lineContent.trim(),
|
|
181
218
|
confidence: "med",
|
|
182
219
|
});
|
|
183
220
|
}
|
|
184
221
|
|
|
185
222
|
if (t.isStringLiteral(value) && isMockString(value.value)) {
|
|
186
|
-
const line = getLineNumber(path.node, code);
|
|
187
223
|
findings.push({
|
|
188
224
|
type: "mock_property_value",
|
|
189
225
|
severity: "WARN",
|
|
@@ -193,7 +229,7 @@ function analyzeMockData(code, filePath) {
|
|
|
193
229
|
column: path.node.loc.start.column,
|
|
194
230
|
title: "Mock string in object property",
|
|
195
231
|
message: `Property value appears to be mock data: "${value.value.substring(0, 50)}"`,
|
|
196
|
-
codeSnippet:
|
|
232
|
+
codeSnippet: lineContent.trim(),
|
|
197
233
|
confidence: "med",
|
|
198
234
|
});
|
|
199
235
|
}
|
|
@@ -236,10 +272,17 @@ function analyzeMockData(code, filePath) {
|
|
|
236
272
|
|
|
237
273
|
// Check template literals
|
|
238
274
|
TemplateLiteral(path) {
|
|
275
|
+
const line = getLineNumber(path.node, code);
|
|
276
|
+
const lineContent = lines[line - 1] || "";
|
|
277
|
+
|
|
278
|
+
// Skip if this line is a false positive (CSS class, UI label, etc.)
|
|
279
|
+
if (shouldSkipFinding(lineContent, line - 1, lines, "mock")) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
239
283
|
const quasis = path.node.quasis;
|
|
240
284
|
for (const quasi of quasis) {
|
|
241
285
|
if (quasi.value && isMockString(quasi.value.raw)) {
|
|
242
|
-
const line = getLineNumber(path.node, code);
|
|
243
286
|
findings.push({
|
|
244
287
|
type: "mock_template_literal",
|
|
245
288
|
severity: "WARN",
|
|
@@ -249,7 +292,7 @@ function analyzeMockData(code, filePath) {
|
|
|
249
292
|
column: path.node.loc.start.column,
|
|
250
293
|
title: "Mock data in template literal",
|
|
251
294
|
message: `Template literal contains mock/test data pattern`,
|
|
252
|
-
codeSnippet:
|
|
295
|
+
codeSnippet: lineContent.trim(),
|
|
253
296
|
confidence: "med",
|
|
254
297
|
});
|
|
255
298
|
break;
|