@aiready/consistency 0.16.2 → 0.16.3
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/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-test.log +12 -10
- package/dist/chunk-AASFXGUR.mjs +1622 -0
- package/dist/chunk-AR7DIZLP.mjs +827 -0
- package/dist/chunk-BMILMNKJ.mjs +1633 -0
- package/dist/chunk-HJCP36VW.mjs +821 -0
- package/dist/chunk-QOIPVP6P.mjs +1607 -0
- package/dist/chunk-RMEQWG52.mjs +1633 -0
- package/dist/chunk-XVW5DKJQ.mjs +1619 -0
- package/dist/cli.js +277 -1019
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +320 -1242
- package/dist/index.mjs +58 -230
- package/package.json +2 -2
- package/src/__tests__/contract.test.ts +18 -2
- package/src/analyzer.ts +49 -28
- package/src/analyzers/naming-ast.ts +188 -328
- package/src/analyzers/naming.ts +52 -365
- package/src/analyzers/patterns.ts +51 -228
- package/src/types.ts +10 -10
- package/src/utils/context-detector.ts +23 -10
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TSESTree } from '@typescript-eslint/typescript-estree';
|
|
2
|
+
import { Severity } from '@aiready/core';
|
|
2
3
|
import type { NamingIssue } from '../types';
|
|
3
4
|
import {
|
|
4
5
|
parseFile,
|
|
@@ -8,369 +9,241 @@ import {
|
|
|
8
9
|
isCoverageContext,
|
|
9
10
|
isLoopStatement,
|
|
10
11
|
} from '../utils/ast-parser';
|
|
11
|
-
import { ScopeTracker } from '../utils/scope-tracker';
|
|
12
12
|
import {
|
|
13
13
|
buildCodeContext,
|
|
14
|
-
adjustSeverity,
|
|
15
14
|
isAcceptableInContext,
|
|
16
|
-
|
|
15
|
+
adjustSeverity,
|
|
17
16
|
} from '../utils/context-detector';
|
|
18
|
-
import { loadNamingConfig } from '../utils/config-loader';
|
|
19
17
|
|
|
20
18
|
/**
|
|
21
|
-
*
|
|
22
|
-
* Only supports TypeScript/JavaScript files (.ts, .tsx, .js, .jsx)
|
|
19
|
+
* Advanced naming analyzer using TypeScript AST
|
|
23
20
|
*/
|
|
24
21
|
export async function analyzeNamingAST(
|
|
25
|
-
|
|
22
|
+
filePaths: string[]
|
|
26
23
|
): Promise<NamingIssue[]> {
|
|
27
|
-
const
|
|
24
|
+
const allIssues: NamingIssue[] = [];
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
const { allAbbreviations, allShortWords, disabledChecks } =
|
|
31
|
-
await loadNamingConfig(files);
|
|
32
|
-
|
|
33
|
-
// Filter to only JS/TS files that the TypeScript parser can handle
|
|
34
|
-
const supportedFiles = files.filter((file) =>
|
|
35
|
-
/\.(js|jsx|ts|tsx)$/i.test(file)
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
for (const file of supportedFiles) {
|
|
26
|
+
for (const filePath of filePaths) {
|
|
39
27
|
try {
|
|
40
|
-
const ast = parseFile(
|
|
41
|
-
if (!ast) continue;
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
disabledChecks
|
|
49
|
-
);
|
|
50
|
-
issues.push(...fileIssues);
|
|
51
|
-
} catch (error) {
|
|
52
|
-
console.warn(`Skipping ${file} due to parse error:`, error);
|
|
28
|
+
const ast = parseFile(filePath);
|
|
29
|
+
if (!ast) continue;
|
|
30
|
+
|
|
31
|
+
const context = buildCodeContext(filePath, ast);
|
|
32
|
+
const issues = analyzeIdentifiers(ast, filePath, context);
|
|
33
|
+
allIssues.push(...issues);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
void err;
|
|
53
36
|
}
|
|
54
37
|
}
|
|
55
38
|
|
|
56
|
-
return
|
|
39
|
+
return allIssues;
|
|
57
40
|
}
|
|
58
41
|
|
|
59
|
-
|
|
60
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Traverse AST and find naming issues in identifiers
|
|
44
|
+
*/
|
|
45
|
+
function analyzeIdentifiers(
|
|
61
46
|
ast: TSESTree.Program,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
disabledChecks: Set<string>
|
|
47
|
+
filePath: string,
|
|
48
|
+
context: any
|
|
65
49
|
): NamingIssue[] {
|
|
66
50
|
const issues: NamingIssue[] = [];
|
|
67
|
-
const scopeTracker = new ScopeTracker(
|
|
68
|
-
const context = buildCodeContext(file, ast);
|
|
69
|
-
const ancestors: TSESTree.Node[] = [];
|
|
51
|
+
const scopeTracker = new ScopeTracker();
|
|
70
52
|
|
|
71
|
-
// First pass: Build scope tree and track all variables
|
|
72
53
|
traverseAST(ast, {
|
|
73
|
-
enter: (node
|
|
74
|
-
|
|
54
|
+
enter: (node) => {
|
|
55
|
+
// 1. Variable Declarations
|
|
56
|
+
if (node.type === 'VariableDeclarator' && node.id.type === 'Identifier') {
|
|
57
|
+
const isParameter = false;
|
|
58
|
+
const isLoopVariable = isLoopStatement(node.parent?.parent);
|
|
59
|
+
scopeTracker.declareVariable(
|
|
60
|
+
node.id.name,
|
|
61
|
+
node.id,
|
|
62
|
+
getLineNumber(node.id),
|
|
63
|
+
{ isParameter, isLoopVariable }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
75
66
|
|
|
76
|
-
//
|
|
67
|
+
// 2. Function Parameters
|
|
77
68
|
if (
|
|
78
69
|
node.type === 'FunctionDeclaration' ||
|
|
79
70
|
node.type === 'FunctionExpression' ||
|
|
80
71
|
node.type === 'ArrowFunctionExpression'
|
|
81
72
|
) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
isParameter: true,
|
|
94
|
-
}
|
|
95
|
-
);
|
|
96
|
-
} else if (
|
|
97
|
-
param.type === 'ObjectPattern' ||
|
|
98
|
-
param.type === 'ArrayPattern'
|
|
99
|
-
) {
|
|
100
|
-
// Handle destructured parameters
|
|
101
|
-
extractIdentifiersFromPattern(param, scopeTracker, true);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
} else if (node.type === 'BlockStatement') {
|
|
106
|
-
scopeTracker.enterScope('block', node);
|
|
107
|
-
} else if (isLoopStatement(node)) {
|
|
108
|
-
scopeTracker.enterScope('loop', node);
|
|
109
|
-
} else if (node.type === 'ClassDeclaration') {
|
|
110
|
-
scopeTracker.enterScope('class', node);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Track variable declarations
|
|
114
|
-
if (node.type === 'VariableDeclarator') {
|
|
115
|
-
if (node.id.type === 'Identifier') {
|
|
116
|
-
void isCoverageContext(node, ancestors);
|
|
117
|
-
scopeTracker.declareVariable(
|
|
118
|
-
node.id.name,
|
|
119
|
-
node.id,
|
|
120
|
-
getLineNumber(node.id),
|
|
121
|
-
{
|
|
122
|
-
type:
|
|
123
|
-
'typeAnnotation' in node.id
|
|
124
|
-
? (node.id as any).typeAnnotation
|
|
125
|
-
: null,
|
|
126
|
-
isDestructured: false,
|
|
127
|
-
isLoopVariable: scopeTracker.getCurrentScopeType() === 'loop',
|
|
128
|
-
}
|
|
129
|
-
);
|
|
130
|
-
} else if (
|
|
131
|
-
node.id.type === 'ObjectPattern' ||
|
|
132
|
-
node.id.type === 'ArrayPattern'
|
|
133
|
-
) {
|
|
134
|
-
extractIdentifiersFromPattern(
|
|
135
|
-
node.id,
|
|
136
|
-
scopeTracker,
|
|
137
|
-
false,
|
|
138
|
-
ancestors
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Track references
|
|
144
|
-
if (node.type === 'Identifier' && parent) {
|
|
145
|
-
// Only count as reference if it's not a declaration
|
|
146
|
-
if (parent.type !== 'VariableDeclarator' || parent.id !== node) {
|
|
147
|
-
if (parent.type !== 'FunctionDeclaration' || parent.id !== node) {
|
|
148
|
-
scopeTracker.addReference(node.name, node);
|
|
73
|
+
node.params.forEach((param) => {
|
|
74
|
+
if (param.type === 'Identifier') {
|
|
75
|
+
scopeTracker.declareVariable(
|
|
76
|
+
param.name,
|
|
77
|
+
param,
|
|
78
|
+
getLineNumber(param),
|
|
79
|
+
{ isParameter: true }
|
|
80
|
+
);
|
|
81
|
+
} else if (param.type === 'ObjectPattern') {
|
|
82
|
+
// Handle destructured parameters: { id, name }
|
|
83
|
+
extractDestructuredIdentifiers(param, true, scopeTracker);
|
|
149
84
|
}
|
|
150
|
-
}
|
|
85
|
+
});
|
|
151
86
|
}
|
|
152
|
-
},
|
|
153
|
-
leave: (node) => {
|
|
154
|
-
ancestors.pop();
|
|
155
87
|
|
|
156
|
-
//
|
|
88
|
+
// 3. Class/Interface/Type names
|
|
157
89
|
if (
|
|
158
|
-
node.type === '
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
node.
|
|
162
|
-
isLoopStatement(node) ||
|
|
163
|
-
node.type === 'ClassDeclaration'
|
|
90
|
+
(node.type === 'ClassDeclaration' ||
|
|
91
|
+
node.type === 'TSInterfaceDeclaration' ||
|
|
92
|
+
node.type === 'TSTypeAliasDeclaration') &&
|
|
93
|
+
node.id
|
|
164
94
|
) {
|
|
165
|
-
|
|
95
|
+
checkNamingConvention(
|
|
96
|
+
node.id.name,
|
|
97
|
+
'PascalCase',
|
|
98
|
+
node.id,
|
|
99
|
+
filePath,
|
|
100
|
+
issues,
|
|
101
|
+
context
|
|
102
|
+
);
|
|
166
103
|
}
|
|
167
104
|
},
|
|
168
105
|
});
|
|
169
106
|
|
|
170
|
-
//
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
for (const varInfo of allVariables) {
|
|
174
|
-
const name = varInfo.name;
|
|
175
|
-
const line = varInfo.declarationLine;
|
|
176
|
-
|
|
177
|
-
// Skip if checks are disabled
|
|
178
|
-
if (disabledChecks.has('single-letter') && name.length === 1) continue;
|
|
179
|
-
if (disabledChecks.has('abbreviation') && name.length <= 3) continue;
|
|
180
|
-
|
|
181
|
-
// Check coverage context
|
|
182
|
-
const isInCoverage =
|
|
183
|
-
['s', 'b', 'f', 'l'].includes(name) && varInfo.isDestructured;
|
|
184
|
-
if (isInCoverage) continue;
|
|
185
|
-
|
|
186
|
-
// Check if acceptable in context
|
|
187
|
-
const functionComplexity =
|
|
188
|
-
varInfo.node.type === 'Identifier' && 'parent' in varInfo.node
|
|
189
|
-
? calculateComplexity(varInfo.node as any)
|
|
190
|
-
: context.complexity;
|
|
191
|
-
|
|
192
|
-
if (
|
|
193
|
-
isAcceptableInContext(name, context, {
|
|
194
|
-
isLoopVariable: varInfo.isLoopVariable || allAbbreviations.has(name),
|
|
195
|
-
isParameter: varInfo.isParameter,
|
|
196
|
-
isDestructured: varInfo.isDestructured,
|
|
197
|
-
complexity: functionComplexity,
|
|
198
|
-
})
|
|
199
|
-
) {
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Single letter check
|
|
204
|
-
if (
|
|
205
|
-
name.length === 1 &&
|
|
206
|
-
!allAbbreviations.has(name) &&
|
|
207
|
-
!allShortWords.has(name)
|
|
208
|
-
) {
|
|
209
|
-
// Check if short-lived
|
|
210
|
-
const isShortLived = scopeTracker.isShortLived(varInfo, 5);
|
|
211
|
-
if (!isShortLived) {
|
|
212
|
-
issues.push({
|
|
213
|
-
file,
|
|
214
|
-
line,
|
|
215
|
-
type: 'poor-naming',
|
|
216
|
-
identifier: name,
|
|
217
|
-
severity: adjustSeverity('minor', context, 'poor-naming'),
|
|
218
|
-
suggestion: `Use descriptive variable name instead of single letter '${name}'`,
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Abbreviation check (2-3 letters)
|
|
225
|
-
if (name.length >= 2 && name.length <= 3) {
|
|
226
|
-
if (
|
|
227
|
-
!allShortWords.has(name.toLowerCase()) &&
|
|
228
|
-
!allAbbreviations.has(name.toLowerCase())
|
|
229
|
-
) {
|
|
230
|
-
// Check if short-lived for abbreviations too
|
|
231
|
-
const isShortLived = scopeTracker.isShortLived(varInfo, 5);
|
|
232
|
-
if (!isShortLived) {
|
|
233
|
-
issues.push({
|
|
234
|
-
file,
|
|
235
|
-
line,
|
|
236
|
-
type: 'abbreviation',
|
|
237
|
-
identifier: name,
|
|
238
|
-
severity: adjustSeverity('info', context, 'abbreviation'),
|
|
239
|
-
suggestion: `Consider using full word instead of abbreviation '${name}'`,
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Snake_case check for TypeScript/JavaScript
|
|
247
|
-
if (
|
|
248
|
-
!disabledChecks.has('convention-mix') &&
|
|
249
|
-
file.match(/\.(ts|tsx|js|jsx)$/)
|
|
250
|
-
) {
|
|
251
|
-
if (
|
|
252
|
-
name.includes('_') &&
|
|
253
|
-
!name.startsWith('_') &&
|
|
254
|
-
name.toLowerCase() === name
|
|
255
|
-
) {
|
|
256
|
-
const camelCase = name.replace(/_([a-z])/g, (_, letter) =>
|
|
257
|
-
letter.toUpperCase()
|
|
258
|
-
);
|
|
259
|
-
issues.push({
|
|
260
|
-
file,
|
|
261
|
-
line,
|
|
262
|
-
type: 'convention-mix',
|
|
263
|
-
identifier: name,
|
|
264
|
-
severity: adjustSeverity('minor', context, 'convention-mix'),
|
|
265
|
-
suggestion: `Use camelCase '${camelCase}' instead of snake_case in TypeScript/JavaScript`,
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
}
|
|
107
|
+
// Check all collected variables
|
|
108
|
+
for (const varInfo of scopeTracker.getVariables()) {
|
|
109
|
+
checkVariableNaming(varInfo, filePath, issues, context);
|
|
269
110
|
}
|
|
270
111
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
traverseAST(ast, {
|
|
274
|
-
enter: (node) => {
|
|
275
|
-
if (
|
|
276
|
-
node.type === 'FunctionDeclaration' ||
|
|
277
|
-
node.type === 'MethodDefinition'
|
|
278
|
-
) {
|
|
279
|
-
const name = getFunctionName(node);
|
|
280
|
-
if (!name) return;
|
|
281
|
-
|
|
282
|
-
const line = getLineNumber(node);
|
|
112
|
+
return issues;
|
|
113
|
+
}
|
|
283
114
|
|
|
284
|
-
|
|
285
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Check if a name follows a specific convention
|
|
117
|
+
*/
|
|
118
|
+
function checkNamingConvention(
|
|
119
|
+
name: string,
|
|
120
|
+
convention: 'camelCase' | 'PascalCase' | 'UPPER_CASE',
|
|
121
|
+
node: TSESTree.Node,
|
|
122
|
+
file: string,
|
|
123
|
+
issues: NamingIssue[],
|
|
124
|
+
context: any
|
|
125
|
+
) {
|
|
126
|
+
let isValid = true;
|
|
127
|
+
if (convention === 'PascalCase') {
|
|
128
|
+
isValid = /^[A-Z][a-zA-Z0-9]*$/.test(name);
|
|
129
|
+
} else if (convention === 'camelCase') {
|
|
130
|
+
isValid = /^[a-z][a-zA-Z0-9]*$/.test(name);
|
|
131
|
+
} else if (convention === 'UPPER_CASE') {
|
|
132
|
+
isValid = /^[A-Z][A-Z0-9_]*$/.test(name);
|
|
133
|
+
}
|
|
286
134
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
135
|
+
if (!isValid) {
|
|
136
|
+
const severity = adjustSeverity(Severity.Info, context, 'convention-mix');
|
|
137
|
+
issues.push({
|
|
138
|
+
file,
|
|
139
|
+
line: getLineNumber(node),
|
|
140
|
+
type: 'convention-mix',
|
|
141
|
+
identifier: name,
|
|
142
|
+
severity,
|
|
143
|
+
suggestion: `Follow ${convention} for this identifier`,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
291
147
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
].includes(name);
|
|
308
|
-
const isLanguageKeyword = [
|
|
309
|
-
'constructor',
|
|
310
|
-
'toString',
|
|
311
|
-
'valueOf',
|
|
312
|
-
'toJSON',
|
|
313
|
-
].includes(name);
|
|
314
|
-
const isFrameworkPattern = name.match(
|
|
315
|
-
/^(goto|fill|click|select|submit|wait|expect)\w*/
|
|
316
|
-
); // Page Object Model, test framework patterns
|
|
148
|
+
/**
|
|
149
|
+
* Advanced variable naming checks
|
|
150
|
+
*/
|
|
151
|
+
function checkVariableNaming(
|
|
152
|
+
varInfo: any,
|
|
153
|
+
file: string,
|
|
154
|
+
issues: NamingIssue[],
|
|
155
|
+
context: any
|
|
156
|
+
) {
|
|
157
|
+
const { name, node, line, options } = varInfo;
|
|
158
|
+
|
|
159
|
+
// Skip very common small names if they are in acceptable context
|
|
160
|
+
if (isAcceptableInContext(name, context, options)) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
317
163
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
164
|
+
// 1. Single letter names
|
|
165
|
+
if (name.length === 1 && !options.isLoopVariable) {
|
|
166
|
+
const severity = adjustSeverity(Severity.Minor, context, 'poor-naming');
|
|
167
|
+
issues.push({
|
|
168
|
+
file,
|
|
169
|
+
line,
|
|
170
|
+
type: 'poor-naming',
|
|
171
|
+
identifier: name,
|
|
172
|
+
severity,
|
|
173
|
+
suggestion: 'Use a more descriptive name than a single letter',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
326
176
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
177
|
+
// 2. Vague names
|
|
178
|
+
const vagueNames = [
|
|
179
|
+
'data',
|
|
180
|
+
'info',
|
|
181
|
+
'item',
|
|
182
|
+
'obj',
|
|
183
|
+
'val',
|
|
184
|
+
'tmp',
|
|
185
|
+
'temp',
|
|
186
|
+
'thing',
|
|
187
|
+
'stuff',
|
|
188
|
+
];
|
|
189
|
+
if (vagueNames.includes(name.toLowerCase())) {
|
|
190
|
+
const severity = adjustSeverity(Severity.Minor, context, 'poor-naming');
|
|
191
|
+
issues.push({
|
|
192
|
+
file,
|
|
193
|
+
line,
|
|
194
|
+
type: 'poor-naming',
|
|
195
|
+
identifier: name,
|
|
196
|
+
severity,
|
|
197
|
+
suggestion: `Avoid vague names like '${name}'. What does this data represent?`,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
330
200
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
line,
|
|
347
|
-
type: 'unclear',
|
|
348
|
-
identifier: name,
|
|
349
|
-
severity: adjustSeverity('info', context, 'unclear'),
|
|
350
|
-
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`,
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
},
|
|
201
|
+
// 3. Abbreviations
|
|
202
|
+
if (
|
|
203
|
+
name.length > 1 &&
|
|
204
|
+
name.length <= 3 &&
|
|
205
|
+
!options.isLoopVariable &&
|
|
206
|
+
!isCommonAbbreviation(name)
|
|
207
|
+
) {
|
|
208
|
+
const severity = adjustSeverity(Severity.Info, context, 'abbreviation');
|
|
209
|
+
issues.push({
|
|
210
|
+
file,
|
|
211
|
+
line,
|
|
212
|
+
type: 'abbreviation',
|
|
213
|
+
identifier: name,
|
|
214
|
+
severity,
|
|
215
|
+
suggestion: 'Avoid non-standard abbreviations',
|
|
355
216
|
});
|
|
356
217
|
}
|
|
218
|
+
}
|
|
357
219
|
|
|
358
|
-
|
|
220
|
+
function isCommonAbbreviation(name: string): boolean {
|
|
221
|
+
const common = ['id', 'db', 'fs', 'os', 'ip', 'ui', 'ux', 'api', 'env', 'url'];
|
|
222
|
+
return common.includes(name.toLowerCase());
|
|
359
223
|
}
|
|
360
224
|
|
|
361
225
|
/**
|
|
362
|
-
*
|
|
226
|
+
* Simple scope-aware variable tracker
|
|
363
227
|
*/
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
228
|
+
class ScopeTracker {
|
|
229
|
+
private variables: any[] = [];
|
|
230
|
+
|
|
231
|
+
declareVariable(name: string, node: TSESTree.Node, line: number, options = {}) {
|
|
232
|
+
this.variables.push({ name, node, line, options });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
getVariables() {
|
|
236
|
+
return this.variables;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function extractDestructuredIdentifiers(
|
|
241
|
+
node: TSESTree.ObjectPattern | TSESTree.ArrayPattern,
|
|
367
242
|
isParameter: boolean,
|
|
368
|
-
|
|
369
|
-
)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (pattern.type === 'ObjectPattern') {
|
|
373
|
-
for (const prop of pattern.properties) {
|
|
243
|
+
scopeTracker: ScopeTracker
|
|
244
|
+
) {
|
|
245
|
+
if (node.type === 'ObjectPattern') {
|
|
246
|
+
node.properties.forEach((prop) => {
|
|
374
247
|
if (prop.type === 'Property' && prop.value.type === 'Identifier') {
|
|
375
248
|
scopeTracker.declareVariable(
|
|
376
249
|
prop.value.name,
|
|
@@ -381,24 +254,11 @@ function extractIdentifiersFromPattern(
|
|
|
381
254
|
isDestructured: true,
|
|
382
255
|
}
|
|
383
256
|
);
|
|
384
|
-
} else if (
|
|
385
|
-
prop.type === 'RestElement' &&
|
|
386
|
-
prop.argument.type === 'Identifier'
|
|
387
|
-
) {
|
|
388
|
-
scopeTracker.declareVariable(
|
|
389
|
-
prop.argument.name,
|
|
390
|
-
prop.argument,
|
|
391
|
-
getLineNumber(prop.argument),
|
|
392
|
-
{
|
|
393
|
-
isParameter,
|
|
394
|
-
isDestructured: true,
|
|
395
|
-
}
|
|
396
|
-
);
|
|
397
257
|
}
|
|
398
|
-
}
|
|
399
|
-
} else if (
|
|
400
|
-
for (const element of
|
|
401
|
-
if (element
|
|
258
|
+
});
|
|
259
|
+
} else if (node.type === 'ArrayPattern') {
|
|
260
|
+
for (const element of node.elements) {
|
|
261
|
+
if (element?.type === 'Identifier') {
|
|
402
262
|
scopeTracker.declareVariable(
|
|
403
263
|
element.name,
|
|
404
264
|
element,
|