@aiready/pattern-detect 0.17.15 → 0.17.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyzer-entry/index.d.mts +2 -2
- package/dist/analyzer-entry/index.d.ts +2 -2
- package/dist/analyzer-entry/index.js +357 -140
- package/dist/analyzer-entry/index.mjs +4 -4
- package/dist/chunk-3LMYFYWG.mjs +514 -0
- package/dist/chunk-4YXKUW4P.mjs +143 -0
- package/dist/chunk-5A3ULAQ5.mjs +571 -0
- package/dist/chunk-5FACKJ7M.mjs +519 -0
- package/dist/chunk-6B72OWZA.mjs +143 -0
- package/dist/chunk-6SHBBRHF.mjs +600 -0
- package/dist/chunk-BKSIA7A2.mjs +516 -0
- package/dist/chunk-CM5YJR7G.mjs +516 -0
- package/dist/chunk-FSXOU23F.mjs +620 -0
- package/dist/chunk-GUYQI3AF.mjs +514 -0
- package/dist/chunk-H2TGXGMX.mjs +587 -0
- package/dist/chunk-KMAOEVRS.mjs +150 -0
- package/dist/chunk-NWG2ZIGX.mjs +146 -0
- package/dist/chunk-OFVJFGQW.mjs +514 -0
- package/dist/chunk-PCCZREHY.mjs +143 -0
- package/dist/chunk-PQS5ACTN.mjs +516 -0
- package/dist/chunk-TVE75IDM.mjs +143 -0
- package/dist/chunk-UDOGQ42Q.mjs +603 -0
- package/dist/chunk-UFI4UDQI.mjs +514 -0
- package/dist/chunk-UXV57HN3.mjs +144 -0
- package/dist/chunk-VC2BOV6R.mjs +143 -0
- package/dist/chunk-VI2OVG73.mjs +514 -0
- package/dist/chunk-VKGYNHFY.mjs +514 -0
- package/dist/chunk-WBLZYAQ2.mjs +518 -0
- package/dist/chunk-WFVXMMB3.mjs +143 -0
- package/dist/chunk-WQC43BIO.mjs +516 -0
- package/dist/chunk-WTAIM3SG.mjs +146 -0
- package/dist/chunk-XC7U55PE.mjs +514 -0
- package/dist/chunk-XR373Q6G.mjs +146 -0
- package/dist/chunk-XWIBTD67.mjs +620 -0
- package/dist/chunk-YUQ2VQVJ.mjs +514 -0
- package/dist/chunk-Z4NOH52X.mjs +143 -0
- package/dist/cli.js +357 -140
- package/dist/cli.mjs +4 -4
- package/dist/context-rules-entry/index.js +351 -139
- package/dist/context-rules-entry/index.mjs +1 -1
- package/dist/detector-entry/index.d.mts +2 -2
- package/dist/detector-entry/index.d.ts +2 -2
- package/dist/detector-entry/index.js +355 -140
- package/dist/detector-entry/index.mjs +2 -2
- package/dist/index-BGvkJ9j1.d.mts +136 -0
- package/dist/index-BJq32qmj.d.mts +137 -0
- package/dist/index-BpoJSgX-.d.mts +136 -0
- package/dist/index-C7qLPKmH.d.ts +150 -0
- package/dist/index-CThnG9hv.d.ts +155 -0
- package/dist/index-D0Hpg9nN.d.mts +150 -0
- package/dist/index-DN6XpBOW.d.mts +155 -0
- package/dist/index-F8xqZ2PS.d.ts +136 -0
- package/dist/index-HNhDr6CV.d.ts +137 -0
- package/dist/index-ux0Wo8Ps.d.ts +136 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +359 -142
- package/dist/index.mjs +4 -4
- package/dist/scoring-entry/index.d.mts +1 -1
- package/dist/scoring-entry/index.d.ts +1 -1
- package/dist/scoring-entry/index.js +2 -2
- package/dist/scoring-entry/index.mjs +1 -1
- package/dist/types-tgrmUrHE.d.mts +37 -0
- package/dist/types-tgrmUrHE.d.ts +37 -0
- package/package.json +5 -3
package/dist/cli.js
CHANGED
|
@@ -194,179 +194,391 @@ var INFRA_RULES = [
|
|
|
194
194
|
}
|
|
195
195
|
];
|
|
196
196
|
|
|
197
|
-
// src/rules/categories/logic-
|
|
198
|
-
var
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
197
|
+
// src/rules/categories/logic/file-detectors.ts
|
|
198
|
+
var FileDetectors = {
|
|
199
|
+
isIndexFile: (file) => file.endsWith("/index.ts") || file.endsWith("/index.js") || file.endsWith("/index.tsx") || file.endsWith("/index.jsx"),
|
|
200
|
+
isTypeFile: (file) => file.endsWith(".d.ts") || file.includes("/types/"),
|
|
201
|
+
isUtilFile: (file) => file.endsWith(".util.ts") || file.endsWith(".helper.ts") || file.endsWith(".utils.ts"),
|
|
202
|
+
isHookFile: (file) => file.includes("/hooks/") || file.endsWith(".hook.ts") || file.endsWith(".hook.tsx"),
|
|
203
|
+
isHelperFile: (file) => file.includes("/utils/") || file.includes("/helpers/") || file.endsWith(".util.ts"),
|
|
204
|
+
isVizFile: (file) => file.includes("/visualizer/") || file.includes("/charts/") || file.includes("GraphCanvas") || file.includes("ForceDirected"),
|
|
205
|
+
isApiFile: (file) => file.includes("/api/") || file.includes("/lib/") || file.includes("/utils/") || file.endsWith(".ts"),
|
|
206
|
+
isPackageOrApp: (file) => file.includes("/packages/") || file.includes("/apps/") || file.includes("/core/"),
|
|
207
|
+
getPackageName: (file) => {
|
|
208
|
+
const match = file.match(/\/(packages|apps|core)\/([^/]+)\//);
|
|
209
|
+
return match?.[2] || null;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// src/rules/categories/logic/keyword-lists.ts
|
|
214
|
+
var VALIDATION_KEYWORDS = [
|
|
215
|
+
"isValid",
|
|
216
|
+
"validate",
|
|
217
|
+
"checkValid",
|
|
218
|
+
"isEmail",
|
|
219
|
+
"isPhone",
|
|
220
|
+
"isUrl",
|
|
221
|
+
"isNumeric",
|
|
222
|
+
"isAlpha",
|
|
223
|
+
"isAlphanumeric",
|
|
224
|
+
"isEmpty",
|
|
225
|
+
"isNotEmpty",
|
|
226
|
+
"isRequired",
|
|
227
|
+
"isOptional"
|
|
228
|
+
];
|
|
229
|
+
var HOOK_KEYWORDS = [
|
|
230
|
+
"function use",
|
|
231
|
+
"export function use",
|
|
232
|
+
"const use",
|
|
233
|
+
"export const use"
|
|
234
|
+
];
|
|
235
|
+
var VIZ_EVENT_KEYWORDS = ["dragstarted", "dragged", "dragended"];
|
|
236
|
+
var VIZ_LIB_KEYWORDS = ["simulation", "d3.", "alphaTarget"];
|
|
237
|
+
var ICON_HELPER_KEYWORDS = [
|
|
238
|
+
"getIcon",
|
|
239
|
+
"getColor",
|
|
240
|
+
"getLabel",
|
|
241
|
+
"getRating"
|
|
242
|
+
];
|
|
243
|
+
var UTIL_KEYWORDS = [
|
|
244
|
+
"format",
|
|
245
|
+
"parse",
|
|
246
|
+
"sanitize",
|
|
247
|
+
"normalize",
|
|
248
|
+
"convert",
|
|
249
|
+
"utility",
|
|
250
|
+
"helper"
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
// src/rules/categories/logic/detectors.ts
|
|
254
|
+
var isEnumDefinition = (code) => /(?:export\s+)?(?:const\s+)?enum\s+/.test(code) || code.includes("enum ") && code.includes("{") && code.includes("}");
|
|
255
|
+
var hasSingletonGetter = (code) => /(?:export\s+)?(?:async\s+)?function\s+get[A-Z][a-zA-Z0-9]*\s*\(/.test(
|
|
256
|
+
code
|
|
257
|
+
) || /(?:export\s+)?const\s+get[A-Z][a-zA-Z0-9]*\s*=\s*(?:async\s+)?\(\)\s*=>/.test(
|
|
258
|
+
code
|
|
259
|
+
);
|
|
260
|
+
var hasSingletonPattern = (code) => code.includes("if (!") && code.includes("instance") && code.includes(" = ") || code.includes("if (!_") && code.includes(" = new ");
|
|
261
|
+
var hasReExportPattern = (code) => {
|
|
262
|
+
const lines = code.split("\n").filter((l) => l.trim());
|
|
263
|
+
if (lines.length === 0) return false;
|
|
264
|
+
const reExportLines = lines.filter(
|
|
265
|
+
(l) => /^export\s+(\{[^}]+\}|\*)\s+from\s+/.test(l.trim()) || /^export\s+\*\s+as\s+\w+\s+from\s+/.test(l.trim())
|
|
266
|
+
).length;
|
|
267
|
+
return reExportLines > 0 && reExportLines / lines.length > 0.5;
|
|
268
|
+
};
|
|
269
|
+
var isInterfaceOnlySnippet = (code) => {
|
|
270
|
+
const hasInterface = code.includes("interface ");
|
|
271
|
+
const hasType = code.includes("type ");
|
|
272
|
+
const hasEnum = code.includes("enum ");
|
|
273
|
+
const hasNoImpl = ![
|
|
274
|
+
"function ",
|
|
275
|
+
"class ",
|
|
276
|
+
"const ",
|
|
277
|
+
"let ",
|
|
278
|
+
"var ",
|
|
279
|
+
"export default",
|
|
280
|
+
"export {"
|
|
281
|
+
].some((kw) => code.includes(kw));
|
|
282
|
+
return (hasInterface || hasType || hasEnum) && hasNoImpl;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// src/rules/categories/logic/code-patterns.ts
|
|
286
|
+
var COMMON_ENUM_PATTERNS = [
|
|
287
|
+
["LOW", ["'low'", "0", "'LOW'"]],
|
|
288
|
+
["HIGH", ["'high'", "2", "'HIGH'"]],
|
|
289
|
+
["MEDIUM", ["'medium'", "1", "'MEDIUM'"]]
|
|
290
|
+
];
|
|
291
|
+
var hasEnumValue = (code, enumName, variants) => variants.some((v) => code.includes(`${enumName} = ${v}`));
|
|
292
|
+
var CodePatterns = {
|
|
293
|
+
// Enum patterns
|
|
294
|
+
hasCommonEnumValues: (code) => COMMON_ENUM_PATTERNS.every(
|
|
295
|
+
([name, variants]) => hasEnumValue(code, name, variants)
|
|
296
|
+
),
|
|
297
|
+
isEnumDefinition,
|
|
298
|
+
// Type patterns - helpers for checking type-only definitions
|
|
299
|
+
hasTypeDefinition: (code) => code.includes("interface ") || code.includes("type ") || code.includes("enum "),
|
|
300
|
+
isTypeOnlyFile: (code) => {
|
|
301
|
+
const hasTypes = CodePatterns.hasTypeDefinition(code);
|
|
302
|
+
const hasNoImpl = ![
|
|
303
|
+
"function ",
|
|
304
|
+
"class ",
|
|
305
|
+
"const ",
|
|
306
|
+
"let ",
|
|
307
|
+
"export default"
|
|
308
|
+
].some((kw) => code.includes(kw));
|
|
309
|
+
return hasTypes && hasNoImpl;
|
|
310
|
+
},
|
|
311
|
+
hasOnlyTypeDefinitions: (code) => {
|
|
312
|
+
const hasTypes = CodePatterns.hasTypeDefinition(code);
|
|
313
|
+
const hasNoImpl = ![
|
|
314
|
+
"function ",
|
|
315
|
+
"class ",
|
|
316
|
+
"const ",
|
|
317
|
+
"let ",
|
|
318
|
+
"var ",
|
|
319
|
+
"export default",
|
|
320
|
+
"export {"
|
|
321
|
+
].some((kw) => code.includes(kw));
|
|
322
|
+
return hasTypes && hasNoImpl;
|
|
323
|
+
},
|
|
324
|
+
// Utility patterns - function group patterns
|
|
325
|
+
hasUtilPattern: (code) => CodePatterns.hasFunctionGroup(code, "") || CodePatterns.hasKeywordGroup(code, UTIL_KEYWORDS),
|
|
326
|
+
hasFunctionGroup: (code, prefix) => {
|
|
327
|
+
const regex = new RegExp(
|
|
328
|
+
`${prefix}(format|parse|sanitize|normalize|convert)`,
|
|
329
|
+
"i"
|
|
330
|
+
);
|
|
331
|
+
return regex.test(code);
|
|
332
|
+
},
|
|
333
|
+
// Pattern checkers using keyword lists
|
|
334
|
+
hasKeywordGroup: (code, keywords) => keywords.some((kw) => code.includes(kw)),
|
|
335
|
+
// Validation patterns - unified keyword check
|
|
336
|
+
hasValidationPattern: (code) => CodePatterns.hasKeywordGroup(code, VALIDATION_KEYWORDS),
|
|
337
|
+
// Hook patterns - React/Vue hooks
|
|
338
|
+
hasHookPattern: (code) => CodePatterns.hasKeywordGroup(code, HOOK_KEYWORDS),
|
|
339
|
+
// Score/Rating patterns
|
|
340
|
+
hasScorePattern: (code) => (code.includes("if (score >=") || code.includes("if (score >")) && code.includes("return") && code.includes("'") && code.split("if (score").length >= 3,
|
|
341
|
+
// Visualization patterns - D3/Canvas
|
|
342
|
+
hasVizPattern: (code) => CodePatterns.hasKeywordGroup(code, VIZ_EVENT_KEYWORDS) && CodePatterns.hasKeywordGroup(code, VIZ_LIB_KEYWORDS),
|
|
343
|
+
// Switch/Icon patterns
|
|
344
|
+
hasSwitchPattern: (code) => code.includes("switch (") && code.includes("case '") && code.includes("return") && code.split("case ").length >= 4,
|
|
345
|
+
hasIconPattern: (code) => CodePatterns.hasKeywordGroup(code, ICON_HELPER_KEYWORDS),
|
|
346
|
+
// Singleton patterns
|
|
347
|
+
hasSingletonGetter,
|
|
348
|
+
hasSingletonPattern,
|
|
349
|
+
// Re-export patterns
|
|
350
|
+
hasReExportPattern,
|
|
351
|
+
// Interface-only snippets
|
|
352
|
+
isInterfaceOnlySnippet
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// src/rules/categories/logic/api-patterns.ts
|
|
356
|
+
var API_PATTERNS = {
|
|
357
|
+
stripe: {
|
|
358
|
+
functions: ["getStripe"],
|
|
359
|
+
keywords: ["process.env.STRIPE_SECRET_KEY"]
|
|
360
|
+
},
|
|
361
|
+
userManagement: {
|
|
362
|
+
functions: ["getUserByEmail"],
|
|
363
|
+
keywords: ["queryItems"]
|
|
364
|
+
},
|
|
365
|
+
userUpdate: {
|
|
366
|
+
functions: ["updateUser"],
|
|
367
|
+
keywords: ["buildUpdateExpression"]
|
|
368
|
+
},
|
|
369
|
+
repositoryListing: {
|
|
370
|
+
functions: ["listUserRepositories", "listTeamRepositories"],
|
|
371
|
+
keywords: ["queryItems"]
|
|
372
|
+
},
|
|
373
|
+
remediationQueries: {
|
|
374
|
+
functions: ["getRemediation"],
|
|
375
|
+
keywords: ["queryItems"]
|
|
376
|
+
},
|
|
377
|
+
keyFormatting: {
|
|
378
|
+
functions: ["formatBreakdownKey"],
|
|
379
|
+
keywords: [".replace(/([A-Z])/g"]
|
|
380
|
+
},
|
|
381
|
+
dynamoDbQueries: {
|
|
382
|
+
functions: ["queryItems"],
|
|
383
|
+
keywords: ["KeyConditionExpression"]
|
|
384
|
+
},
|
|
385
|
+
itemOperations: {
|
|
386
|
+
functions: ["putItem"],
|
|
387
|
+
keywords: ["createdAt"]
|
|
388
|
+
},
|
|
389
|
+
itemUpdates: {
|
|
390
|
+
functions: ["updateItem"],
|
|
391
|
+
keywords: ["buildUpdateExpression"]
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
var ApiPatterns = {
|
|
395
|
+
/**
|
|
396
|
+
* Check if code contains a common API pattern
|
|
397
|
+
* Reduces from 11 separate || conditions to single call
|
|
398
|
+
*/
|
|
399
|
+
hasCommonApiPattern: (code) => {
|
|
400
|
+
for (const pattern of Object.values(API_PATTERNS)) {
|
|
401
|
+
const hasFunctions = pattern.functions.some((fn) => code.includes(fn));
|
|
402
|
+
const hasKeywords = pattern.keywords.some((kw) => code.includes(kw));
|
|
403
|
+
if (hasFunctions && hasKeywords) {
|
|
404
|
+
return true;
|
|
209
405
|
}
|
|
210
|
-
|
|
211
|
-
|
|
406
|
+
}
|
|
407
|
+
return false;
|
|
408
|
+
},
|
|
409
|
+
/**
|
|
410
|
+
* Get all API patterns (useful for documentation/analysis)
|
|
411
|
+
*/
|
|
412
|
+
getPatterns: () => API_PATTERNS,
|
|
413
|
+
/**
|
|
414
|
+
* Check if code matches a specific API pattern by name
|
|
415
|
+
*/
|
|
416
|
+
matchesPattern: (code, patternName) => {
|
|
417
|
+
const pattern = API_PATTERNS[patternName];
|
|
418
|
+
if (!pattern) return false;
|
|
419
|
+
const hasFunctions = pattern.functions.some((fn) => code.includes(fn));
|
|
420
|
+
const hasKeywords = pattern.keywords.some((kw) => code.includes(kw));
|
|
421
|
+
return hasFunctions && hasKeywords;
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// src/rules/categories/logic/rule-builders.ts
|
|
426
|
+
var import_core4 = require("@aiready/core");
|
|
427
|
+
function createRule(config) {
|
|
428
|
+
return {
|
|
429
|
+
name: config.name,
|
|
430
|
+
detect: config.detect,
|
|
431
|
+
severity: config.severity || import_core4.Severity.Info,
|
|
432
|
+
reason: config.reason,
|
|
433
|
+
suggestion: config.suggestion
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
var RuleTemplates = {
|
|
437
|
+
// Type-related rules
|
|
438
|
+
typeDefinition: {
|
|
439
|
+
severity: import_core4.Severity.Info,
|
|
440
|
+
reason: "Type/interface definitions are intentionally duplicated for module independence",
|
|
441
|
+
suggestion: "Extract to shared types package only if causing maintenance burden"
|
|
442
|
+
},
|
|
443
|
+
crossPackageType: {
|
|
444
|
+
severity: import_core4.Severity.Info,
|
|
445
|
+
reason: "Types in different packages/modules are often intentionally similar for module independence",
|
|
446
|
+
suggestion: "Cross-package type duplication is acceptable for decoupled module architecture"
|
|
447
|
+
},
|
|
448
|
+
// Pattern rules
|
|
449
|
+
standardPatterns: {
|
|
450
|
+
severity: import_core4.Severity.Info,
|
|
451
|
+
reason: "This pattern is inherently similar and intentionally duplicated across modules",
|
|
452
|
+
suggestion: "Pattern duplication is acceptable for domain clarity and independence"
|
|
453
|
+
},
|
|
454
|
+
// API rules
|
|
455
|
+
commonApiFunction: {
|
|
456
|
+
severity: import_core4.Severity.Info,
|
|
457
|
+
reason: "Common API/utility functions are legitimately duplicated across modules for clarity and independence",
|
|
458
|
+
suggestion: "Consider extracting to shared utilities only if causing significant duplication"
|
|
459
|
+
},
|
|
460
|
+
// Utility rules
|
|
461
|
+
utilityFunction: {
|
|
462
|
+
severity: import_core4.Severity.Info,
|
|
463
|
+
reason: "Utility functions may be intentionally similar across modules",
|
|
464
|
+
suggestion: "Consider extracting to shared utilities only if causing significant duplication"
|
|
465
|
+
},
|
|
466
|
+
// Boilerplate rules
|
|
467
|
+
boilerplate: {
|
|
468
|
+
severity: import_core4.Severity.Info,
|
|
469
|
+
reason: "This pattern is boilerplate and acceptable duplication",
|
|
470
|
+
suggestion: "Boilerplate reduction is acceptable for standard patterns"
|
|
471
|
+
},
|
|
472
|
+
// Enum rules
|
|
473
|
+
enumSemantic: {
|
|
212
474
|
severity: import_core4.Severity.Info,
|
|
213
475
|
reason: "Enums with different names represent different semantic domain concepts, even if they share similar values",
|
|
214
476
|
suggestion: "Different enums (e.g., EscalationPriority vs HealthSeverity) serve different purposes and should not be merged"
|
|
215
477
|
},
|
|
216
|
-
|
|
217
|
-
{
|
|
218
|
-
name: "enum-value-similarity",
|
|
219
|
-
detect: (file, code) => {
|
|
220
|
-
const hasCommonEnumValues = (code.includes("LOW = 'low'") || code.includes("LOW = 0") || code.includes("LOW = 'LOW'")) && (code.includes("HIGH = 'high'") || code.includes("HIGH = 2") || code.includes("HIGH = 'HIGH'")) && (code.includes("MEDIUM = 'medium'") || code.includes("MEDIUM = 1") || code.includes("MEDIUM = 'MEDIUM'"));
|
|
221
|
-
const isEnumDefinition = /(?:export\s+)?(?:const\s+)?enum\s+/.test(code) || code.includes("enum ") && code.includes("{") && code.includes("}");
|
|
222
|
-
return hasCommonEnumValues && isEnumDefinition;
|
|
223
|
-
},
|
|
478
|
+
enumValue: {
|
|
224
479
|
severity: import_core4.Severity.Info,
|
|
225
480
|
reason: "Common enum values (LOW, MEDIUM, HIGH, CRITICAL) are standard patterns used across different domain enums",
|
|
226
481
|
suggestion: "Enum value similarity is expected for severity/priority enums and should not be flagged as duplication"
|
|
227
482
|
},
|
|
228
|
-
//
|
|
229
|
-
{
|
|
230
|
-
name: "re-export-files",
|
|
231
|
-
detect: (file, code) => {
|
|
232
|
-
const isIndexFile = file.endsWith("/index.ts") || file.endsWith("/index.js") || file.endsWith("/index.tsx") || file.endsWith("/index.jsx");
|
|
233
|
-
const lines = code.split("\n").filter((l) => l.trim());
|
|
234
|
-
if (lines.length === 0) return false;
|
|
235
|
-
const reExportLines = lines.filter(
|
|
236
|
-
(l) => /^export\s+(\{[^}]+\}|\*)\s+from\s+/.test(l.trim()) || /^export\s+\*\s+as\s+\w+\s+from\s+/.test(l.trim())
|
|
237
|
-
).length;
|
|
238
|
-
return isIndexFile && reExportLines > 0 && reExportLines / lines.length > 0.5;
|
|
239
|
-
},
|
|
483
|
+
// Barrel file rules
|
|
484
|
+
barrelFile: {
|
|
240
485
|
severity: import_core4.Severity.Info,
|
|
241
486
|
reason: "Barrel/index files intentionally re-export for API surface consolidation",
|
|
242
487
|
suggestion: "Re-exports in barrel files are expected and not true duplication"
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// src/rules/categories/logic-rules.ts
|
|
492
|
+
var LOGIC_RULES = [
|
|
493
|
+
// Enum patterns - identify by semantic meaning and value patterns
|
|
494
|
+
createRule({
|
|
495
|
+
name: "enum-semantic-difference",
|
|
496
|
+
detect: (file, code) => CodePatterns.isEnumDefinition(code),
|
|
497
|
+
...RuleTemplates.enumSemantic
|
|
498
|
+
}),
|
|
499
|
+
createRule({
|
|
500
|
+
name: "enum-value-similarity",
|
|
501
|
+
detect: (file, code) => CodePatterns.hasCommonEnumValues(code) && CodePatterns.isEnumDefinition(code),
|
|
502
|
+
...RuleTemplates.enumValue
|
|
503
|
+
}),
|
|
504
|
+
// Barrel/re-export files - intentional API consolidation
|
|
505
|
+
createRule({
|
|
506
|
+
name: "re-export-files",
|
|
507
|
+
detect: (file, code) => FileDetectors.isIndexFile(file) && CodePatterns.hasReExportPattern(code),
|
|
508
|
+
...RuleTemplates.barrelFile
|
|
509
|
+
}),
|
|
510
|
+
// Type definitions - consolidated rule for both local and cross-package types
|
|
511
|
+
// PHASE 2 CONSOLIDATION: merged type-definitions + cross-package-types
|
|
512
|
+
createRule({
|
|
246
513
|
name: "type-definitions",
|
|
247
514
|
detect: (file, code) => {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
},
|
|
257
|
-
// Cross-Package Type Definitions - Different packages may have similar types
|
|
258
|
-
{
|
|
259
|
-
name: "cross-package-types",
|
|
260
|
-
detect: (file, code) => {
|
|
261
|
-
const hasTypeDefinition = code.includes("interface ") || code.includes("type ") || code.includes("enum ");
|
|
262
|
-
const isPackageOrApp = file.includes("/packages/") || file.includes("/apps/") || file.includes("/core/");
|
|
263
|
-
const packageMatch = file.match(/\/(packages|apps|core)\/([^/]+)\//);
|
|
264
|
-
const hasPackageStructure = packageMatch !== null;
|
|
265
|
-
return hasTypeDefinition && isPackageOrApp && hasPackageStructure;
|
|
515
|
+
if (FileDetectors.isTypeFile(file)) {
|
|
516
|
+
if (CodePatterns.hasOnlyTypeDefinitions(code)) return true;
|
|
517
|
+
}
|
|
518
|
+
if (CodePatterns.isInterfaceOnlySnippet(code)) return true;
|
|
519
|
+
if (FileDetectors.isPackageOrApp(file) && FileDetectors.getPackageName(file) !== null && CodePatterns.hasTypeDefinition(code)) {
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
return false;
|
|
266
523
|
},
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
{
|
|
524
|
+
reason: "Type/interface definitions are intentionally duplicated for module independence and decoupled module architecture",
|
|
525
|
+
suggestion: "Extract to shared types package only if causing significant maintenance burden or cross-package conflicts"
|
|
526
|
+
}),
|
|
527
|
+
// Utility functions - dedicated utility file patterns
|
|
528
|
+
createRule({
|
|
273
529
|
name: "utility-functions",
|
|
274
|
-
detect: (file, code) =>
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
severity: import_core4.Severity.Info,
|
|
280
|
-
reason: "Utility functions in dedicated utility files may be intentionally similar",
|
|
281
|
-
suggestion: "Consider extracting to shared utilities only if causing significant duplication"
|
|
282
|
-
},
|
|
283
|
-
// React/Vue Hooks - Standard patterns
|
|
284
|
-
{
|
|
530
|
+
detect: (file, code) => FileDetectors.isUtilFile(file) && CodePatterns.hasUtilPattern(code),
|
|
531
|
+
...RuleTemplates.utilityFunction
|
|
532
|
+
}),
|
|
533
|
+
// React/Vue Hooks - standard hook patterns
|
|
534
|
+
createRule({
|
|
285
535
|
name: "shared-hooks",
|
|
286
|
-
detect: (file, code) =>
|
|
287
|
-
const isHookFile = file.includes("/hooks/") || file.endsWith(".hook.ts") || file.endsWith(".hook.tsx");
|
|
288
|
-
const hasHookPattern = code.includes("function use") || code.includes("export function use") || code.includes("const use") || code.includes("export const use");
|
|
289
|
-
return isHookFile && hasHookPattern;
|
|
290
|
-
},
|
|
291
|
-
severity: import_core4.Severity.Info,
|
|
536
|
+
detect: (file, code) => FileDetectors.isHookFile(file) && CodePatterns.hasHookPattern(code),
|
|
292
537
|
reason: "Hooks follow standard patterns and are often intentionally similar across components",
|
|
293
538
|
suggestion: "Consider extracting common hook logic only if hooks become complex"
|
|
294
|
-
},
|
|
295
|
-
// Score/Rating
|
|
296
|
-
{
|
|
539
|
+
}),
|
|
540
|
+
// Score/Rating helpers - common threshold patterns
|
|
541
|
+
createRule({
|
|
297
542
|
name: "score-helpers",
|
|
298
|
-
detect: (file, code) =>
|
|
299
|
-
const isHelperFile = file.includes("/utils/") || file.includes("/helpers/") || file.endsWith(".util.ts");
|
|
300
|
-
const hasScorePattern = (code.includes("if (score >=") || code.includes("if (score >")) && code.includes("return") && code.includes("'") && code.split("if (score").length >= 3;
|
|
301
|
-
return isHelperFile && hasScorePattern;
|
|
302
|
-
},
|
|
303
|
-
severity: import_core4.Severity.Info,
|
|
543
|
+
detect: (file, code) => (FileDetectors.isHelperFile(file) || file.includes("/utils/")) && CodePatterns.hasScorePattern(code),
|
|
304
544
|
reason: "Score/rating helper functions use common threshold patterns that are intentionally similar",
|
|
305
545
|
suggestion: "Score formatting duplication is acceptable for consistent UI display"
|
|
306
|
-
},
|
|
307
|
-
// D3/Canvas
|
|
308
|
-
{
|
|
546
|
+
}),
|
|
547
|
+
// D3/Canvas event handlers - visualization patterns
|
|
548
|
+
createRule({
|
|
309
549
|
name: "visualization-handlers",
|
|
310
|
-
detect: (file, code) =>
|
|
311
|
-
const isVizFile = file.includes("/visualizer/") || file.includes("/charts/") || file.includes("GraphCanvas") || file.includes("ForceDirected");
|
|
312
|
-
const hasVizPattern = (code.includes("dragstarted") || code.includes("dragged") || code.includes("dragended")) && (code.includes("simulation") || code.includes("d3.") || code.includes("alphaTarget"));
|
|
313
|
-
return isVizFile && hasVizPattern;
|
|
314
|
-
},
|
|
315
|
-
severity: import_core4.Severity.Info,
|
|
550
|
+
detect: (file, code) => FileDetectors.isVizFile(file) && CodePatterns.hasVizPattern(code),
|
|
316
551
|
reason: "D3/visualization event handlers follow standard patterns and are intentionally similar",
|
|
317
552
|
suggestion: "Visualization boilerplate duplication is acceptable for interactive charts"
|
|
318
|
-
},
|
|
319
|
-
//
|
|
320
|
-
{
|
|
553
|
+
}),
|
|
554
|
+
// Switch statement helpers - enum-to-value mapping
|
|
555
|
+
createRule({
|
|
321
556
|
name: "switch-helpers",
|
|
322
|
-
detect: (file, code) =>
|
|
323
|
-
const hasSwitchPattern = code.includes("switch (") && code.includes("case '") && code.includes("return") && code.split("case ").length >= 4;
|
|
324
|
-
const hasIconPattern = code.includes("getIcon") || code.includes("getColor") || code.includes("getLabel") || code.includes("getRating");
|
|
325
|
-
return hasSwitchPattern && hasIconPattern;
|
|
326
|
-
},
|
|
327
|
-
severity: import_core4.Severity.Info,
|
|
557
|
+
detect: (file, code) => CodePatterns.hasSwitchPattern(code) && CodePatterns.hasIconPattern(code),
|
|
328
558
|
reason: "Switch statement helpers for enum-to-value mapping are inherently similar",
|
|
329
559
|
suggestion: "Switch duplication is acceptable for mapping enums to display values"
|
|
330
|
-
},
|
|
331
|
-
// Common API/Utility
|
|
332
|
-
{
|
|
560
|
+
}),
|
|
561
|
+
// Common API/Utility functions - legitimately duplicated across modules
|
|
562
|
+
createRule({
|
|
333
563
|
name: "common-api-functions",
|
|
334
|
-
detect: (file, code) =>
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
reason: "Common API/utility functions are legitimately duplicated across modules for clarity and independence",
|
|
341
|
-
suggestion: "Consider extracting to shared utilities only if causing significant duplication"
|
|
342
|
-
},
|
|
343
|
-
// Validation Functions - Inherently similar patterns
|
|
344
|
-
{
|
|
564
|
+
detect: (file, code) => FileDetectors.isApiFile(file) && ApiPatterns.hasCommonApiPattern(code),
|
|
565
|
+
...RuleTemplates.commonApiFunction
|
|
566
|
+
}),
|
|
567
|
+
// Validation functions - inherently similar patterns
|
|
568
|
+
// PHASE 2: Consolidated validation-function branches into single rule
|
|
569
|
+
createRule({
|
|
345
570
|
name: "validation-functions",
|
|
346
|
-
detect: (file, code) =>
|
|
347
|
-
const hasValidationPattern = code.includes("isValid") || code.includes("validate") || code.includes("checkValid") || code.includes("isEmail") || code.includes("isPhone") || code.includes("isUrl") || code.includes("isNumeric") || code.includes("isAlpha") || code.includes("isAlphanumeric") || code.includes("isEmpty") || code.includes("isNotEmpty") || code.includes("isRequired") || code.includes("isOptional");
|
|
348
|
-
return hasValidationPattern;
|
|
349
|
-
},
|
|
350
|
-
severity: import_core4.Severity.Info,
|
|
571
|
+
detect: (file, code) => CodePatterns.hasValidationPattern(code),
|
|
351
572
|
reason: "Validation functions are inherently similar and often intentionally duplicated for domain clarity",
|
|
352
573
|
suggestion: "Consider extracting to shared validators only if validation logic becomes complex"
|
|
353
|
-
},
|
|
354
|
-
// Singleton
|
|
355
|
-
{
|
|
574
|
+
}),
|
|
575
|
+
// Singleton getter pattern - standard initialization
|
|
576
|
+
createRule({
|
|
356
577
|
name: "singleton-getter",
|
|
357
|
-
detect: (file, code) =>
|
|
358
|
-
const hasSingletonGetter = /(?:export\s+)?(?:async\s+)?function\s+get[A-Z][a-zA-Z0-9]*\s*\(/.test(
|
|
359
|
-
code
|
|
360
|
-
) || /(?:export\s+)?const\s+get[A-Z][a-zA-Z0-9]*\s*=\s*(?:async\s+)?\(\)\s*=>/.test(
|
|
361
|
-
code
|
|
362
|
-
);
|
|
363
|
-
const hasSingletonPattern = code.includes("if (!") && code.includes("instance") && code.includes(" = ") || code.includes("if (!_") && code.includes(" = new ") || code.includes("if (") && code.includes(" === null") && code.includes(" = new ");
|
|
364
|
-
return hasSingletonGetter && hasSingletonPattern;
|
|
365
|
-
},
|
|
366
|
-
severity: import_core4.Severity.Info,
|
|
578
|
+
detect: (file, code) => CodePatterns.hasSingletonGetter(code) && CodePatterns.hasSingletonPattern(code),
|
|
367
579
|
reason: "Singleton getter functions follow standard initialization pattern and are intentionally similar",
|
|
368
580
|
suggestion: "Singleton getters are boilerplate and acceptable duplication for lazy initialization"
|
|
369
|
-
}
|
|
581
|
+
})
|
|
370
582
|
];
|
|
371
583
|
|
|
372
584
|
// src/context-rules.ts
|
|
@@ -399,7 +611,7 @@ function calculateSeverity(file1, file2, code, similarity, linesOfCode) {
|
|
|
399
611
|
reason: "Nearly identical code should be consolidated",
|
|
400
612
|
suggestion: "Move to shared utility file"
|
|
401
613
|
};
|
|
402
|
-
} else if (similarity >= 0.85) {
|
|
614
|
+
} else if (similarity >= 0.85 && linesOfCode >= 10) {
|
|
403
615
|
return {
|
|
404
616
|
severity: import_core5.Severity.Major,
|
|
405
617
|
reason: "High similarity indicates significant duplication",
|
|
@@ -453,7 +665,10 @@ async function detectDuplicatePatterns(fileContents, options) {
|
|
|
453
665
|
ignoreWhitelist = []
|
|
454
666
|
} = options;
|
|
455
667
|
const allBlocks = [];
|
|
456
|
-
const
|
|
668
|
+
const regexStr = (f) => f.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, "[^/]*").replace(/\[\^\/\]\*\[\^\/\]\*/g, ".*");
|
|
669
|
+
const excludeRegexes = excludePatterns.map(
|
|
670
|
+
(p) => new RegExp(`${regexStr(p)}$`, "i")
|
|
671
|
+
);
|
|
457
672
|
for (const { file, content } of fileContents) {
|
|
458
673
|
const blocks = extractBlocks(file, content);
|
|
459
674
|
for (const b of blocks) {
|
|
@@ -942,6 +1157,7 @@ async function analyzePatterns(options) {
|
|
|
942
1157
|
minClusterTokenCost = 1e3,
|
|
943
1158
|
minClusterFiles = 3,
|
|
944
1159
|
excludePatterns = [],
|
|
1160
|
+
excludeFiles = [],
|
|
945
1161
|
confidenceThreshold = 0,
|
|
946
1162
|
ignoreWhitelist = [],
|
|
947
1163
|
...scanOptions
|
|
@@ -971,6 +1187,7 @@ async function analyzePatterns(options) {
|
|
|
971
1187
|
maxCandidatesPerBlock,
|
|
972
1188
|
streamResults,
|
|
973
1189
|
excludePatterns,
|
|
1190
|
+
excludeFiles,
|
|
974
1191
|
confidenceThreshold,
|
|
975
1192
|
ignoreWhitelist,
|
|
976
1193
|
onProgress: options.onProgress
|
package/dist/cli.mjs
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
analyzePatterns,
|
|
4
4
|
generateSummary
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-WQC43BIO.mjs";
|
|
6
|
+
import "./chunk-XR373Q6G.mjs";
|
|
7
7
|
import {
|
|
8
8
|
filterBySeverity
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-XWIBTD67.mjs";
|
|
10
|
+
import "./chunk-UXV57HN3.mjs";
|
|
11
11
|
|
|
12
12
|
// src/cli.ts
|
|
13
13
|
import { Command } from "commander";
|