tlc-claude-code 1.6.4 → 1.7.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/dashboard/dist/components/ContainerSecurityPane.d.ts +45 -0
- package/dashboard/dist/components/ContainerSecurityPane.js +44 -0
- package/dashboard/dist/components/ContainerSecurityPane.test.d.ts +1 -0
- package/dashboard/dist/components/ContainerSecurityPane.test.js +153 -0
- package/package.json +1 -1
- package/server/lib/access-control.test.js +1 -1
- package/server/lib/agents-cancel-command.test.js +1 -1
- package/server/lib/agents-get-command.test.js +1 -1
- package/server/lib/agents-list-command.test.js +1 -1
- package/server/lib/agents-logs-command.test.js +1 -1
- package/server/lib/agents-retry-command.test.js +1 -1
- package/server/lib/budget-limits.test.js +2 -2
- package/server/lib/code-gate/bypass-logger.js +129 -0
- package/server/lib/code-gate/bypass-logger.test.js +142 -0
- package/server/lib/code-gate/gate-command.js +114 -0
- package/server/lib/code-gate/gate-command.test.js +111 -0
- package/server/lib/code-gate/gate-config.js +163 -0
- package/server/lib/code-gate/gate-config.test.js +181 -0
- package/server/lib/code-gate/gate-engine.js +193 -0
- package/server/lib/code-gate/gate-engine.test.js +258 -0
- package/server/lib/code-gate/gate-reporter.js +123 -0
- package/server/lib/code-gate/gate-reporter.test.js +159 -0
- package/server/lib/code-gate/hooks-generator.js +149 -0
- package/server/lib/code-gate/hooks-generator.test.js +142 -0
- package/server/lib/code-gate/llm-reviewer.js +176 -0
- package/server/lib/code-gate/llm-reviewer.test.js +161 -0
- package/server/lib/code-gate/push-gate.js +133 -0
- package/server/lib/code-gate/push-gate.test.js +190 -0
- package/server/lib/code-gate/rules/architecture-rules.js +228 -0
- package/server/lib/code-gate/rules/architecture-rules.test.js +155 -0
- package/server/lib/code-gate/rules/client-rules.js +120 -0
- package/server/lib/code-gate/rules/client-rules.test.js +121 -0
- package/server/lib/code-gate/rules/config-rules.js +140 -0
- package/server/lib/code-gate/rules/config-rules.test.js +103 -0
- package/server/lib/code-gate/rules/database-rules.js +158 -0
- package/server/lib/code-gate/rules/database-rules.test.js +119 -0
- package/server/lib/code-gate/rules/docker-rules.js +201 -0
- package/server/lib/code-gate/rules/docker-rules.test.js +104 -0
- package/server/lib/code-gate/rules/quality-rules.js +304 -0
- package/server/lib/code-gate/rules/quality-rules.test.js +199 -0
- package/server/lib/code-gate/rules/security-rules.js +228 -0
- package/server/lib/code-gate/rules/security-rules.test.js +131 -0
- package/server/lib/code-gate/rules/structure-rules.js +155 -0
- package/server/lib/code-gate/rules/structure-rules.test.js +107 -0
- package/server/lib/code-gate/rules/test-rules.js +93 -0
- package/server/lib/code-gate/rules/test-rules.test.js +97 -0
- package/server/lib/code-gate/typescript-gate.js +128 -0
- package/server/lib/code-gate/typescript-gate.test.js +131 -0
- package/server/lib/code-generator.test.js +1 -1
- package/server/lib/cost-command.test.js +1 -1
- package/server/lib/cost-optimizer.test.js +1 -1
- package/server/lib/cost-projections.test.js +1 -1
- package/server/lib/cost-reports.test.js +1 -1
- package/server/lib/cost-tracker.test.js +1 -1
- package/server/lib/crypto-patterns.test.js +1 -1
- package/server/lib/design-command.test.js +1 -1
- package/server/lib/design-parser.test.js +1 -1
- package/server/lib/gemini-vision.test.js +1 -1
- package/server/lib/input-validator.test.js +1 -1
- package/server/lib/litellm-client.test.js +1 -1
- package/server/lib/litellm-command.test.js +1 -1
- package/server/lib/litellm-config.test.js +1 -1
- package/server/lib/model-pricing.test.js +1 -1
- package/server/lib/models-command.test.js +1 -1
- package/server/lib/optimize-command.test.js +1 -1
- package/server/lib/orchestration-integration.test.js +1 -1
- package/server/lib/output-encoder.test.js +1 -1
- package/server/lib/quality-evaluator.test.js +1 -1
- package/server/lib/quality-gate-command.test.js +1 -1
- package/server/lib/quality-gate-scorer.test.js +1 -1
- package/server/lib/quality-history.test.js +1 -1
- package/server/lib/quality-presets.test.js +1 -1
- package/server/lib/quality-retry.test.js +1 -1
- package/server/lib/quality-thresholds.test.js +1 -1
- package/server/lib/secure-auth.test.js +1 -1
- package/server/lib/secure-code-command.test.js +1 -1
- package/server/lib/secure-errors.test.js +1 -1
- package/server/lib/security/auth-security.test.js +4 -3
- package/server/lib/vision-command.test.js +1 -1
- package/server/lib/visual-command.test.js +1 -1
- package/server/lib/visual-testing.test.js +1 -1
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality Rules
|
|
3
|
+
*
|
|
4
|
+
* Detects hardcoded values, oversized functions, console.log leftovers,
|
|
5
|
+
* TODO/FIXME without issue references, and other code quality issues.
|
|
6
|
+
*
|
|
7
|
+
* Each check function accepts (filePath, content) and returns an array
|
|
8
|
+
* of findings: { severity, rule, line, message, fix }
|
|
9
|
+
*
|
|
10
|
+
* @module code-gate/rules/quality-rules
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check if a file is a test file (skip quality checks for tests).
|
|
15
|
+
* @param {string} filePath
|
|
16
|
+
* @returns {boolean}
|
|
17
|
+
*/
|
|
18
|
+
function isTestFile(filePath) {
|
|
19
|
+
return /\.(test|spec)\.[jt]sx?$/.test(filePath) || filePath.includes('__tests__');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get the line number for a character offset in content.
|
|
24
|
+
* @param {string} content
|
|
25
|
+
* @param {number} offset
|
|
26
|
+
* @returns {number}
|
|
27
|
+
*/
|
|
28
|
+
function lineAt(content, offset) {
|
|
29
|
+
return content.substring(0, offset).split('\n').length;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check for hardcoded URLs, IPs, and ports.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} filePath - File path
|
|
36
|
+
* @param {string} content - File content
|
|
37
|
+
* @returns {Array<{severity: string, rule: string, line: number, message: string, fix: string}>}
|
|
38
|
+
*/
|
|
39
|
+
function checkHardcodedUrls(filePath, content) {
|
|
40
|
+
if (isTestFile(filePath)) return [];
|
|
41
|
+
const findings = [];
|
|
42
|
+
const lines = content.split('\n');
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < lines.length; i++) {
|
|
45
|
+
const line = lines[i];
|
|
46
|
+
const trimmed = line.trim();
|
|
47
|
+
|
|
48
|
+
// Skip comments
|
|
49
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Hardcoded http/https URLs
|
|
54
|
+
const urlMatch = line.match(/['"`](https?:\/\/[^'"`]+)['"`]/);
|
|
55
|
+
if (urlMatch) {
|
|
56
|
+
findings.push({
|
|
57
|
+
severity: 'block',
|
|
58
|
+
rule: 'no-hardcoded-urls',
|
|
59
|
+
line: i + 1,
|
|
60
|
+
message: `Hardcoded URL: ${urlMatch[1]}`,
|
|
61
|
+
fix: 'Use process.env or config for URLs',
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Hardcoded IP addresses
|
|
66
|
+
const ipMatch = line.match(/['"`](\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})['"`]/);
|
|
67
|
+
if (ipMatch && !urlMatch) {
|
|
68
|
+
findings.push({
|
|
69
|
+
severity: 'block',
|
|
70
|
+
rule: 'no-hardcoded-urls',
|
|
71
|
+
line: i + 1,
|
|
72
|
+
message: `Hardcoded IP: ${ipMatch[1]}`,
|
|
73
|
+
fix: 'Use process.env or config for host addresses',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Hardcoded port assignments (const port = 3000)
|
|
78
|
+
const portMatch = line.match(/\b(?:const|let|var)\s+port\s*=\s*(\d+)/);
|
|
79
|
+
if (portMatch) {
|
|
80
|
+
findings.push({
|
|
81
|
+
severity: 'block',
|
|
82
|
+
rule: 'no-hardcoded-urls',
|
|
83
|
+
line: i + 1,
|
|
84
|
+
message: `Hardcoded port: ${portMatch[1]}`,
|
|
85
|
+
fix: 'Use process.env.PORT or config',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return findings;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check for hardcoded secrets, API keys, tokens, and passwords.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} filePath - File path
|
|
97
|
+
* @param {string} content - File content
|
|
98
|
+
* @returns {Array}
|
|
99
|
+
*/
|
|
100
|
+
function checkHardcodedSecrets(filePath, content) {
|
|
101
|
+
if (isTestFile(filePath)) return [];
|
|
102
|
+
const findings = [];
|
|
103
|
+
const lines = content.split('\n');
|
|
104
|
+
|
|
105
|
+
const secretPatterns = [
|
|
106
|
+
{ pattern: /(?:api[_-]?key|apikey)\s*=\s*['"`]([^'"`]{8,})['"`]/i, label: 'API key' },
|
|
107
|
+
{ pattern: /(?:password|passwd|pwd)\s*=\s*['"`]([^'"`]+)['"`]/i, label: 'password' },
|
|
108
|
+
{ pattern: /(?:secret|token)\s*=\s*['"`]([^'"`]{8,})['"`]/i, label: 'token/secret' },
|
|
109
|
+
{ pattern: /['"`](eyJ[A-Za-z0-9_-]+\.)/i, label: 'JWT token' },
|
|
110
|
+
{ pattern: /['"`](sk-[a-zA-Z0-9]{20,})['"`]/, label: 'API key' },
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < lines.length; i++) {
|
|
114
|
+
const line = lines[i];
|
|
115
|
+
const trimmed = line.trim();
|
|
116
|
+
|
|
117
|
+
// Skip comments and env references
|
|
118
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*')) continue;
|
|
119
|
+
if (line.includes('process.env')) continue;
|
|
120
|
+
|
|
121
|
+
for (const { pattern, label } of secretPatterns) {
|
|
122
|
+
if (pattern.test(line)) {
|
|
123
|
+
findings.push({
|
|
124
|
+
severity: 'block',
|
|
125
|
+
rule: 'no-hardcoded-secrets',
|
|
126
|
+
line: i + 1,
|
|
127
|
+
message: `Hardcoded ${label} detected`,
|
|
128
|
+
fix: 'Use environment variables or a secrets manager',
|
|
129
|
+
});
|
|
130
|
+
break; // One finding per line is enough
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return findings;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check for console.log/warn/debug left in production code.
|
|
140
|
+
* console.error is allowed.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} filePath
|
|
143
|
+
* @param {string} content
|
|
144
|
+
* @returns {Array}
|
|
145
|
+
*/
|
|
146
|
+
function checkConsoleLogs(filePath, content) {
|
|
147
|
+
if (isTestFile(filePath)) return [];
|
|
148
|
+
const findings = [];
|
|
149
|
+
const lines = content.split('\n');
|
|
150
|
+
|
|
151
|
+
for (let i = 0; i < lines.length; i++) {
|
|
152
|
+
const line = lines[i];
|
|
153
|
+
const trimmed = line.trim();
|
|
154
|
+
if (trimmed.startsWith('//')) continue;
|
|
155
|
+
|
|
156
|
+
const match = line.match(/console\.(log|warn|debug|info)\s*\(/);
|
|
157
|
+
if (match) {
|
|
158
|
+
findings.push({
|
|
159
|
+
severity: 'warn',
|
|
160
|
+
rule: 'no-console-log',
|
|
161
|
+
line: i + 1,
|
|
162
|
+
message: `console.${match[1]}() found in production code`,
|
|
163
|
+
fix: 'Use a proper logger or remove debug output',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return findings;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Check for functions exceeding the maximum line count.
|
|
173
|
+
*
|
|
174
|
+
* @param {string} filePath
|
|
175
|
+
* @param {string} content
|
|
176
|
+
* @param {Object} [options]
|
|
177
|
+
* @param {number} [options.maxLines=50]
|
|
178
|
+
* @returns {Array}
|
|
179
|
+
*/
|
|
180
|
+
function checkFunctionLength(filePath, content, options = {}) {
|
|
181
|
+
const maxLines = options.maxLines || 50;
|
|
182
|
+
const findings = [];
|
|
183
|
+
const lines = content.split('\n');
|
|
184
|
+
let braceDepth = 0;
|
|
185
|
+
let funcStart = -1;
|
|
186
|
+
let funcName = '';
|
|
187
|
+
|
|
188
|
+
for (let i = 0; i < lines.length; i++) {
|
|
189
|
+
const line = lines[i];
|
|
190
|
+
|
|
191
|
+
// Detect function start
|
|
192
|
+
if (funcStart === -1) {
|
|
193
|
+
const funcMatch = line.match(/(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:\([^)]*\)|[^=])\s*=>)\s*\{?/);
|
|
194
|
+
if (funcMatch) {
|
|
195
|
+
funcName = funcMatch[1] || funcMatch[2] || 'anonymous';
|
|
196
|
+
if (line.includes('{')) {
|
|
197
|
+
funcStart = i;
|
|
198
|
+
braceDepth = 0;
|
|
199
|
+
// Count braces on this line
|
|
200
|
+
for (const ch of line) {
|
|
201
|
+
if (ch === '{') braceDepth++;
|
|
202
|
+
if (ch === '}') braceDepth--;
|
|
203
|
+
}
|
|
204
|
+
if (braceDepth <= 0) {
|
|
205
|
+
// One-liner function
|
|
206
|
+
funcStart = -1;
|
|
207
|
+
}
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (funcStart >= 0) {
|
|
214
|
+
for (const ch of line) {
|
|
215
|
+
if (ch === '{') braceDepth++;
|
|
216
|
+
if (ch === '}') braceDepth--;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (braceDepth <= 0) {
|
|
220
|
+
const length = i - funcStart + 1;
|
|
221
|
+
if (length > maxLines) {
|
|
222
|
+
findings.push({
|
|
223
|
+
severity: 'warn',
|
|
224
|
+
rule: 'max-function-length',
|
|
225
|
+
line: funcStart + 1,
|
|
226
|
+
message: `Function '${funcName}' is ${length} lines (max: ${maxLines})`,
|
|
227
|
+
fix: 'Extract helper functions to reduce complexity',
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
funcStart = -1;
|
|
231
|
+
funcName = '';
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return findings;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Check if file exceeds maximum line count.
|
|
241
|
+
*
|
|
242
|
+
* @param {string} filePath
|
|
243
|
+
* @param {string} content
|
|
244
|
+
* @param {Object} [options]
|
|
245
|
+
* @param {number} [options.maxLines=300]
|
|
246
|
+
* @returns {Array}
|
|
247
|
+
*/
|
|
248
|
+
function checkFileLength(filePath, content, options = {}) {
|
|
249
|
+
const maxLines = options.maxLines || 300;
|
|
250
|
+
const lineCount = content.split('\n').length;
|
|
251
|
+
|
|
252
|
+
if (lineCount > maxLines) {
|
|
253
|
+
return [{
|
|
254
|
+
severity: 'warn',
|
|
255
|
+
rule: 'max-file-length',
|
|
256
|
+
line: 1,
|
|
257
|
+
message: `File is ${lineCount} lines (max: ${maxLines})`,
|
|
258
|
+
fix: 'Split into smaller, focused modules',
|
|
259
|
+
}];
|
|
260
|
+
}
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Check for TODO/FIXME/HACK comments without issue references.
|
|
266
|
+
* Valid references: (#123), [JIRA-456], (PROJ-789)
|
|
267
|
+
*
|
|
268
|
+
* @param {string} filePath
|
|
269
|
+
* @param {string} content
|
|
270
|
+
* @returns {Array}
|
|
271
|
+
*/
|
|
272
|
+
function checkTodoWithoutRef(filePath, content) {
|
|
273
|
+
const findings = [];
|
|
274
|
+
const lines = content.split('\n');
|
|
275
|
+
|
|
276
|
+
for (let i = 0; i < lines.length; i++) {
|
|
277
|
+
const line = lines[i];
|
|
278
|
+
const match = line.match(/\/\/\s*(TODO|FIXME|HACK)\b/i);
|
|
279
|
+
if (match) {
|
|
280
|
+
// Check if there's an issue reference after the tag
|
|
281
|
+
const hasRef = /[#(\[][A-Z0-9_-]+[\])]/i.test(line);
|
|
282
|
+
if (!hasRef) {
|
|
283
|
+
findings.push({
|
|
284
|
+
severity: 'warn',
|
|
285
|
+
rule: 'todo-needs-ref',
|
|
286
|
+
line: i + 1,
|
|
287
|
+
message: `${match[1]} without issue reference`,
|
|
288
|
+
fix: `Add issue reference: // ${match[1]}(#123): description`,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return findings;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
module.exports = {
|
|
298
|
+
checkHardcodedUrls,
|
|
299
|
+
checkHardcodedSecrets,
|
|
300
|
+
checkConsoleLogs,
|
|
301
|
+
checkFunctionLength,
|
|
302
|
+
checkFileLength,
|
|
303
|
+
checkTodoWithoutRef,
|
|
304
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality Rules Tests
|
|
3
|
+
*
|
|
4
|
+
* Detects hardcoded values, oversized functions, console.log leftovers,
|
|
5
|
+
* TODO without issue refs, and other code quality issues.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
checkHardcodedUrls,
|
|
11
|
+
checkHardcodedSecrets,
|
|
12
|
+
checkConsoleLogs,
|
|
13
|
+
checkFunctionLength,
|
|
14
|
+
checkFileLength,
|
|
15
|
+
checkTodoWithoutRef,
|
|
16
|
+
} = require('./quality-rules.js');
|
|
17
|
+
|
|
18
|
+
describe('Quality Rules', () => {
|
|
19
|
+
describe('checkHardcodedUrls', () => {
|
|
20
|
+
it('detects http:// URLs in code', () => {
|
|
21
|
+
const findings = checkHardcodedUrls('app.js', 'const url = "http://localhost:3000";');
|
|
22
|
+
expect(findings).toHaveLength(1);
|
|
23
|
+
expect(findings[0].rule).toBe('no-hardcoded-urls');
|
|
24
|
+
expect(findings[0].severity).toBe('block');
|
|
25
|
+
expect(findings[0].fix).toBeDefined();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('detects https:// URLs in code', () => {
|
|
29
|
+
const findings = checkHardcodedUrls('app.js', 'const api = "https://api.example.com";');
|
|
30
|
+
expect(findings).toHaveLength(1);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('passes clean code using env vars', () => {
|
|
34
|
+
const findings = checkHardcodedUrls('app.js', 'const url = process.env.API_URL;');
|
|
35
|
+
expect(findings).toHaveLength(0);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('ignores URLs in comments', () => {
|
|
39
|
+
const findings = checkHardcodedUrls('app.js', '// See https://docs.example.com for details');
|
|
40
|
+
expect(findings).toHaveLength(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('ignores URLs in test files', () => {
|
|
44
|
+
const findings = checkHardcodedUrls('app.test.js', 'const url = "http://localhost:3000";');
|
|
45
|
+
expect(findings).toHaveLength(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('detects hardcoded IPs', () => {
|
|
49
|
+
const findings = checkHardcodedUrls('app.js', 'const host = "192.168.1.100";');
|
|
50
|
+
expect(findings).toHaveLength(1);
|
|
51
|
+
expect(findings[0].rule).toBe('no-hardcoded-urls');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('detects hardcoded ports', () => {
|
|
55
|
+
const code = 'const port = 3000;\napp.listen(port);';
|
|
56
|
+
const findings = checkHardcodedUrls('server.js', code);
|
|
57
|
+
expect(findings).toHaveLength(1);
|
|
58
|
+
expect(findings[0].rule).toBe('no-hardcoded-urls');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('checkHardcodedSecrets', () => {
|
|
63
|
+
it('detects API keys', () => {
|
|
64
|
+
const code = 'const apiKey = "sk-1234567890abcdef";';
|
|
65
|
+
const findings = checkHardcodedSecrets('config.js', code);
|
|
66
|
+
expect(findings).toHaveLength(1);
|
|
67
|
+
expect(findings[0].rule).toBe('no-hardcoded-secrets');
|
|
68
|
+
expect(findings[0].severity).toBe('block');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('detects passwords in assignments', () => {
|
|
72
|
+
const code = 'const password = "super-secret-123";';
|
|
73
|
+
const findings = checkHardcodedSecrets('auth.js', code);
|
|
74
|
+
expect(findings).toHaveLength(1);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('detects tokens', () => {
|
|
78
|
+
const code = 'const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test";';
|
|
79
|
+
const findings = checkHardcodedSecrets('api.js', code);
|
|
80
|
+
expect(findings).toHaveLength(1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('passes env var references', () => {
|
|
84
|
+
const code = 'const apiKey = process.env.API_KEY;';
|
|
85
|
+
const findings = checkHardcodedSecrets('config.js', code);
|
|
86
|
+
expect(findings).toHaveLength(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('ignores test files', () => {
|
|
90
|
+
const code = 'const token = "test-token-12345";';
|
|
91
|
+
const findings = checkHardcodedSecrets('auth.test.js', code);
|
|
92
|
+
expect(findings).toHaveLength(0);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('checkConsoleLogs', () => {
|
|
97
|
+
it('detects console.log in production code', () => {
|
|
98
|
+
const findings = checkConsoleLogs('app.js', 'console.log("debug");');
|
|
99
|
+
expect(findings).toHaveLength(1);
|
|
100
|
+
expect(findings[0].rule).toBe('no-console-log');
|
|
101
|
+
expect(findings[0].severity).toBe('warn');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('detects console.warn', () => {
|
|
105
|
+
const findings = checkConsoleLogs('app.js', 'console.warn("oops");');
|
|
106
|
+
expect(findings).toHaveLength(1);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('detects console.debug', () => {
|
|
110
|
+
const findings = checkConsoleLogs('app.js', 'console.debug("trace");');
|
|
111
|
+
expect(findings).toHaveLength(1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('allows console.error', () => {
|
|
115
|
+
const findings = checkConsoleLogs('app.js', 'console.error("critical failure");');
|
|
116
|
+
expect(findings).toHaveLength(0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('ignores test files', () => {
|
|
120
|
+
const findings = checkConsoleLogs('app.test.js', 'console.log("test output");');
|
|
121
|
+
expect(findings).toHaveLength(0);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('checkFunctionLength', () => {
|
|
126
|
+
it('detects functions over 50 lines', () => {
|
|
127
|
+
const lines = ['function longFunc() {'];
|
|
128
|
+
for (let i = 0; i < 55; i++) {
|
|
129
|
+
lines.push(` const x${i} = ${i};`);
|
|
130
|
+
}
|
|
131
|
+
lines.push('}');
|
|
132
|
+
const findings = checkFunctionLength('app.js', lines.join('\n'));
|
|
133
|
+
expect(findings).toHaveLength(1);
|
|
134
|
+
expect(findings[0].rule).toBe('max-function-length');
|
|
135
|
+
expect(findings[0].severity).toBe('warn');
|
|
136
|
+
expect(findings[0].message).toContain('longFunc');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('passes short functions', () => {
|
|
140
|
+
const code = 'function short() {\n return 1;\n}';
|
|
141
|
+
const findings = checkFunctionLength('app.js', code);
|
|
142
|
+
expect(findings).toHaveLength(0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('detects arrow functions over limit', () => {
|
|
146
|
+
const lines = ['const longArrow = () => {'];
|
|
147
|
+
for (let i = 0; i < 55; i++) {
|
|
148
|
+
lines.push(` const x${i} = ${i};`);
|
|
149
|
+
}
|
|
150
|
+
lines.push('};');
|
|
151
|
+
const findings = checkFunctionLength('app.js', lines.join('\n'));
|
|
152
|
+
expect(findings).toHaveLength(1);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('checkFileLength', () => {
|
|
157
|
+
it('detects files over 300 lines', () => {
|
|
158
|
+
const lines = Array.from({ length: 310 }, (_, i) => `const x${i} = ${i};`);
|
|
159
|
+
const findings = checkFileLength('app.js', lines.join('\n'));
|
|
160
|
+
expect(findings).toHaveLength(1);
|
|
161
|
+
expect(findings[0].rule).toBe('max-file-length');
|
|
162
|
+
expect(findings[0].severity).toBe('warn');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('passes files under limit', () => {
|
|
166
|
+
const findings = checkFileLength('app.js', 'const x = 1;');
|
|
167
|
+
expect(findings).toHaveLength(0);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('checkTodoWithoutRef', () => {
|
|
172
|
+
it('detects TODO without issue reference', () => {
|
|
173
|
+
const findings = checkTodoWithoutRef('app.js', '// TODO: fix this later');
|
|
174
|
+
expect(findings).toHaveLength(1);
|
|
175
|
+
expect(findings[0].rule).toBe('todo-needs-ref');
|
|
176
|
+
expect(findings[0].severity).toBe('warn');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('detects FIXME without reference', () => {
|
|
180
|
+
const findings = checkTodoWithoutRef('app.js', '// FIXME: broken');
|
|
181
|
+
expect(findings).toHaveLength(1);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('detects HACK without reference', () => {
|
|
185
|
+
const findings = checkTodoWithoutRef('app.js', '// HACK: workaround');
|
|
186
|
+
expect(findings).toHaveLength(1);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('passes TODO with issue reference', () => {
|
|
190
|
+
const findings = checkTodoWithoutRef('app.js', '// TODO(#123): fix this later');
|
|
191
|
+
expect(findings).toHaveLength(0);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('passes TODO with ticket reference', () => {
|
|
195
|
+
const findings = checkTodoWithoutRef('app.js', '// TODO [JIRA-456]: fix');
|
|
196
|
+
expect(findings).toHaveLength(0);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|