@aiready/consistency 0.8.30 → 0.8.32

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.
@@ -14,11 +14,18 @@ export async function analyzeNaming(files: string[]): Promise<NamingIssue[]> {
14
14
  const issues: NamingIssue[] = [];
15
15
 
16
16
  // Load and merge configuration
17
- const { customAbbreviations, customShortWords, disabledChecks } = await loadNamingConfig(files);
17
+ const { customAbbreviations, customShortWords, disabledChecks } =
18
+ await loadNamingConfig(files);
18
19
 
19
20
  for (const file of files) {
20
21
  const content = await readFileContent(file);
21
- const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
22
+ const fileIssues = analyzeFileNaming(
23
+ file,
24
+ content,
25
+ customAbbreviations,
26
+ customShortWords,
27
+ disabledChecks
28
+ );
22
29
  issues.push(...fileIssues);
23
30
  }
24
31
 
@@ -26,8 +33,8 @@ export async function analyzeNaming(files: string[]): Promise<NamingIssue[]> {
26
33
  }
27
34
 
28
35
  function analyzeFileNaming(
29
- file: string,
30
- content: string,
36
+ file: string,
37
+ content: string,
31
38
  customAbbreviations: Set<string>,
32
39
  customShortWords: Set<string>,
33
40
  disabledChecks: Set<string>
@@ -41,7 +48,10 @@ function analyzeFileNaming(
41
48
  const lines = content.split('\n');
42
49
 
43
50
  // Merge custom sets with defaults
44
- const allAbbreviations = new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
51
+ const allAbbreviations = new Set([
52
+ ...ACCEPTABLE_ABBREVIATIONS,
53
+ ...customAbbreviations,
54
+ ]);
45
55
  const allShortWords = new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
46
56
 
47
57
  /**
@@ -56,10 +66,13 @@ function analyzeFileNaming(
56
66
  /**
57
67
  * Helper: Check if a variable is short-lived (used only within 3-5 lines)
58
68
  */
59
- const isShortLivedVariable = (varName: string, declarationIndex: number): boolean => {
69
+ const isShortLivedVariable = (
70
+ varName: string,
71
+ declarationIndex: number
72
+ ): boolean => {
60
73
  const searchRange = 5; // Check 5 lines after declaration
61
74
  const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
62
-
75
+
63
76
  let usageCount = 0;
64
77
  for (let i = declarationIndex; i < endIndex; i++) {
65
78
  // Match variable name as whole word
@@ -69,7 +82,7 @@ function analyzeFileNaming(
69
82
  usageCount += matches.length;
70
83
  }
71
84
  }
72
-
85
+
73
86
  // If variable is only used 2-3 times within 5 lines, it's short-lived
74
87
  // (1 = declaration, 1-2 = actual usage)
75
88
  return usageCount >= 2 && usageCount <= 3;
@@ -82,54 +95,70 @@ function analyzeFileNaming(
82
95
 
83
96
  // Check for single letter variables (except i, j, k, l in loops/common contexts)
84
97
  if (!disabledChecks.has('single-letter')) {
85
- const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
98
+ const singleLetterMatches = line.matchAll(
99
+ /\b(?:const|let|var)\s+([a-hm-z])\s*=/gi
100
+ );
86
101
  for (const match of singleLetterMatches) {
87
102
  const letter = match[1].toLowerCase();
88
-
103
+
89
104
  // Coverage metrics context (s/b/f/l are standard for statements/branches/functions/lines)
90
- const isCoverageContext = /coverage|summary|metrics|pct|percent/i.test(line) ||
105
+ const isCoverageContext =
106
+ /coverage|summary|metrics|pct|percent/i.test(line) ||
91
107
  /\.(?:statements|branches|functions|lines)\.pct/i.test(line);
92
108
  if (isCoverageContext && ['s', 'b', 'f', 'l'].includes(letter)) {
93
109
  continue;
94
110
  }
95
-
111
+
96
112
  // Enhanced loop/iterator context detection
97
- const isInLoopContext =
98
- line.includes('for') ||
113
+ const isInLoopContext =
114
+ line.includes('for') ||
99
115
  /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) ||
100
116
  line.includes('=>') || // Arrow function
101
117
  /\w+\s*=>\s*/.test(line); // Callback pattern
102
-
118
+
103
119
  // Check for i18n/translation context
104
- const isI18nContext =
120
+ const isI18nContext =
105
121
  line.includes('useTranslation') ||
106
122
  line.includes('i18n.t') ||
107
123
  /\bt\s*\(['"]/.test(line); // t('key') pattern
108
-
124
+
109
125
  // Check for arrow function parameter (improved detection with context window)
110
- const isArrowFunctionParam =
126
+ const isArrowFunctionParam =
111
127
  /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
112
128
  /[a-z]\s*=>/.test(line) || // s => on same line
113
129
  // Multi-line arrow function detection: look for pattern in context window
114
- new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
115
- new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || '') && /=>/.test(contextWindow); // .map(\n s =>
116
-
130
+ (new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) &&
131
+ /=>/.test(contextWindow)) || // (s)\n =>
132
+ (new RegExp(
133
+ `\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`
134
+ ).test(lines[index - 1] || '') &&
135
+ /=>/.test(contextWindow)); // .map(\n s =>
136
+
117
137
  // Check if variable is short-lived (comparison/temporary contexts)
118
138
  const isShortLived = isShortLivedVariable(letter, index);
119
-
120
- if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !['x', 'y', 'z', 'i', 'j', 'k', 'l', 'n', 'm'].includes(letter)) {
139
+
140
+ if (
141
+ !isInLoopContext &&
142
+ !isI18nContext &&
143
+ !isArrowFunctionParam &&
144
+ !isShortLived &&
145
+ !['x', 'y', 'z', 'i', 'j', 'k', 'l', 'n', 'm'].includes(letter)
146
+ ) {
121
147
  // Skip in test files unless it's really unclear
122
- if (isTestFile && ['a', 'b', 'c', 'd', 'e', 'f', 's'].includes(letter)) {
148
+ if (
149
+ isTestFile &&
150
+ ['a', 'b', 'c', 'd', 'e', 'f', 's'].includes(letter)
151
+ ) {
123
152
  continue;
124
153
  }
125
-
154
+
126
155
  issues.push({
127
156
  file,
128
157
  line: lineNumber,
129
158
  type: 'poor-naming',
130
159
  identifier: match[1],
131
160
  severity: 'minor',
132
- suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
161
+ suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`,
133
162
  });
134
163
  }
135
164
  }
@@ -137,63 +166,78 @@ function analyzeFileNaming(
137
166
 
138
167
  // Check for overly abbreviated variables
139
168
  if (!disabledChecks.has('abbreviation')) {
140
- const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
169
+ const abbreviationMatches = line.matchAll(
170
+ /\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g
171
+ );
141
172
  for (const match of abbreviationMatches) {
142
173
  const abbrev = match[1].toLowerCase();
143
-
174
+
144
175
  // Skip if it's a common short English word (full word, not abbreviation)
145
176
  if (allShortWords.has(abbrev)) {
146
177
  continue;
147
178
  }
148
-
179
+
149
180
  // Skip acceptable abbreviations (including custom ones)
150
181
  if (allAbbreviations.has(abbrev)) {
151
182
  continue;
152
183
  }
153
-
184
+
154
185
  // Check for arrow function parameter context (with multi-line detection)
155
- const isArrowFunctionParam =
186
+ const isArrowFunctionParam =
156
187
  /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
157
188
  new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
158
189
  // Multi-line arrow function: check context window
159
- (new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow)) || // (s)\n =>
160
- (new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || '') &&
161
- new RegExp(`^\\s*${abbrev}\\s*=>`).test(line)); // .map(\n s =>
162
-
190
+ (new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) &&
191
+ /=>/.test(contextWindow)) || // (s)\n =>
192
+ (new RegExp(
193
+ `\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`
194
+ ).test(lines[index - 1] || '') &&
195
+ new RegExp(`^\\s*${abbrev}\\s*=>`).test(line)); // .map(\n s =>
196
+
163
197
  if (isArrowFunctionParam) {
164
198
  continue;
165
199
  }
166
-
200
+
167
201
  // For very short names (1-2 letters), check for date/time context
168
202
  if (abbrev.length <= 2) {
169
- const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
203
+ const isDateTimeContext =
204
+ /date|time|day|hour|minute|second|timestamp/i.test(line);
170
205
  if (isDateTimeContext && ['d', 't', 'dt'].includes(abbrev)) {
171
206
  continue;
172
207
  }
173
-
208
+
174
209
  // Check for user/auth context
175
210
  const isUserContext = /user|auth|account/i.test(line);
176
211
  if (isUserContext && abbrev === 'u') {
177
212
  continue;
178
213
  }
179
214
  }
180
-
215
+
181
216
  issues.push({
182
217
  file,
183
218
  line: lineNumber,
184
219
  type: 'abbreviation',
185
220
  identifier: match[1],
186
221
  severity: 'info',
187
- suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
222
+ suggestion: `Consider using full word instead of abbreviation '${match[1]}'`,
188
223
  });
189
224
  }
190
225
  }
191
226
 
192
227
  // Check for snake_case vs camelCase mixing in TypeScript/JavaScript
193
- if (!disabledChecks.has('convention-mix') && file.match(/\.(ts|tsx|js|jsx)$/)) {
194
- const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
195
- const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
196
-
228
+ if (
229
+ !disabledChecks.has('convention-mix') &&
230
+ file.match(/\.(ts|tsx|js|jsx)$/)
231
+ ) {
232
+ const camelCaseVars = line.match(
233
+ /\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/
234
+ );
235
+ // Variable present for future checks; reference to avoid lint warnings
236
+ void camelCaseVars;
237
+ const snakeCaseVars = line.match(
238
+ /\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/
239
+ );
240
+
197
241
  if (snakeCaseVars) {
198
242
  issues.push({
199
243
  file,
@@ -201,14 +245,16 @@ function analyzeFileNaming(
201
245
  type: 'convention-mix',
202
246
  identifier: snakeCaseVars[1],
203
247
  severity: 'minor',
204
- suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`
248
+ suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`,
205
249
  });
206
250
  }
207
251
  }
208
252
 
209
253
  // Check for unclear boolean names (should start with is/has/should/can)
210
254
  if (!disabledChecks.has('unclear')) {
211
- const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
255
+ const booleanMatches = line.matchAll(
256
+ /\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi
257
+ );
212
258
  for (const match of booleanMatches) {
213
259
  const name = match[1];
214
260
  if (!name.match(/^(is|has|should|can|will|did)/i)) {
@@ -218,7 +264,7 @@ function analyzeFileNaming(
218
264
  type: 'unclear',
219
265
  identifier: name,
220
266
  severity: 'info',
221
- suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
267
+ suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`,
222
268
  });
223
269
  }
224
270
  }
@@ -229,19 +275,39 @@ function analyzeFileNaming(
229
275
  const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
230
276
  for (const match of functionMatches) {
231
277
  const name = match[1];
232
-
278
+
233
279
  // Skip JavaScript/TypeScript keywords that shouldn't be function names
234
- const isKeyword = ['for', 'if', 'else', 'while', 'do', 'switch', 'case', 'break', 'continue', 'return', 'throw', 'try', 'catch', 'finally', 'with', 'yield', 'await'].includes(name);
280
+ const isKeyword = [
281
+ 'for',
282
+ 'if',
283
+ 'else',
284
+ 'while',
285
+ 'do',
286
+ 'switch',
287
+ 'case',
288
+ 'break',
289
+ 'continue',
290
+ 'return',
291
+ 'throw',
292
+ 'try',
293
+ 'catch',
294
+ 'finally',
295
+ 'with',
296
+ 'yield',
297
+ 'await',
298
+ ].includes(name);
235
299
  if (isKeyword) {
236
300
  continue;
237
301
  }
238
-
302
+
239
303
  // Skip common entry point names
240
- const isEntryPoint = ['main', 'init', 'setup', 'bootstrap'].includes(name);
304
+ const isEntryPoint = ['main', 'init', 'setup', 'bootstrap'].includes(
305
+ name
306
+ );
241
307
  if (isEntryPoint) {
242
308
  continue;
243
309
  }
244
-
310
+
245
311
  // Functions should typically start with verbs, but allow:
246
312
  // 1. Factory/builder patterns (ends with Factory, Builder, etc.)
247
313
  // 2. Descriptive compound names that explain what they return
@@ -251,37 +317,63 @@ function analyzeFileNaming(
251
317
  // 6. Compound words with 3+ capitals
252
318
  // 7. Helper/utility functions (common patterns)
253
319
  // 8. React hooks (useX pattern)
254
-
255
- const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/);
320
+
321
+ const isFactoryPattern = name.match(
322
+ /(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/
323
+ );
256
324
  const isEventHandler = name.match(/^on[A-Z]/);
257
325
  const isDescriptiveLong = name.length > 15; // Reduced from 20 to 15
258
326
  const isReactHook = name.match(/^use[A-Z]/); // React hooks
259
-
327
+
260
328
  // Check for descriptive patterns
261
- const isDescriptivePattern = name.match(/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/) ||
262
- name.match(/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/);
263
-
329
+ const isDescriptivePattern =
330
+ name.match(
331
+ /^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/
332
+ ) ||
333
+ name.match(
334
+ /\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/
335
+ );
336
+
264
337
  // Helper/utility function patterns
265
- const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
266
- name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/); // metadataTo, pathFrom
267
-
338
+ const isHelperPattern =
339
+ name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
340
+ name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/); // metadataTo, pathFrom
341
+
268
342
  // Common utility names that are descriptive
269
- const isUtilityName = ['cn', 'proxy', 'sitemap', 'robots', 'gtag'].includes(name);
270
-
343
+ const isUtilityName = [
344
+ 'cn',
345
+ 'proxy',
346
+ 'sitemap',
347
+ 'robots',
348
+ 'gtag',
349
+ ].includes(name);
350
+
271
351
  // Count capital letters for compound detection
272
352
  const capitalCount = (name.match(/[A-Z]/g) || []).length;
273
353
  const isCompoundWord = capitalCount >= 3; // daysSinceLastCommit has 4 capitals
274
-
275
- const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount|track|store|persist|upsert|derive|classify|combine|discover|activate|require|assert|expect|mask|escape|sign|put|list|complete|page|safe|mock|pick|pluralize|text)/);
276
-
277
- if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord && !isHelperPattern && !isUtilityName && !isReactHook) {
354
+
355
+ const hasActionVerb = name.match(
356
+ /^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount|track|store|persist|upsert|derive|classify|combine|discover|activate|require|assert|expect|mask|escape|sign|put|list|complete|page|safe|mock|pick|pluralize|text)/
357
+ );
358
+
359
+ if (
360
+ !hasActionVerb &&
361
+ !isFactoryPattern &&
362
+ !isEventHandler &&
363
+ !isDescriptiveLong &&
364
+ !isDescriptivePattern &&
365
+ !isCompoundWord &&
366
+ !isHelperPattern &&
367
+ !isUtilityName &&
368
+ !isReactHook
369
+ ) {
278
370
  issues.push({
279
371
  file,
280
372
  line: lineNumber,
281
373
  type: 'unclear',
282
374
  identifier: name,
283
375
  severity: 'info',
284
- suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
376
+ suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`,
285
377
  });
286
378
  }
287
379
  }
@@ -4,7 +4,9 @@ import type { PatternIssue } from '../types';
4
4
  /**
5
5
  * Analyzes code pattern consistency
6
6
  */
7
- export async function analyzePatterns(files: string[]): Promise<PatternIssue[]> {
7
+ export async function analyzePatterns(
8
+ files: string[]
9
+ ): Promise<PatternIssue[]> {
8
10
  const issues: PatternIssue[] = [];
9
11
 
10
12
  // Analyze error handling patterns
@@ -27,12 +29,12 @@ async function analyzeErrorHandling(files: string[]): Promise<PatternIssue[]> {
27
29
  tryCatch: [] as string[],
28
30
  throwsError: [] as string[],
29
31
  returnsNull: [] as string[],
30
- returnsError: [] as string[]
32
+ returnsError: [] as string[],
31
33
  };
32
34
 
33
35
  for (const file of files) {
34
36
  const content = await readFileContent(file);
35
-
37
+
36
38
  if (content.includes('try {') || content.includes('} catch')) {
37
39
  patterns.tryCatch.push(file);
38
40
  }
@@ -50,24 +52,36 @@ async function analyzeErrorHandling(files: string[]): Promise<PatternIssue[]> {
50
52
  const issues: PatternIssue[] = [];
51
53
 
52
54
  // Check for mixed error handling strategies
53
- const strategiesUsed = Object.values(patterns).filter(p => p.length > 0).length;
55
+ const strategiesUsed = Object.values(patterns).filter(
56
+ (p) => p.length > 0
57
+ ).length;
54
58
  if (strategiesUsed > 2) {
55
59
  issues.push({
56
- files: [...new Set([
57
- ...patterns.tryCatch,
58
- ...patterns.throwsError,
59
- ...patterns.returnsNull,
60
- ...patterns.returnsError
61
- ])],
60
+ files: [
61
+ ...new Set([
62
+ ...patterns.tryCatch,
63
+ ...patterns.throwsError,
64
+ ...patterns.returnsNull,
65
+ ...patterns.returnsError,
66
+ ]),
67
+ ],
62
68
  type: 'error-handling',
63
69
  description: 'Inconsistent error handling strategies across codebase',
64
70
  examples: [
65
- patterns.tryCatch.length > 0 ? `Try-catch used in ${patterns.tryCatch.length} files` : '',
66
- patterns.throwsError.length > 0 ? `Throws errors in ${patterns.throwsError.length} files` : '',
67
- patterns.returnsNull.length > 0 ? `Returns null in ${patterns.returnsNull.length} files` : '',
68
- patterns.returnsError.length > 0 ? `Returns error objects in ${patterns.returnsError.length} files` : ''
69
- ].filter(e => e),
70
- severity: 'major'
71
+ patterns.tryCatch.length > 0
72
+ ? `Try-catch used in ${patterns.tryCatch.length} files`
73
+ : '',
74
+ patterns.throwsError.length > 0
75
+ ? `Throws errors in ${patterns.throwsError.length} files`
76
+ : '',
77
+ patterns.returnsNull.length > 0
78
+ ? `Returns null in ${patterns.returnsNull.length} files`
79
+ : '',
80
+ patterns.returnsError.length > 0
81
+ ? `Returns error objects in ${patterns.returnsError.length} files`
82
+ : '',
83
+ ].filter((e) => e),
84
+ severity: 'major',
71
85
  });
72
86
  }
73
87
 
@@ -78,12 +92,12 @@ async function analyzeAsyncPatterns(files: string[]): Promise<PatternIssue[]> {
78
92
  const patterns = {
79
93
  asyncAwait: [] as string[],
80
94
  promises: [] as string[],
81
- callbacks: [] as string[]
95
+ callbacks: [] as string[],
82
96
  };
83
97
 
84
98
  for (const file of files) {
85
99
  const content = await readFileContent(file);
86
-
100
+
87
101
  if (content.match(/async\s+(function|\(|[a-zA-Z])/)) {
88
102
  patterns.asyncAwait.push(file);
89
103
  }
@@ -105,20 +119,24 @@ async function analyzeAsyncPatterns(files: string[]): Promise<PatternIssue[]> {
105
119
  description: 'Mixed async patterns: callbacks and async/await',
106
120
  examples: [
107
121
  `Callbacks found in: ${patterns.callbacks.slice(0, 3).join(', ')}`,
108
- `Async/await used in: ${patterns.asyncAwait.slice(0, 3).join(', ')}`
122
+ `Async/await used in: ${patterns.asyncAwait.slice(0, 3).join(', ')}`,
109
123
  ],
110
- severity: 'minor'
124
+ severity: 'minor',
111
125
  });
112
126
  }
113
127
 
114
128
  // Mixing .then() chains with async/await
115
- if (patterns.promises.length > patterns.asyncAwait.length * 0.3 && patterns.asyncAwait.length > 0) {
129
+ if (
130
+ patterns.promises.length > patterns.asyncAwait.length * 0.3 &&
131
+ patterns.asyncAwait.length > 0
132
+ ) {
116
133
  issues.push({
117
134
  files: patterns.promises,
118
135
  type: 'async-style',
119
- description: 'Consider using async/await instead of promise chains for consistency',
136
+ description:
137
+ 'Consider using async/await instead of promise chains for consistency',
120
138
  examples: patterns.promises.slice(0, 5),
121
- severity: 'info'
139
+ severity: 'info',
122
140
  });
123
141
  }
124
142
 
@@ -129,19 +147,19 @@ async function analyzeImportStyles(files: string[]): Promise<PatternIssue[]> {
129
147
  const patterns = {
130
148
  esModules: [] as string[],
131
149
  commonJS: [] as string[],
132
- mixed: [] as string[]
150
+ mixed: [] as string[],
133
151
  };
134
152
 
135
153
  for (const file of files) {
136
154
  const content = await readFileContent(file);
137
155
  const hasESM = content.match(/^import\s+/m);
138
-
156
+
139
157
  // Check for actual CommonJS require() calls, excluding:
140
158
  // - String literals: "require('...') or 'require('...')
141
159
  // - Regex patterns: /require\(/
142
160
  // - Comments: // require( or /* require( */
143
161
  const hasCJS = hasActualRequireCalls(content);
144
-
162
+
145
163
  if (hasESM && hasCJS) {
146
164
  patterns.mixed.push(file);
147
165
  } else if (hasESM) {
@@ -160,13 +178,15 @@ async function analyzeImportStyles(files: string[]): Promise<PatternIssue[]> {
160
178
  type: 'import-style',
161
179
  description: 'Mixed ES modules and CommonJS imports in same files',
162
180
  examples: patterns.mixed.slice(0, 5),
163
- severity: 'major'
181
+ severity: 'major',
164
182
  });
165
183
  }
166
184
 
167
185
  // Check for inconsistent styles across project
168
186
  if (patterns.esModules.length > 0 && patterns.commonJS.length > 0) {
169
- const ratio = patterns.commonJS.length / (patterns.esModules.length + patterns.commonJS.length);
187
+ const ratio =
188
+ patterns.commonJS.length /
189
+ (patterns.esModules.length + patterns.commonJS.length);
170
190
  if (ratio > 0.2 && ratio < 0.8) {
171
191
  issues.push({
172
192
  files: [...patterns.esModules, ...patterns.commonJS],
@@ -174,9 +194,9 @@ async function analyzeImportStyles(files: string[]): Promise<PatternIssue[]> {
174
194
  description: 'Inconsistent import styles across project',
175
195
  examples: [
176
196
  `ES modules: ${patterns.esModules.length} files`,
177
- `CommonJS: ${patterns.commonJS.length} files`
197
+ `CommonJS: ${patterns.commonJS.length} files`,
178
198
  ],
179
- severity: 'minor'
199
+ severity: 'minor',
180
200
  });
181
201
  }
182
202
  }
@@ -196,20 +216,20 @@ function hasActualRequireCalls(content: string): boolean {
196
216
  // Simple heuristic: remove obvious false positives
197
217
  // 1. Remove single-line comments
198
218
  let cleaned = content.replace(/\/\/.*$/gm, '');
199
-
219
+
200
220
  // 2. Remove multi-line comments (non-greedy)
201
221
  cleaned = cleaned.replace(/\/\*[\s\S]*?\*\//g, '');
202
-
222
+
203
223
  // 3. Remove string literals - use simpler regex to avoid backtracking
204
224
  // Match strings but don't try to be perfect, just remove obvious ones
205
225
  cleaned = cleaned.replace(/"[^"\n]*"/g, '""');
206
226
  cleaned = cleaned.replace(/'[^'\n]*'/g, "''");
207
227
  cleaned = cleaned.replace(/`[^`]*`/g, '``');
208
-
228
+
209
229
  // 4. Simple regex detection: if we see /require in the line, likely a regex pattern
210
230
  // Remove lines that look like regex patterns with require
211
231
  cleaned = cleaned.replace(/\/[^/\n]*require[^/\n]*\/[gimsuvy]*/g, '');
212
-
232
+
213
233
  // Now check for require( in the cleaned content
214
234
  return /require\s*\(/.test(cleaned);
215
235
  }
@@ -217,11 +237,15 @@ function hasActualRequireCalls(content: string): boolean {
217
237
  /**
218
238
  * Analyzes API design consistency
219
239
  */
220
- export async function analyzeAPIDesign(files: string[]): Promise<PatternIssue[]> {
240
+ export async function analyzeAPIDesign(
241
+ files: string[]
242
+ ): Promise<PatternIssue[]> {
221
243
  // This would analyze:
222
244
  // - Function parameter order consistency
223
245
  // - Return type patterns
224
246
  // - Options object vs individual parameters
225
247
  // For now, return empty array
248
+ // Parameter currently unused; reference to avoid lint warnings
249
+ void files;
226
250
  return [];
227
251
  }