@sun-asterisk/sunlint 1.2.1 → 1.2.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/config/rule-analysis-strategies.js +18 -2
- package/engines/eslint-engine.js +9 -11
- package/engines/heuristic-engine.js +55 -31
- package/package.json +2 -1
- package/rules/README.md +252 -0
- package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
- package/rules/common/C002_no_duplicate_code/config.json +23 -0
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
- package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
- package/rules/common/C006_function_naming/analyzer.js +504 -0
- package/rules/common/C006_function_naming/config.json +86 -0
- package/rules/common/C006_function_naming/smart-analyzer.js +503 -0
- package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
- package/rules/common/C012_command_query_separation/analyzer.js +481 -0
- package/rules/common/C012_command_query_separation/ast-analyzer.js +495 -0
- package/rules/common/C013_no_dead_code/analyzer.js +206 -0
- package/rules/common/C014_dependency_injection/analyzer.js +338 -0
- package/rules/common/C017_constructor_logic/analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +362 -0
- package/rules/common/C019_log_level_usage/config.json +121 -0
- package/rules/common/C029_catch_block_logging/analyzer-backup.js +426 -0
- package/rules/common/C029_catch_block_logging/analyzer-fixed.js +130 -0
- package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +487 -0
- package/rules/common/C029_catch_block_logging/analyzer-simple.js +110 -0
- package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +755 -0
- package/rules/common/C029_catch_block_logging/analyzer.js +129 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +441 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +127 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer.js +133 -0
- package/rules/common/C029_catch_block_logging/cfg-analyzer.js +408 -0
- package/rules/common/C029_catch_block_logging/config.json +59 -0
- package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +454 -0
- package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +700 -0
- package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +568 -0
- package/rules/common/C029_catch_block_logging/semantic-analyzer.js +459 -0
- package/rules/common/C031_validation_separation/analyzer.js +186 -0
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
- package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +296 -0
- package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
- package/rules/common/C043_no_console_or_print/analyzer.js +431 -0
- package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +590 -0
- package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
- package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
- package/rules/docs/C002_no_duplicate_code.md +57 -0
- package/rules/docs/C031_validation_separation.md +72 -0
- package/rules/index.js +155 -0
- package/rules/migration/converter.js +385 -0
- package/rules/migration/mapping.json +164 -0
- package/rules/parser/constants.js +31 -0
- package/rules/parser/file-config.js +80 -0
- package/rules/parser/rule-parser-simple.js +305 -0
- package/rules/parser/rule-parser.js +527 -0
- package/rules/security/S015_insecure_tls_certificate/analyzer.js +150 -0
- package/rules/security/S015_insecure_tls_certificate/ast-analyzer.js +237 -0
- package/rules/security/S023_no_json_injection/analyzer.js +278 -0
- package/rules/security/S023_no_json_injection/ast-analyzer.js +359 -0
- package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
- package/rules/security/S026_json_schema_validation/config.json +27 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +436 -0
- package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
- package/rules/security/S029_csrf_protection/analyzer.js +330 -0
- package/rules/tests/C002_no_duplicate_code.test.js +50 -0
- package/rules/universal/C010/generic.js +0 -0
- package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
- package/rules/utils/ast-utils.js +191 -0
- package/rules/utils/base-analyzer.js +98 -0
- package/rules/utils/pattern-matchers.js +239 -0
- package/rules/utils/rule-helpers.js +264 -0
- package/rules/utils/severity-constants.js +93 -0
- package/scripts/generate_insights.js +188 -0
- package/scripts/merge-reports.js +0 -424
- package/scripts/test-scripts/README.md +0 -22
- package/scripts/test-scripts/test-c041-comparison.js +0 -114
- package/scripts/test-scripts/test-c041-eslint.js +0 -67
- package/scripts/test-scripts/test-eslint-rules.js +0 -146
- package/scripts/test-scripts/test-real-world.js +0 -44
- package/scripts/test-scripts/test-rules-on-real-projects.js +0 -86
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C003 Heuristic Analyzer: No vague abbreviations
|
|
3
|
+
* Detects unclear variable names and abbreviations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class C003NoVagueAbbreviations {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.options = {
|
|
9
|
+
allowedSingleChar: new Set(options.allowedSingleChar || [
|
|
10
|
+
'i', 'j', 'k', 'x', 'y', 'z', 'n', 'm', 't', 'v', 'r', 'e', 'p', 'w', 'h'
|
|
11
|
+
]),
|
|
12
|
+
allowedAbbreviations: new Set(options.allowedAbbreviations || [
|
|
13
|
+
// Technical abbreviations from user feedback
|
|
14
|
+
'id', 'url', 'uri', 'api', 'ui', 'db', 'fs', 'os', 'io', 'ai', 'ml', 'qa', 'ci', 'cd', 'pr',
|
|
15
|
+
'jwt', 'uuid', 'json', 'xml', 'html', 'css', 'sql', 'http', 'https', 'ftp', 'smtp', 'tcp', 'udp',
|
|
16
|
+
'pdf', 'csv', 'tsv', 'png', 'jpg', 'gif', 'svg', 'mp4', 'mp3', 'zip', 'tar',
|
|
17
|
+
'js', 'ts', 'py', 'rb', 'go', 'rs', 'kt', 'cs', 'vb', 'sh',
|
|
18
|
+
'dom', 'xhr', 'spa', 'pwa', 'seo', 'cdn', 'ssl', 'tls',
|
|
19
|
+
'orm', 'ddl', 'dml', 'etl', 'olap', 'oltp',
|
|
20
|
+
'kpi', 'roi', 'sla', 'poc', 'mvp', 'b2b', 'b2c', 'crm', 'erp',
|
|
21
|
+
'jsx', 'tsx', 'vue', 'scss', 'less',
|
|
22
|
+
'it', 'ut', 'e2e',
|
|
23
|
+
// Common development terms
|
|
24
|
+
'config', 'env', 'app', 'btn', 'img', 'src', 'dest', 'req', 'res', 'ctx',
|
|
25
|
+
'min', 'max', 'len', 'num', 'str', 'auth', 'log', 'err', 'msg', 'key',
|
|
26
|
+
// Add the variants from user feedback cases
|
|
27
|
+
'qa1', 'ci1', 'tsx2', 'it2', 'qa2', 'ci2',
|
|
28
|
+
// Common test/function context terms
|
|
29
|
+
'value', 'result', 'response', 'request', 'data', 'item', 'element', 'object',
|
|
30
|
+
// Common programming terms
|
|
31
|
+
'async', 'length', 'ms'
|
|
32
|
+
]),
|
|
33
|
+
minLength: options.minLength || 2,
|
|
34
|
+
strictMode: options.strictMode || false
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Patterns for suspicious abbreviations
|
|
38
|
+
this.suspiciousPatterns = [
|
|
39
|
+
/^[a-z]{1,2}[0-9]*$/, // e.g., 'u', 'usr', 'n1', 'v2'
|
|
40
|
+
/^[a-z]*[aeiou]*[bcdfghjklmnpqrstvwxyz]{4,}$/, // too many consonants
|
|
41
|
+
/^(tmp|temp|val|var|data|info|item|elem|el|obj|arr)([A-Z0-9].*)?$/, // generic names
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// Unclear names that should be avoided in most contexts (but not in specific contexts)
|
|
45
|
+
this.unclearNames = new Set([
|
|
46
|
+
'stuff', 'thing', 'something', 'anything', 'everything',
|
|
47
|
+
'flag', 'check', 'test', 'validate', 'process', 'handle',
|
|
48
|
+
'obj', 'arg', 'val', 'fn'
|
|
49
|
+
// Removed: 'data', 'info', 'element', 'object', 'value', 'result', 'response', 'request', 'temp', 'tmp', 'variable'
|
|
50
|
+
// These are often acceptable in specific contexts
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
// Context patterns where single letters are acceptable
|
|
54
|
+
this.loopPatterns = [
|
|
55
|
+
/for\s*\(\s*(?:let|const|var)\s+([a-z])\s*[=;]/i,
|
|
56
|
+
/\.forEach\s*\(\s*(?:\([^)]*\)|\w+)\s*=>/,
|
|
57
|
+
/\.map\s*\(\s*(?:\([^)]*\)|\w+)\s*=>/,
|
|
58
|
+
/\.filter\s*\(\s*(?:\([^)]*\)|\w+)\s*=>/
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if variable is in loop context
|
|
64
|
+
*/
|
|
65
|
+
isLoopContext(content, variableName, line) {
|
|
66
|
+
const lines = content.split('\n');
|
|
67
|
+
const currentLine = lines[line - 1] || '';
|
|
68
|
+
const prevLine = lines[line - 2] || '';
|
|
69
|
+
const nextLine = lines[line] || '';
|
|
70
|
+
|
|
71
|
+
const contextLines = [prevLine, currentLine, nextLine].join(' ');
|
|
72
|
+
|
|
73
|
+
// Check for for loops
|
|
74
|
+
if (/for\s*\(\s*(?:let|const|var)\s+\w+/i.test(contextLines)) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check for array methods
|
|
79
|
+
if (/\.(forEach|map|filter|reduce|some|every|find|findIndex)\s*\(/i.test(contextLines)) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if variable is in math/algorithm context
|
|
88
|
+
*/
|
|
89
|
+
isMathContext(content, variableName, line) {
|
|
90
|
+
const lines = content.split('\n');
|
|
91
|
+
const currentLine = lines[line - 1] || '';
|
|
92
|
+
|
|
93
|
+
// Math constants like a, b, c in equations
|
|
94
|
+
if (/const\s+[a-z]\s*=\s*\d+.*[a-z]\s*=\s*\d+/.test(currentLine)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Math operations
|
|
99
|
+
if (/[+\-*/=]\s*\w+|\w+\s*[+\-*/=]/.test(currentLine)) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if variable is in function parameter context
|
|
108
|
+
*/
|
|
109
|
+
isFunctionParameter(content, variableName, line) {
|
|
110
|
+
const lines = content.split('\n');
|
|
111
|
+
const currentLine = lines[line - 1] || '';
|
|
112
|
+
|
|
113
|
+
// Check if we're in a function parameter list
|
|
114
|
+
return /function.*\(.*\w+.*\)/.test(currentLine) ||
|
|
115
|
+
/\(.*\w+.*\)\s*=>/.test(currentLine) ||
|
|
116
|
+
/\w+\s*=>\s*/.test(currentLine);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check if variable is coordinate-like
|
|
121
|
+
*/
|
|
122
|
+
isCoordinate(variableName) {
|
|
123
|
+
return /^[xyz](\d+)?$/i.test(variableName) ||
|
|
124
|
+
/^(width|height|top|left|right|bottom)$/i.test(variableName) ||
|
|
125
|
+
/^[wh](\d+)?$/i.test(variableName);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if variable is in math/algorithm context
|
|
130
|
+
*/
|
|
131
|
+
isMathContext(content, variableName, line) {
|
|
132
|
+
const lines = content.split('\n');
|
|
133
|
+
const currentLine = lines[line - 1] || '';
|
|
134
|
+
|
|
135
|
+
// Math constants like a, b, c in equations
|
|
136
|
+
if (/const\s+[a-z]\s*=\s*\d+.*[a-z]\s*=\s*\d+/.test(currentLine)) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Math operations
|
|
141
|
+
if (/[+\-*/=]\s*\w+|\w+\s*[+\-*/=]/.test(currentLine)) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if variable is in function parameter context
|
|
150
|
+
*/
|
|
151
|
+
isFunctionParameter(content, variableName, line) {
|
|
152
|
+
const lines = content.split('\n');
|
|
153
|
+
const currentLine = lines[line - 1] || '';
|
|
154
|
+
|
|
155
|
+
// Check if we're in a function parameter list
|
|
156
|
+
return /function.*\(.*\w+.*\)/.test(currentLine) ||
|
|
157
|
+
/\(.*\w+.*\)\s*=>/.test(currentLine) ||
|
|
158
|
+
/\w+\s*=>\s*/.test(currentLine);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Check if variable is in callback/iteration context
|
|
163
|
+
*/
|
|
164
|
+
isCallbackContext(content, variableName, line) {
|
|
165
|
+
const lines = content.split('\n');
|
|
166
|
+
const currentLine = lines[line - 1] || '';
|
|
167
|
+
|
|
168
|
+
// Array methods with callback
|
|
169
|
+
return /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(currentLine);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Check if variable is in comparison/sorting context
|
|
174
|
+
*/
|
|
175
|
+
isComparisonContext(content, variableName, line) {
|
|
176
|
+
const lines = content.split('\n');
|
|
177
|
+
const currentLine = lines[line - 1] || '';
|
|
178
|
+
const prevLine = lines[line - 2] || '';
|
|
179
|
+
const nextLine = lines[line] || '';
|
|
180
|
+
|
|
181
|
+
const contextLines = [prevLine, currentLine, nextLine].join(' ');
|
|
182
|
+
|
|
183
|
+
// Comparison/sorting patterns
|
|
184
|
+
return /\.sort\s*\(/.test(contextLines) ||
|
|
185
|
+
/compare\s*\(/.test(contextLines) ||
|
|
186
|
+
/\w+\s*[<>=]+\s*\w+/.test(contextLines);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Check if variable is in event context
|
|
191
|
+
*/
|
|
192
|
+
isEventContext(content, variableName, line) {
|
|
193
|
+
const lines = content.split('\n');
|
|
194
|
+
const contextLines = lines.slice(Math.max(0, line - 2), line + 1).join(' ');
|
|
195
|
+
|
|
196
|
+
return /(?:event|evt|e)\s*[:=]|addEventListener|onClick|onSubmit|handler/i.test(contextLines);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Check if variable is in destructuring context
|
|
201
|
+
*/
|
|
202
|
+
isDestructuring(content, variableName, line) {
|
|
203
|
+
const lines = content.split('\n');
|
|
204
|
+
const currentLine = lines[line - 1] || '';
|
|
205
|
+
|
|
206
|
+
// Check for object destructuring
|
|
207
|
+
if (/const\s*{[^}]*}\s*=/.test(currentLine) || /{\s*\w+[^}]*}/.test(currentLine)) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check for array destructuring
|
|
212
|
+
if (/const\s*\[[^\]]*\]\s*=/.test(currentLine) || /\[\s*\w+[^\]]*\]/.test(currentLine)) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Analyze variable names in files
|
|
221
|
+
* @param {string[]} files - Array of file paths to analyze
|
|
222
|
+
* @param {string} language - Programming language
|
|
223
|
+
* @param {Object} options - Analysis options
|
|
224
|
+
* @returns {Object[]} Array of violations
|
|
225
|
+
*/
|
|
226
|
+
async analyze(files, language, options) {
|
|
227
|
+
const violations = [];
|
|
228
|
+
|
|
229
|
+
for (const filePath of files) {
|
|
230
|
+
try {
|
|
231
|
+
// Read file content
|
|
232
|
+
const fs = require('fs');
|
|
233
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
234
|
+
|
|
235
|
+
// Analyze file content
|
|
236
|
+
const fileViolations = this.analyzeContent(content, filePath);
|
|
237
|
+
violations.push(...fileViolations);
|
|
238
|
+
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error(`Error analyzing file ${filePath}:`, error.message);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return violations;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Analyze variable names in content
|
|
249
|
+
*/
|
|
250
|
+
analyzeContent(content, filePath) {
|
|
251
|
+
// Ensure content is a string
|
|
252
|
+
if (typeof content !== 'string') {
|
|
253
|
+
console.error('Content is not a string:', typeof content);
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const violations = [];
|
|
258
|
+
const lines = content.split('\n');
|
|
259
|
+
|
|
260
|
+
lines.forEach((line, index) => {
|
|
261
|
+
const lineNumber = index + 1;
|
|
262
|
+
|
|
263
|
+
// Match variable declarations
|
|
264
|
+
const patterns = [
|
|
265
|
+
// const/let/var declarations
|
|
266
|
+
/(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[=:]/g,
|
|
267
|
+
// Function parameters in function declarations
|
|
268
|
+
/function\s+\w*\s*\(\s*([^)]+)\s*\)/g,
|
|
269
|
+
// Arrow function parameters
|
|
270
|
+
/\(\s*([^)]+)\s*\)\s*=>/g,
|
|
271
|
+
// Single parameter arrow functions
|
|
272
|
+
/([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/g,
|
|
273
|
+
];
|
|
274
|
+
|
|
275
|
+
patterns.forEach(pattern => {
|
|
276
|
+
let match;
|
|
277
|
+
while ((match = pattern.exec(line)) !== null) {
|
|
278
|
+
let variableNames = [];
|
|
279
|
+
|
|
280
|
+
if (match[1]) {
|
|
281
|
+
// Handle parameter lists
|
|
282
|
+
if (match[1].includes(',')) {
|
|
283
|
+
variableNames = match[1].split(',')
|
|
284
|
+
.map(param => param.trim().split(/[:\s=]/)[0].trim())
|
|
285
|
+
.filter(name => name && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name));
|
|
286
|
+
} else {
|
|
287
|
+
const cleanName = match[1].trim().split(/[:\s=]/)[0].trim();
|
|
288
|
+
if (cleanName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(cleanName)) {
|
|
289
|
+
variableNames = [cleanName];
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
variableNames.forEach(variableName => {
|
|
295
|
+
const violation = this.checkVariableName(variableName, content, lineNumber, match.index);
|
|
296
|
+
if (violation) {
|
|
297
|
+
violations.push({
|
|
298
|
+
rule: 'C003',
|
|
299
|
+
severity: 'warning',
|
|
300
|
+
message: violation.message,
|
|
301
|
+
line: lineNumber,
|
|
302
|
+
column: match.index + 1,
|
|
303
|
+
filePath: filePath
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return violations;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Check individual variable name
|
|
316
|
+
*/
|
|
317
|
+
checkVariableName(variableName, content, line, column) {
|
|
318
|
+
// Skip if empty or invalid
|
|
319
|
+
if (!variableName || typeof variableName !== 'string') {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Skip TypeScript/React specific names
|
|
324
|
+
if (variableName.startsWith('_') || variableName.includes('$')) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Skip common React patterns
|
|
329
|
+
if (/^(useState|useEffect|useCallback|useMemo|useRef|useContext)$/.test(variableName)) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Check single character variables
|
|
334
|
+
if (variableName.length === 1) {
|
|
335
|
+
const lowerName = variableName.toLowerCase();
|
|
336
|
+
|
|
337
|
+
// Allow if in allowed single char list
|
|
338
|
+
if (this.options.allowedSingleChar.has(lowerName)) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Allow if in loop context
|
|
343
|
+
if (this.isLoopContext(content, variableName, line)) {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Allow coordinates
|
|
348
|
+
if (this.isCoordinate(variableName)) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Allow in event context
|
|
353
|
+
if (lowerName === 'e' && this.isEventContext(content, variableName, line)) {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Allow in math context (a, b, c coefficients)
|
|
358
|
+
if (['a', 'b', 'c'].includes(lowerName) && this.isMathContext(content, variableName, line)) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Allow a, b in comparison/sorting context
|
|
363
|
+
if (['a', 'b'].includes(lowerName) && this.isComparisonContext(content, variableName, line)) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
message: `Variable '${variableName}' is only 1 character long. Use descriptive names (except for counters like i, j, k).`
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const lowerName = variableName.toLowerCase();
|
|
373
|
+
|
|
374
|
+
// Check if it's an allowed abbreviation
|
|
375
|
+
if (this.options.allowedAbbreviations.has(lowerName)) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Allow in destructuring context
|
|
380
|
+
if (this.isDestructuring(content, variableName, line)) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Allow in callback/function parameter context for common terms
|
|
385
|
+
if (['item', 'data', 'element'].includes(lowerName) &&
|
|
386
|
+
(this.isCallbackContext(content, variableName, line) ||
|
|
387
|
+
this.isFunctionParameter(content, variableName, line))) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Check for minimum length
|
|
392
|
+
if (variableName.length < this.options.minLength) {
|
|
393
|
+
return {
|
|
394
|
+
message: `Variable '${variableName}' is too short (${variableName.length} characters). Use descriptive names with at least ${this.options.minLength} characters.`
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Check for suspicious patterns
|
|
399
|
+
for (const pattern of this.suspiciousPatterns) {
|
|
400
|
+
if (pattern.test(lowerName)) {
|
|
401
|
+
return {
|
|
402
|
+
message: `Variable '${variableName}' appears to be an unclear abbreviation. Use full descriptive names.`
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Check for unclear names
|
|
408
|
+
if (this.unclearNames.has(lowerName)) {
|
|
409
|
+
return {
|
|
410
|
+
message: `Variable '${variableName}' is unclear or ambiguous. Use more specific descriptive names.`
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
module.exports = C003NoVagueAbbreviations;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "C003",
|
|
3
|
+
"name": "no-vague-abbreviations",
|
|
4
|
+
"description": "Clear variable names, avoid arbitrary abbreviations",
|
|
5
|
+
"category": "naming",
|
|
6
|
+
"severity": "warning",
|
|
7
|
+
"language": ["typescript", "javascript"],
|
|
8
|
+
"engine": "heuristic",
|
|
9
|
+
"meta": {
|
|
10
|
+
"docs": {
|
|
11
|
+
"description": "Ensure clear, understandable variable names without arbitrary abbreviations",
|
|
12
|
+
"url": "https://github.com/sun-asterisk/engineer-excellence/blob/main/docs/rules/C003.md"
|
|
13
|
+
},
|
|
14
|
+
"schema": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {
|
|
17
|
+
"allowedSingleChar": {
|
|
18
|
+
"type": "array",
|
|
19
|
+
"items": { "type": "string" },
|
|
20
|
+
"description": "Single character variables that are allowed"
|
|
21
|
+
},
|
|
22
|
+
"allowedAbbreviations": {
|
|
23
|
+
"type": "array",
|
|
24
|
+
"items": { "type": "string" },
|
|
25
|
+
"description": "Common abbreviations that are allowed"
|
|
26
|
+
},
|
|
27
|
+
"minLength": {
|
|
28
|
+
"type": "integer",
|
|
29
|
+
"minimum": 1,
|
|
30
|
+
"description": "Minimum variable name length"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|