@vibecodeqa/cli 0.21.0 → 0.23.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/dist/check-meta.js +1 -1
- package/dist/cli.js +126 -106
- package/dist/fs-utils.js +17 -2
- package/dist/report/components.d.ts +8 -0
- package/dist/report/components.js +4 -0
- package/dist/report/favicon.d.ts +2 -0
- package/dist/report/favicon.js +2 -0
- package/dist/report/html.js +45 -34
- package/dist/report/pages.js +24 -18
- package/dist/report/svg.js +4 -2
- package/dist/runners/accessibility.js +37 -6
- package/dist/runners/architecture.d.ts +2 -0
- package/dist/runners/architecture.js +155 -1
- package/dist/runners/best-practices.js +187 -40
- package/dist/runners/dependencies.js +3 -1
- package/dist/runners/duplication.js +8 -1
- package/dist/runners/error-handling.js +16 -3
- package/dist/runners/performance.js +3 -1
- package/dist/runners/react.js +43 -7
- package/dist/runners/standards.js +4 -1
- package/dist/runners/type-safety.js +3 -1
- package/package.json +1 -1
|
@@ -28,7 +28,14 @@ export function runDuplication(cwd) {
|
|
|
28
28
|
const block = lines
|
|
29
29
|
.slice(i, i + MIN_LINES)
|
|
30
30
|
.map((l) => l.trim())
|
|
31
|
-
.filter((l) => l.length > 0 &&
|
|
31
|
+
.filter((l) => l.length > 0 &&
|
|
32
|
+
!l.startsWith("//") &&
|
|
33
|
+
!l.startsWith("*") &&
|
|
34
|
+
!l.startsWith("import ") &&
|
|
35
|
+
!l.startsWith("export {") &&
|
|
36
|
+
l !== "{" &&
|
|
37
|
+
l !== "}" &&
|
|
38
|
+
l !== "");
|
|
32
39
|
if (block.length < MIN_LINES - 2)
|
|
33
40
|
continue; // too many empty/trivial lines
|
|
34
41
|
const key = block.join("\n");
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
/** Error handling check — detects poor error handling patterns. */
|
|
2
|
-
import { gradeFromScore } from "../types.js";
|
|
3
2
|
import { getProductionFiles } from "../fs-utils.js";
|
|
3
|
+
import { gradeFromScore } from "../types.js";
|
|
4
4
|
export function runErrorHandling(cwd, stack) {
|
|
5
5
|
const start = Date.now();
|
|
6
6
|
const issues = [];
|
|
7
7
|
const files = getProductionFiles(cwd);
|
|
8
8
|
if (files.length === 0) {
|
|
9
|
-
return {
|
|
9
|
+
return {
|
|
10
|
+
name: "error-handling",
|
|
11
|
+
score: 100,
|
|
12
|
+
grade: "A",
|
|
13
|
+
details: { skipped: true, reason: "no source files" },
|
|
14
|
+
issues: [],
|
|
15
|
+
duration: Date.now() - start,
|
|
16
|
+
};
|
|
10
17
|
}
|
|
11
18
|
let emptyCatch = 0;
|
|
12
19
|
let throwString = 0;
|
|
@@ -20,7 +27,13 @@ export function runErrorHandling(cwd, stack) {
|
|
|
20
27
|
}
|
|
21
28
|
if (/\bthrow\s+["'`]/.test(line)) {
|
|
22
29
|
throwString++;
|
|
23
|
-
issues.push({
|
|
30
|
+
issues.push({
|
|
31
|
+
severity: "warning",
|
|
32
|
+
message: "throw string literal — use throw new Error()",
|
|
33
|
+
file: f.path,
|
|
34
|
+
line: i + 1,
|
|
35
|
+
rule: "throw-string",
|
|
36
|
+
});
|
|
24
37
|
}
|
|
25
38
|
}
|
|
26
39
|
}
|
package/dist/runners/react.js
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
/** React-specific checks — hooks rules, conditional hooks, missing keys, prop spreading. */
|
|
2
|
-
import { gradeFromScore } from "../types.js";
|
|
3
2
|
import { getProductionFiles } from "../fs-utils.js";
|
|
3
|
+
import { gradeFromScore } from "../types.js";
|
|
4
4
|
export function runReact(cwd, stack) {
|
|
5
5
|
const start = Date.now();
|
|
6
6
|
if (stack.framework !== "react") {
|
|
7
|
-
return {
|
|
7
|
+
return {
|
|
8
|
+
name: "react",
|
|
9
|
+
score: 100,
|
|
10
|
+
grade: "A",
|
|
11
|
+
details: { skipped: true, reason: "not a React project" },
|
|
12
|
+
issues: [],
|
|
13
|
+
duration: Date.now() - start,
|
|
14
|
+
};
|
|
8
15
|
}
|
|
9
16
|
const files = getProductionFiles(cwd).filter((f) => f.ext === ".tsx" || f.ext === ".jsx");
|
|
10
17
|
if (files.length === 0) {
|
|
11
|
-
return {
|
|
18
|
+
return {
|
|
19
|
+
name: "react",
|
|
20
|
+
score: 100,
|
|
21
|
+
grade: "A",
|
|
22
|
+
details: { skipped: true, reason: "no JSX/TSX files" },
|
|
23
|
+
issues: [],
|
|
24
|
+
duration: Date.now() - start,
|
|
25
|
+
};
|
|
12
26
|
}
|
|
13
27
|
const issues = [];
|
|
14
28
|
let conditionalHooks = 0;
|
|
@@ -41,7 +55,13 @@ export function runReact(cwd, stack) {
|
|
|
41
55
|
// 1. Hooks called inside conditionals
|
|
42
56
|
if (condBraceDepth > 0 && /\buse[A-Z]\w*\s*\(/.test(trimmed) && !/\/\//.test(trimmed.split("use")[0])) {
|
|
43
57
|
conditionalHooks++;
|
|
44
|
-
issues.push({
|
|
58
|
+
issues.push({
|
|
59
|
+
severity: "error",
|
|
60
|
+
message: "Hook called inside conditional — violates Rules of Hooks",
|
|
61
|
+
file: f.path,
|
|
62
|
+
line: i + 1,
|
|
63
|
+
rule: "conditional-hook",
|
|
64
|
+
});
|
|
45
65
|
}
|
|
46
66
|
// 2. Missing key in .map() returning JSX
|
|
47
67
|
if (/\.map\s*\(/.test(trimmed)) {
|
|
@@ -55,12 +75,24 @@ export function runReact(cwd, stack) {
|
|
|
55
75
|
// 3. index as key
|
|
56
76
|
if (/key=\{(?:i|idx|index)\}/.test(trimmed) || /key=\{.*(?:, *(?:i|idx|index)\))/.test(trimmed)) {
|
|
57
77
|
indexKeys++;
|
|
58
|
-
issues.push({
|
|
78
|
+
issues.push({
|
|
79
|
+
severity: "warning",
|
|
80
|
+
message: "Using index as key — can cause rendering bugs with reorderable lists",
|
|
81
|
+
file: f.path,
|
|
82
|
+
line: i + 1,
|
|
83
|
+
rule: "index-key",
|
|
84
|
+
});
|
|
59
85
|
}
|
|
60
86
|
// 4. Prop spreading ({...props} on DOM elements)
|
|
61
87
|
if (/\{\.\.\.(?!children)\w+\}/.test(trimmed) && /<[a-z]/.test(trimmed)) {
|
|
62
88
|
propSpreading++;
|
|
63
|
-
issues.push({
|
|
89
|
+
issues.push({
|
|
90
|
+
severity: "warning",
|
|
91
|
+
message: "Spreading props onto DOM element — can pass unexpected attributes",
|
|
92
|
+
file: f.path,
|
|
93
|
+
line: i + 1,
|
|
94
|
+
rule: "prop-spreading",
|
|
95
|
+
});
|
|
64
96
|
}
|
|
65
97
|
// 5. Inline arrow functions in JSX event handlers (performance)
|
|
66
98
|
if (/on[A-Z]\w*=\{(?:\(\) =>|function)/.test(trimmed)) {
|
|
@@ -70,7 +102,11 @@ export function runReact(cwd, stack) {
|
|
|
70
102
|
}
|
|
71
103
|
// Only warn about inline handlers if there are many
|
|
72
104
|
if (inlineHandlers > 15) {
|
|
73
|
-
issues.push({
|
|
105
|
+
issues.push({
|
|
106
|
+
severity: "warning",
|
|
107
|
+
message: `${inlineHandlers} inline arrow functions in JSX handlers — extract to named functions for readability`,
|
|
108
|
+
rule: "inline-handlers",
|
|
109
|
+
});
|
|
74
110
|
}
|
|
75
111
|
const errors = issues.filter((i) => i.severity === "error").length;
|
|
76
112
|
const warnings = issues.filter((i) => i.severity === "warning").length;
|
|
@@ -28,7 +28,7 @@ const CODE_SMELLS = [
|
|
|
28
28
|
message: "dangerouslySetInnerHTML bypasses React's XSS protection",
|
|
29
29
|
},
|
|
30
30
|
{ name: "document.write", pattern: /document\.write\s*\(/, severity: "error", message: "document.write blocks rendering" },
|
|
31
|
-
{ name: "http:// URL", pattern: /['"]http:\/\/(?!localhost|127\.0\.0\.1)/, severity: "warning", message: "Non-HTTPS URL — use https://" },
|
|
31
|
+
{ name: "http:// URL", pattern: /['"]http:\/\/(?!localhost|127\.0\.0\.1|www\.w3\.org|schemas?\.)/, severity: "warning", message: "Non-HTTPS URL — use https://" },
|
|
32
32
|
{ name: "TODO/FIXME", pattern: /\b(TODO|FIXME|HACK|XXX)\b/, severity: "warning", message: "Unresolved TODO/FIXME comment" },
|
|
33
33
|
{
|
|
34
34
|
name: "magic number",
|
|
@@ -96,6 +96,9 @@ export function runStandards(cwd, stack) {
|
|
|
96
96
|
continue;
|
|
97
97
|
if (/\bpattern\s*:|name:\s*["']|message:\s*["']|description:\s*["']|risk:\s*["']|recommendation:\s*["']/.test(trimmed))
|
|
98
98
|
continue;
|
|
99
|
+
// Skip string-only lines (check-meta descriptions, inline scripts)
|
|
100
|
+
if (/^\s*["'`].*["'`][,;]?\s*$/.test(line))
|
|
101
|
+
continue;
|
|
99
102
|
for (const check of CODE_SMELLS) {
|
|
100
103
|
// Skip console.log in CLI entry points (intentional output)
|
|
101
104
|
if (check.name === "console.log" && (f.path.includes("cli.") || f.path.includes("bin/")))
|
|
@@ -42,9 +42,11 @@ export function runTypeSafety(cwd, isDart = false) {
|
|
|
42
42
|
const trimmed = line.trim();
|
|
43
43
|
if (trimmed.startsWith("//") || trimmed.startsWith("*"))
|
|
44
44
|
continue;
|
|
45
|
-
// Skip pattern definition lines (prevents false positives
|
|
45
|
+
// Skip pattern definition lines and string-heavy lines (prevents false positives)
|
|
46
46
|
if (/\bpattern\s*:|name:\s*["']|message:\s*["']|description:\s*["']|risk:\s*["']|recommendation:\s*["']/.test(trimmed))
|
|
47
47
|
continue;
|
|
48
|
+
if (/^\s*["'`].*["'`][,;]?\s*$/.test(line))
|
|
49
|
+
continue;
|
|
48
50
|
for (const p of PATTERNS) {
|
|
49
51
|
const matches = line.match(p.pattern);
|
|
50
52
|
if (matches) {
|