@anatolykoptev/krolik-cli 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/cli.js CHANGED
@@ -8,7 +8,7 @@ import { SyntaxKind, Project, Node, DiagnosticCategory } from 'ts-morph';
8
8
  import * as crypto from 'crypto';
9
9
  import { createHash } from 'crypto';
10
10
  import { parseSync } from '@swc/core';
11
- import { spawnSync, spawn, execSync } from 'child_process';
11
+ import { spawnSync, spawn, execFileSync, execSync } from 'child_process';
12
12
  import { defaultOptions, minify } from 'minify-xml';
13
13
  import { homedir } from 'os';
14
14
  import Database from 'better-sqlite3';
@@ -56,7 +56,7 @@ var KROLIK_VERSION, TEMPLATE_VERSION;
56
56
  var init_version = __esm({
57
57
  "src/version.ts"() {
58
58
  init_esm_shims();
59
- KROLIK_VERSION = "0.5.0";
59
+ KROLIK_VERSION = "0.7.0";
60
60
  TEMPLATE_VERSION = "6.0.0";
61
61
  }
62
62
  });
@@ -1394,6 +1394,279 @@ var init_signatures = __esm({
1394
1394
  }
1395
1395
  });
1396
1396
 
1397
+ // src/lib/@ast/fingerprint/normalize.ts
1398
+ function normalizeAst(node, options = {}, depth = 0) {
1399
+ const opts = { ...DEFAULT_OPTIONS, ...options };
1400
+ if (depth > opts.maxDepth) {
1401
+ return { type: "$MAX_DEPTH" };
1402
+ }
1403
+ if (node === null || node === void 0) {
1404
+ return null;
1405
+ }
1406
+ if (typeof node !== "object") {
1407
+ return node;
1408
+ }
1409
+ if (Array.isArray(node)) {
1410
+ return node.map((item) => normalizeAst(item, opts, depth + 1));
1411
+ }
1412
+ const obj = node;
1413
+ const nodeType = obj.type;
1414
+ if (nodeType) {
1415
+ return normalizeNodeByType(obj, nodeType, opts, depth);
1416
+ }
1417
+ return normalizeGenericObject(obj, opts, depth);
1418
+ }
1419
+ function normalizeNodeByType(node, nodeType, opts, depth) {
1420
+ if (nodeType === "Identifier" && opts.normalizeIdentifiers) {
1421
+ return { type: "Identifier", name: PLACEHOLDERS.IDENTIFIER };
1422
+ }
1423
+ if (nodeType === "StringLiteral" && opts.normalizeStrings) {
1424
+ return { type: "StringLiteral", value: PLACEHOLDERS.STRING };
1425
+ }
1426
+ if ((nodeType === "NumericLiteral" || nodeType === "NumberLiteral") && opts.normalizeNumbers) {
1427
+ return { type: "NumericLiteral", value: PLACEHOLDERS.NUMBER };
1428
+ }
1429
+ if (nodeType === "BigIntLiteral" && opts.normalizeNumbers) {
1430
+ return { type: "BigIntLiteral", value: PLACEHOLDERS.NUMBER };
1431
+ }
1432
+ if (nodeType === "RegExpLiteral") {
1433
+ const flags = node.flags;
1434
+ return { type: "RegExpLiteral", pattern: PLACEHOLDERS.REGEX, flags };
1435
+ }
1436
+ if (nodeType === "TemplateLiteral" && opts.normalizeStrings) {
1437
+ const quasis = node.quasis;
1438
+ const expressions = node.expressions;
1439
+ return {
1440
+ type: "TemplateLiteral",
1441
+ quasis: quasis?.map(() => ({ type: "TemplateElement", value: PLACEHOLDERS.TEMPLATE })),
1442
+ expressions: expressions?.map((e) => normalizeAst(e, opts, depth + 1))
1443
+ };
1444
+ }
1445
+ if (nodeType === "JSXText" && opts.normalizeStrings) {
1446
+ return { type: "JSXText", value: PLACEHOLDERS.STRING };
1447
+ }
1448
+ return normalizeGenericObject(node, opts, depth);
1449
+ }
1450
+ function normalizeGenericObject(obj, opts, depth) {
1451
+ const result = {};
1452
+ for (const key of Object.keys(obj)) {
1453
+ if (SKIP_KEYS.has(key)) continue;
1454
+ const value = obj[key];
1455
+ if (key === "type") {
1456
+ if (opts.includeTypes) {
1457
+ result[key] = value;
1458
+ }
1459
+ continue;
1460
+ }
1461
+ result[key] = normalizeAst(value, opts, depth + 1);
1462
+ }
1463
+ return result;
1464
+ }
1465
+ function astToTokens(node, options = {}, tokens = [], depth = 0) {
1466
+ const opts = { ...DEFAULT_OPTIONS, ...options };
1467
+ if (depth > opts.maxDepth) {
1468
+ tokens.push("$MAX");
1469
+ return tokens;
1470
+ }
1471
+ if (node === null || node === void 0) {
1472
+ return tokens;
1473
+ }
1474
+ if (typeof node !== "object") {
1475
+ if (typeof node === "string" && opts.normalizeStrings) {
1476
+ tokens.push(PLACEHOLDERS.STRING);
1477
+ } else if (typeof node === "number" && opts.normalizeNumbers) {
1478
+ tokens.push(PLACEHOLDERS.NUMBER);
1479
+ } else if (typeof node === "boolean") {
1480
+ tokens.push(node ? "TRUE" : "FALSE");
1481
+ }
1482
+ return tokens;
1483
+ }
1484
+ if (Array.isArray(node)) {
1485
+ tokens.push("[");
1486
+ for (const item of node) {
1487
+ astToTokens(item, opts, tokens, depth + 1);
1488
+ }
1489
+ tokens.push("]");
1490
+ return tokens;
1491
+ }
1492
+ const obj = node;
1493
+ const nodeType = obj.type;
1494
+ if (nodeType) {
1495
+ tokens.push(nodeType);
1496
+ if (nodeType === "Identifier" && opts.normalizeIdentifiers) {
1497
+ tokens.push(PLACEHOLDERS.IDENTIFIER);
1498
+ return tokens;
1499
+ }
1500
+ if (nodeType === "StringLiteral" && opts.normalizeStrings) {
1501
+ tokens.push(PLACEHOLDERS.STRING);
1502
+ return tokens;
1503
+ }
1504
+ if ((nodeType === "NumericLiteral" || nodeType === "NumberLiteral") && opts.normalizeNumbers) {
1505
+ tokens.push(PLACEHOLDERS.NUMBER);
1506
+ return tokens;
1507
+ }
1508
+ }
1509
+ tokens.push("{");
1510
+ for (const key of Object.keys(obj)) {
1511
+ if (SKIP_KEYS.has(key) || key === "type") continue;
1512
+ const value = obj[key];
1513
+ if (value !== null && value !== void 0) {
1514
+ tokens.push(`${key}:`);
1515
+ astToTokens(value, opts, tokens, depth + 1);
1516
+ }
1517
+ }
1518
+ tokens.push("}");
1519
+ return tokens;
1520
+ }
1521
+ var DEFAULT_OPTIONS, PLACEHOLDERS, SKIP_KEYS;
1522
+ var init_normalize = __esm({
1523
+ "src/lib/@ast/fingerprint/normalize.ts"() {
1524
+ init_esm_shims();
1525
+ DEFAULT_OPTIONS = {
1526
+ normalizeIdentifiers: true,
1527
+ normalizeStrings: true,
1528
+ normalizeNumbers: true,
1529
+ includeTypes: true,
1530
+ maxDepth: 50
1531
+ };
1532
+ PLACEHOLDERS = {
1533
+ IDENTIFIER: "$ID",
1534
+ STRING: "$STR",
1535
+ NUMBER: "$NUM",
1536
+ REGEX: "$RGX",
1537
+ TEMPLATE: "$TPL"
1538
+ };
1539
+ SKIP_KEYS = /* @__PURE__ */ new Set([
1540
+ "span",
1541
+ "start",
1542
+ "end",
1543
+ "loc",
1544
+ "range",
1545
+ "comments",
1546
+ "leadingComments",
1547
+ "trailingComments",
1548
+ "innerComments",
1549
+ "extra",
1550
+ "raw",
1551
+ "rawValue"
1552
+ ]);
1553
+ }
1554
+ });
1555
+ function generateFingerprint(code, options = {}) {
1556
+ const opts = { ...DEFAULT_OPTIONS2, ...options };
1557
+ try {
1558
+ const ast = parseSync(code, {
1559
+ syntax: "typescript",
1560
+ tsx: true
1561
+ });
1562
+ return generateFingerprintFromAst(ast, opts);
1563
+ } catch {
1564
+ return {
1565
+ fingerprint: "",
1566
+ tokenCount: 0,
1567
+ complexity: 0,
1568
+ tooSmall: true
1569
+ };
1570
+ }
1571
+ }
1572
+ function generateFingerprintFromAst(ast, options = {}) {
1573
+ const opts = { ...DEFAULT_OPTIONS2, ...options };
1574
+ if (opts.tokenBased) {
1575
+ const tokens2 = astToTokens(ast, opts);
1576
+ const tokenCount2 = tokens2.length;
1577
+ if (tokenCount2 < (opts.minTokens ?? 10)) {
1578
+ return {
1579
+ fingerprint: "",
1580
+ tokenCount: tokenCount2,
1581
+ complexity: calculateComplexity(tokens2),
1582
+ tooSmall: true
1583
+ };
1584
+ }
1585
+ const tokenString = tokens2.join(" ");
1586
+ const fingerprint2 = crypto.createHash("md5").update(tokenString).digest("hex");
1587
+ return {
1588
+ fingerprint: fingerprint2,
1589
+ tokenCount: tokenCount2,
1590
+ complexity: calculateComplexity(tokens2),
1591
+ tooSmall: false
1592
+ };
1593
+ }
1594
+ const normalized = normalizeAst(ast, opts);
1595
+ const serialized = JSON.stringify(normalized, null, 0);
1596
+ const tokens = astToTokens(ast, opts);
1597
+ const tokenCount = tokens.length;
1598
+ if (tokenCount < (opts.minTokens ?? 10)) {
1599
+ return {
1600
+ fingerprint: "",
1601
+ tokenCount,
1602
+ complexity: calculateComplexity(tokens),
1603
+ tooSmall: true
1604
+ };
1605
+ }
1606
+ const fingerprint = crypto.createHash("md5").update(serialized).digest("hex");
1607
+ return {
1608
+ fingerprint,
1609
+ tokenCount,
1610
+ complexity: calculateComplexity(tokens),
1611
+ tooSmall: false
1612
+ };
1613
+ }
1614
+ function calculateComplexity(tokens) {
1615
+ let complexity = 0;
1616
+ complexity += Math.log2(tokens.length + 1) * 10;
1617
+ const controlFlow = [
1618
+ "IfStatement",
1619
+ "ForStatement",
1620
+ "WhileStatement",
1621
+ "SwitchStatement",
1622
+ "TryStatement"
1623
+ ];
1624
+ for (const token of tokens) {
1625
+ if (controlFlow.includes(token)) {
1626
+ complexity += 5;
1627
+ }
1628
+ }
1629
+ const callCount = tokens.filter((t) => t === "CallExpression").length;
1630
+ complexity += callCount * 2;
1631
+ let maxDepth = 0;
1632
+ let currentDepth = 0;
1633
+ for (const token of tokens) {
1634
+ if (token === "{" || token === "[") {
1635
+ currentDepth++;
1636
+ maxDepth = Math.max(maxDepth, currentDepth);
1637
+ } else if (token === "}" || token === "]") {
1638
+ currentDepth--;
1639
+ }
1640
+ }
1641
+ complexity += maxDepth * 3;
1642
+ return Math.round(complexity);
1643
+ }
1644
+ var DEFAULT_OPTIONS2;
1645
+ var init_fingerprint = __esm({
1646
+ "src/lib/@ast/fingerprint/fingerprint.ts"() {
1647
+ init_esm_shims();
1648
+ init_normalize();
1649
+ DEFAULT_OPTIONS2 = {
1650
+ normalizeIdentifiers: true,
1651
+ normalizeStrings: true,
1652
+ normalizeNumbers: true,
1653
+ includeTypes: true,
1654
+ maxDepth: 50,
1655
+ tokenBased: false,
1656
+ minTokens: 10
1657
+ };
1658
+ }
1659
+ });
1660
+
1661
+ // src/lib/@ast/fingerprint/index.ts
1662
+ var init_fingerprint2 = __esm({
1663
+ "src/lib/@ast/fingerprint/index.ts"() {
1664
+ init_esm_shims();
1665
+ init_fingerprint();
1666
+ init_normalize();
1667
+ }
1668
+ });
1669
+
1397
1670
  // src/lib/@ast/index.ts
1398
1671
  var init_ast = __esm({
1399
1672
  "src/lib/@ast/index.ts"() {
@@ -1401,6 +1674,7 @@ var init_ast = __esm({
1401
1674
  init_ts_morph();
1402
1675
  init_analysis2();
1403
1676
  init_signatures();
1677
+ init_fingerprint2();
1404
1678
  }
1405
1679
  });
1406
1680
 
@@ -1686,6 +1960,85 @@ var init_imports = __esm({
1686
1960
  });
1687
1961
 
1688
1962
  // src/lib/@ast/swc/string-context.ts
1963
+ function isInsideComment2(content, offset) {
1964
+ let inSingleQuote = false;
1965
+ let inDoubleQuote = false;
1966
+ let inTemplate = false;
1967
+ let templateBraceDepth = 0;
1968
+ let inLineComment = false;
1969
+ let inBlockComment = false;
1970
+ let escaped = false;
1971
+ for (let i = 0; i < offset && i < content.length; i++) {
1972
+ const char = content[i];
1973
+ const nextChar = content[i + 1];
1974
+ if (escaped) {
1975
+ escaped = false;
1976
+ continue;
1977
+ }
1978
+ if (char === "\\" && (inSingleQuote || inDoubleQuote || inTemplate)) {
1979
+ escaped = true;
1980
+ continue;
1981
+ }
1982
+ if (inLineComment) {
1983
+ if (char === "\n") {
1984
+ inLineComment = false;
1985
+ }
1986
+ continue;
1987
+ }
1988
+ if (inBlockComment) {
1989
+ if (char === "*" && nextChar === "/") {
1990
+ inBlockComment = false;
1991
+ i++;
1992
+ }
1993
+ continue;
1994
+ }
1995
+ if (!inLineComment && !inBlockComment) {
1996
+ if (inTemplate && templateBraceDepth === 0 && char === "$" && nextChar === "{") {
1997
+ templateBraceDepth = 1;
1998
+ i++;
1999
+ continue;
2000
+ }
2001
+ if (inTemplate && templateBraceDepth > 0) {
2002
+ if (char === "{") {
2003
+ templateBraceDepth++;
2004
+ } else if (char === "}") {
2005
+ templateBraceDepth--;
2006
+ }
2007
+ if (templateBraceDepth > 0 && char === "/" && nextChar === "/") {
2008
+ inLineComment = true;
2009
+ i++;
2010
+ continue;
2011
+ }
2012
+ if (templateBraceDepth > 0 && char === "/" && nextChar === "*") {
2013
+ inBlockComment = true;
2014
+ i++;
2015
+ continue;
2016
+ }
2017
+ continue;
2018
+ }
2019
+ if (char === "'" && !inDoubleQuote && !inTemplate) {
2020
+ inSingleQuote = !inSingleQuote;
2021
+ } else if (char === '"' && !inSingleQuote && !inTemplate) {
2022
+ inDoubleQuote = !inDoubleQuote;
2023
+ } else if (char === "`" && !inSingleQuote && !inDoubleQuote) {
2024
+ inTemplate = !inTemplate;
2025
+ if (!inTemplate) templateBraceDepth = 0;
2026
+ }
2027
+ if (!inSingleQuote && !inDoubleQuote && !inTemplate) {
2028
+ if (char === "/" && nextChar === "/") {
2029
+ inLineComment = true;
2030
+ i++;
2031
+ continue;
2032
+ }
2033
+ if (char === "/" && nextChar === "*") {
2034
+ inBlockComment = true;
2035
+ i++;
2036
+ }
2037
+ }
2038
+ }
2039
+ }
2040
+ return inLineComment || inBlockComment;
2041
+ }
1689
2042
  function isInsideStringOrComment(content, offset) {
1690
2043
  let inSingleQuote = false;
1691
2044
  let inDoubleQuote = false;
@@ -3107,92 +3460,103 @@ var init_browser_apis = __esm({
3107
3460
  });
3108
3461
 
3109
3462
  // src/lib/@detectors/lint/lint-detector.ts
3110
- function detectLintIssue(node) {
3111
- const nodeType = node.type;
3112
- const span = node.span;
3113
- if (!span) {
3463
+ function detectDebuggerStatement(nodeType, offset) {
3464
+ if (nodeType === "DebuggerStatement") {
3465
+ return { type: "debugger", offset };
3466
+ }
3467
+ return null;
3468
+ }
3469
+ function detectEmptyCatchBlock(node, nodeType, offset) {
3470
+ if (nodeType !== "CatchClause") {
3114
3471
  return null;
3115
3472
  }
3116
- if (nodeType === "DebuggerStatement") {
3117
- return {
3118
- type: "debugger",
3119
- offset: span.start
3120
- };
3473
+ const catchClause = node;
3474
+ const body = catchClause.body;
3475
+ if (!body || body.type !== "BlockStatement") {
3476
+ return null;
3121
3477
  }
3122
- if (nodeType === "CatchClause") {
3123
- const catchClause = node;
3124
- const body = catchClause.body;
3125
- if (body && body.type === "BlockStatement") {
3126
- const stmts = body.stmts ?? [];
3127
- if (stmts.length === 0) {
3128
- return {
3129
- type: "empty-catch",
3130
- offset: span.start
3131
- };
3132
- }
3133
- if (stmts.length === 1) {
3134
- const stmt = stmts[0];
3135
- if (stmt.type === "ReturnStatement") {
3136
- return {
3137
- type: "empty-catch",
3138
- offset: span.start
3139
- };
3140
- }
3141
- }
3142
- }
3478
+ const stmts = body.stmts ?? [];
3479
+ if (stmts.length === 0) {
3480
+ return { type: "empty-catch", offset };
3143
3481
  }
3144
- if (nodeType === "CallExpression" || nodeType === "OptionalChainingExpression") {
3145
- const callExpr = node;
3146
- const callee = callExpr.callee ?? callExpr.base;
3147
- if (!callee) {
3148
- return null;
3149
- }
3150
- const calleeType = callee.type;
3151
- if (calleeType === "MemberExpression") {
3152
- const memberExpr = callee;
3153
- const object = memberExpr.object;
3154
- const property = memberExpr.property;
3155
- if (!object || !property) {
3156
- return null;
3157
- }
3158
- const objectType = object.type;
3159
- const propertyType = property.type;
3160
- if (objectType === "Identifier" && propertyType === "Identifier") {
3161
- const objectValue = object.value;
3162
- const propertyValue = property.value;
3163
- if (objectValue && propertyValue && isConsoleMember(propertyValue, objectValue)) {
3164
- return {
3165
- type: "console",
3166
- offset: span.start,
3167
- method: propertyValue
3168
- };
3169
- }
3170
- }
3171
- }
3172
- if (calleeType === "Identifier") {
3173
- const identifier = callee;
3174
- const identifierValue = identifier.value;
3175
- if (!identifierValue) {
3176
- return null;
3177
- }
3178
- if (isDialogFunction(identifierValue)) {
3179
- return {
3180
- type: "alert",
3181
- offset: span.start,
3182
- method: identifierValue
3183
- };
3184
- }
3185
- if (isEvalFunction(identifierValue)) {
3186
- return {
3187
- type: "eval",
3188
- offset: span.start,
3189
- method: identifierValue
3190
- };
3191
- }
3482
+ if (stmts.length === 1) {
3483
+ const stmt = stmts[0];
3484
+ if (stmt.type === "ReturnStatement") {
3485
+ return { type: "empty-catch", offset };
3192
3486
  }
3193
3487
  }
3194
3488
  return null;
3195
3489
  }
3490
+ function extractCallee(node, nodeType) {
3491
+ if (nodeType !== "CallExpression" && nodeType !== "OptionalChainingExpression") {
3492
+ return null;
3493
+ }
3494
+ const callExpr = node;
3495
+ return callExpr.callee ?? callExpr.base ?? null;
3496
+ }
3497
+ function detectConsoleCall(callee, offset) {
3498
+ const calleeType = callee.type;
3499
+ if (calleeType !== "MemberExpression") {
3500
+ return null;
3501
+ }
3502
+ const memberExpr = callee;
3503
+ const object = memberExpr.object;
3504
+ const property = memberExpr.property;
3505
+ if (!object || !property) {
3506
+ return null;
3507
+ }
3508
+ const objectNode = object;
3509
+ const propertyNode = property;
3510
+ if (objectNode.type !== "Identifier" || propertyNode.type !== "Identifier") {
3511
+ return null;
3512
+ }
3513
+ const objectValue = objectNode.value;
3514
+ const propertyValue = propertyNode.value;
3515
+ if (!objectValue || !propertyValue) {
3516
+ return null;
3517
+ }
3518
+ if (isConsoleMember(propertyValue, objectValue)) {
3519
+ return { type: "console", offset, method: propertyValue };
3520
+ }
3521
+ return null;
3522
+ }
3523
+ function detectGlobalFunctionCall(callee, offset) {
3524
+ const calleeType = callee.type;
3525
+ if (calleeType !== "Identifier") {
3526
+ return null;
3527
+ }
3528
+ const identifier = callee;
3529
+ const name = identifier.value;
3530
+ if (!name) {
3531
+ return null;
3532
+ }
3533
+ if (isDialogFunction(name)) {
3534
+ return { type: "alert", offset, method: name };
3535
+ }
3536
+ if (isEvalFunction(name)) {
3537
+ return { type: "eval", offset, method: name };
3538
+ }
3539
+ return null;
3540
+ }
3541
+ function detectLintIssue(node) {
3542
+ const nodeType = node.type;
3543
+ const span = node.span;
3544
+ if (!span || !nodeType) {
3545
+ return null;
3546
+ }
3547
+ const offset = span.start;
3548
+ const debuggerResult = detectDebuggerStatement(nodeType, offset);
3549
+ if (debuggerResult) return debuggerResult;
3550
+ const emptyCatchResult = detectEmptyCatchBlock(node, nodeType, offset);
3551
+ if (emptyCatchResult) return emptyCatchResult;
3552
+ const callee = extractCallee(node, nodeType);
3553
+ if (!callee) return null;
3554
+ const consoleResult = detectConsoleCall(callee, offset);
3555
+ if (consoleResult) return consoleResult;
3556
+ const globalResult = detectGlobalFunctionCall(callee, offset);
3557
+ if (globalResult) return globalResult;
3558
+ return null;
3559
+ }
3196
3560
  var init_lint_detector = __esm({
3197
3561
  "src/lib/@detectors/lint/lint-detector.ts"() {
3198
3562
  init_esm_shims();
@@ -3202,117 +3566,119 @@ var init_lint_detector = __esm({
3202
3566
 
3203
3567
  // src/lib/@detectors/lint/type-safety-detector.ts
3204
3568
  function isAnyType(node) {
3205
- const nodeType = node.type;
3206
- if (nodeType === "TsKeywordType") {
3207
- const kind = node.kind;
3208
- return kind === "any";
3569
+ const typedNode = node;
3570
+ return typedNode.type === "TsKeywordType" && typedNode.kind === "any";
3571
+ }
3572
+ function createDetection(type, offset) {
3573
+ return { type, offset };
3574
+ }
3575
+ function detectTypeAnnotation(node, span) {
3576
+ if (node.type !== "TsTypeAnnotation") return null;
3577
+ const typeAnnotation = node.typeAnnotation;
3578
+ if (!typeAnnotation || !isAnyType(typeAnnotation)) return null;
3579
+ return createDetection("any-annotation", span.start);
3580
+ }
3581
+ function detectAsAnyAssertion(node, span) {
3582
+ if (node.type !== "TsAsExpression") return null;
3583
+ const typeAnnotation = node.typeAnnotation;
3584
+ if (!typeAnnotation || !isAnyType(typeAnnotation)) return null;
3585
+ return createDetection("any-assertion", span.start);
3586
+ }
3587
+ function detectDoubleAssertionPattern(node, span) {
3588
+ if (node.type !== "TsAsExpression") return null;
3589
+ const expression = node.expression;
3590
+ if (!expression || expression.type !== "TsAsExpression") return null;
3591
+ const innerTypeAnnotation = expression.typeAnnotation;
3592
+ if (!innerTypeAnnotation) return null;
3593
+ const isUnknownAssertion = innerTypeAnnotation.type === "TsKeywordType" && innerTypeAnnotation.kind === "unknown";
3594
+ if (!isUnknownAssertion) return null;
3595
+ return createDetection("double-assertion", span.start);
3596
+ }
3597
+ function detectNonNullAssertionHandler(node, span) {
3598
+ if (node.type !== "TsNonNullExpression") return null;
3599
+ return createDetection("non-null", span.start);
3600
+ }
3601
+ function detectAnyInTypeParams(node, span) {
3602
+ if (node.type !== "TsTypeReference") return null;
3603
+ const params = node.typeParams?.params;
3604
+ if (!params) return null;
3605
+ for (const param of params) {
3606
+ if (isAnyType(param)) {
3607
+ return createDetection("any-annotation", span.start);
3608
+ }
3209
3609
  }
3210
- return false;
3610
+ return null;
3611
+ }
3612
+ function detectAnyInReturnType(node, span) {
3613
+ const functionTypes = ["TsFunctionType", "TsConstructorType", "TsMethodSignature"];
3614
+ if (!functionTypes.includes(node.type ?? "")) return null;
3615
+ const typeAnnotation = node.typeAnnotation;
3616
+ if (!typeAnnotation) return null;
3617
+ const returnType = typeAnnotation.typeAnnotation;
3618
+ if (!returnType || !isAnyType(returnType)) return null;
3619
+ return createDetection("any-annotation", span.start);
3620
+ }
3621
+ function detectAnyInParamType(node, span) {
3622
+ if (node.type !== "Parameter") return null;
3623
+ const typeAnnotation = node.typeAnnotation;
3624
+ if (!typeAnnotation) return null;
3625
+ const paramType = typeAnnotation.typeAnnotation;
3626
+ if (!paramType || !isAnyType(paramType)) return null;
3627
+ return createDetection("any-param", span.start);
3628
+ }
3629
+ function detectAnyInArrayType(node, span) {
3630
+ if (node.type !== "TsArrayType") return null;
3631
+ const elemType = node.elemType;
3632
+ if (!elemType || !isAnyType(elemType)) return null;
3633
+ return createDetection("any-array", span.start);
3211
3634
  }
3212
3635
  function detectTypeSafetyIssue(node) {
3213
- const nodeType = node.type;
3214
- const span = node.span;
3215
- if (!span) return null;
3216
- if (nodeType === "TsTypeAnnotation") {
3217
- const typeAnnotation = node.typeAnnotation;
3218
- if (typeAnnotation && isAnyType(typeAnnotation)) {
3219
- return {
3220
- type: "any-annotation",
3221
- offset: span.start
3222
- };
3223
- }
3224
- }
3225
- if (nodeType === "TsAsExpression") {
3226
- const typeAnnotation = node.typeAnnotation;
3227
- const expression = node.expression;
3228
- if (typeAnnotation && isAnyType(typeAnnotation)) {
3229
- return {
3230
- type: "any-assertion",
3231
- offset: span.start
3232
- };
3233
- }
3234
- if (expression && expression.type === "TsAsExpression") {
3235
- const innerTypeAnnotation = expression.typeAnnotation;
3236
- if (innerTypeAnnotation) {
3237
- const innerType = innerTypeAnnotation.type;
3238
- const innerKind = innerTypeAnnotation.kind;
3239
- if (innerType === "TsKeywordType" && innerKind === "unknown") {
3240
- return {
3241
- type: "double-assertion",
3242
- offset: span.start
3243
- };
3244
- }
3245
- }
3246
- }
3247
- }
3248
- if (nodeType === "TsNonNullExpression") {
3249
- return {
3250
- type: "non-null",
3251
- offset: span.start
3252
- };
3253
- }
3254
- if (nodeType === "TsTypeReference") {
3255
- const typeParams = node.typeParams;
3256
- if (typeParams?.params) {
3257
- for (const param of typeParams.params) {
3258
- if (isAnyType(param)) {
3259
- return {
3260
- type: "any-annotation",
3261
- offset: span.start
3262
- };
3263
- }
3264
- }
3265
- }
3266
- }
3267
- if (nodeType === "TsFunctionType" || nodeType === "TsConstructorType" || nodeType === "TsMethodSignature") {
3268
- const typeAnnotation = node.typeAnnotation;
3269
- if (typeAnnotation) {
3270
- const returnType = typeAnnotation.typeAnnotation;
3271
- if (returnType && isAnyType(returnType)) {
3272
- return {
3273
- type: "any-annotation",
3274
- offset: span.start
3275
- };
3276
- }
3277
- }
3278
- }
3279
- if (nodeType === "Parameter") {
3280
- const typeAnnotation = node.typeAnnotation;
3281
- if (typeAnnotation) {
3282
- const paramType = typeAnnotation.typeAnnotation;
3283
- if (paramType && isAnyType(paramType)) {
3284
- return {
3285
- type: "any-param",
3286
- offset: span.start
3287
- };
3288
- }
3289
- }
3290
- }
3291
- if (nodeType === "TsArrayType") {
3292
- const elemType = node.elemType;
3293
- if (elemType && isAnyType(elemType)) {
3294
- return {
3295
- type: "any-array",
3296
- offset: span.start
3297
- };
3298
- }
3636
+ const typedNode = node;
3637
+ const { type: nodeType, span } = typedNode;
3638
+ if (!span || !nodeType) return null;
3639
+ const handlers = detectionStrategies.get(nodeType);
3640
+ if (!handlers) return null;
3641
+ for (const handler of handlers) {
3642
+ const result = handler(typedNode, span);
3643
+ if (result) return result;
3299
3644
  }
3300
3645
  return null;
3301
3646
  }
3647
+ var detectionStrategies;
3302
3648
  var init_type_safety_detector = __esm({
3303
3649
  "src/lib/@detectors/lint/type-safety-detector.ts"() {
3304
3650
  init_esm_shims();
3651
+ detectionStrategies = /* @__PURE__ */ new Map([
3652
+ ["TsTypeAnnotation", [detectTypeAnnotation]],
3653
+ ["TsAsExpression", [detectAsAnyAssertion, detectDoubleAssertionPattern]],
3654
+ ["TsNonNullExpression", [detectNonNullAssertionHandler]],
3655
+ ["TsTypeReference", [detectAnyInTypeParams]],
3656
+ ["TsFunctionType", [detectAnyInReturnType]],
3657
+ ["TsConstructorType", [detectAnyInReturnType]],
3658
+ ["TsMethodSignature", [detectAnyInReturnType]],
3659
+ ["Parameter", [detectAnyInParamType]],
3660
+ ["TsArrayType", [detectAnyInArrayType]]
3661
+ ]);
3305
3662
  }
3306
3663
  });
3307
3664
 
3308
3665
  // src/lib/@detectors/security/security-detector.ts
3309
- function detectSecurityIssue(node) {
3310
- const nodeType = node.type;
3311
- const span = node.span;
3312
- if (!span) {
3313
- return null;
3314
- }
3315
- if (nodeType !== "CallExpression") {
3666
+ function getNodeType2(node) {
3667
+ return node?.type;
3668
+ }
3669
+ function getNodeSpan2(node) {
3670
+ return node.span;
3671
+ }
3672
+ function getIdentifierValue(node) {
3673
+ return node.value;
3674
+ }
3675
+ function getArgumentExpression(arg) {
3676
+ return arg.expression;
3677
+ }
3678
+ function parseCallExpression(node) {
3679
+ const nodeType = getNodeType2(node);
3680
+ const span = getNodeSpan2(node);
3681
+ if (!span || nodeType !== "CallExpression") {
3316
3682
  return null;
3317
3683
  }
3318
3684
  const callExpr = node;
@@ -3320,82 +3686,132 @@ function detectSecurityIssue(node) {
3320
3686
  if (!callee) {
3321
3687
  return null;
3322
3688
  }
3323
- const calleeType = callee.type;
3324
- if (calleeType === "Identifier") {
3325
- const identifier = callee;
3326
- const identifierValue = identifier.value;
3327
- if (identifierValue && COMMAND_EXEC_METHODS.includes(identifierValue)) {
3328
- const args = callExpr.arguments ?? [];
3329
- const firstArg = args[0];
3330
- if (firstArg) {
3331
- const argExpr = firstArg.expression;
3332
- if (argExpr) {
3333
- const argType = argExpr.type;
3334
- if (argType === "TemplateLiteral") {
3335
- const templateLiteral = argExpr;
3336
- const expressions = templateLiteral.expressions ?? [];
3337
- if (expressions.length > 0) {
3338
- return {
3339
- type: "command-injection",
3340
- offset: span.start,
3341
- method: identifierValue
3342
- };
3343
- }
3344
- }
3345
- }
3346
- }
3347
- }
3689
+ const calleeType = getNodeType2(callee);
3690
+ if (!calleeType) {
3691
+ return null;
3348
3692
  }
3349
- if (calleeType === "MemberExpression") {
3350
- const memberExpr = callee;
3351
- const object = memberExpr.object;
3352
- const property = memberExpr.property;
3353
- if (!object || !property) {
3354
- return null;
3355
- }
3356
- const objectType = object.type;
3357
- if (objectType === "Identifier") {
3358
- const objectValue = object.value;
3359
- if (objectValue === "path") {
3360
- const propertyType = property.type;
3361
- if (propertyType === "Identifier") {
3362
- const propertyValue = property.value;
3363
- if (propertyValue && PATH_METHODS.includes(propertyValue)) {
3364
- const args = callExpr.arguments ?? [];
3365
- const pathSegmentArgs = args.slice(1);
3366
- const hasUntrustedPathSegment = pathSegmentArgs.some((arg) => {
3367
- const argExpr = arg.expression;
3368
- if (!argExpr) return false;
3369
- const argType = argExpr.type;
3370
- if (argType === "Identifier") return true;
3371
- if (argType === "TemplateLiteral") {
3372
- const templateLiteral = argExpr;
3373
- return (templateLiteral.expressions?.length ?? 0) > 0;
3374
- }
3375
- if (argType === "MemberExpression") return true;
3376
- if (argType === "CallExpression") return true;
3377
- return false;
3378
- });
3379
- if (hasUntrustedPathSegment) {
3380
- return {
3381
- type: "path-traversal",
3382
- offset: span.start,
3383
- method: `path.${propertyValue}`
3384
- };
3385
- }
3386
- }
3387
- }
3388
- }
3693
+ return {
3694
+ callee,
3695
+ calleeType,
3696
+ args: callExpr.arguments ?? [],
3697
+ span
3698
+ };
3699
+ }
3700
+ function hasTemplateInterpolation(argExpr) {
3701
+ const argType = getNodeType2(argExpr);
3702
+ if (argType !== "TemplateLiteral") {
3703
+ return false;
3704
+ }
3705
+ const templateLiteral = argExpr;
3706
+ const expressions = templateLiteral.expressions ?? [];
3707
+ return expressions.length > 0;
3708
+ }
3709
+ function detectCommandInjectionFromCall(parsed) {
3710
+ if (parsed.calleeType !== "Identifier") {
3711
+ return null;
3712
+ }
3713
+ const methodName = getIdentifierValue(parsed.callee);
3714
+ if (!methodName || !COMMAND_EXEC_METHODS.has(methodName)) {
3715
+ return null;
3716
+ }
3717
+ const firstArg = parsed.args[0];
3718
+ if (!firstArg) {
3719
+ return null;
3720
+ }
3721
+ const argExpr = getArgumentExpression(firstArg);
3722
+ if (!argExpr) {
3723
+ return null;
3724
+ }
3725
+ if (hasTemplateInterpolation(argExpr)) {
3726
+ return {
3727
+ type: "command-injection",
3728
+ offset: parsed.span.start,
3729
+ method: methodName
3730
+ };
3731
+ }
3732
+ return null;
3733
+ }
3734
+ function isUntrustedArgument(arg) {
3735
+ const argExpr = getArgumentExpression(arg);
3736
+ if (!argExpr) {
3737
+ return false;
3738
+ }
3739
+ const argType = getNodeType2(argExpr);
3740
+ if (!argType) {
3741
+ return false;
3742
+ }
3743
+ if (UNTRUSTED_ARG_TYPES.has(argType)) {
3744
+ return true;
3745
+ }
3746
+ if (argType === "TemplateLiteral") {
3747
+ return hasTemplateInterpolation(argExpr);
3748
+ }
3749
+ return false;
3750
+ }
3751
+ function extractPathMethod(callee) {
3752
+ const memberExpr = callee;
3753
+ const { object, property } = memberExpr;
3754
+ if (!object || !property) {
3755
+ return null;
3756
+ }
3757
+ if (getNodeType2(object) !== "Identifier") {
3758
+ return null;
3759
+ }
3760
+ if (getIdentifierValue(object) !== "path") {
3761
+ return null;
3762
+ }
3763
+ if (getNodeType2(property) !== "Identifier") {
3764
+ return null;
3765
+ }
3766
+ const methodName = getIdentifierValue(property);
3767
+ if (!methodName || !PATH_METHODS.has(methodName)) {
3768
+ return null;
3769
+ }
3770
+ return methodName;
3771
+ }
3772
+ function detectPathTraversalFromCall(parsed) {
3773
+ if (parsed.calleeType !== "MemberExpression") {
3774
+ return null;
3775
+ }
3776
+ const methodName = extractPathMethod(parsed.callee);
3777
+ if (!methodName) {
3778
+ return null;
3779
+ }
3780
+ const pathSegmentArgs = parsed.args.slice(1);
3781
+ const hasUntrustedSegment = pathSegmentArgs.some(isUntrustedArgument);
3782
+ if (hasUntrustedSegment) {
3783
+ return {
3784
+ type: "path-traversal",
3785
+ offset: parsed.span.start,
3786
+ method: `path.${methodName}`
3787
+ };
3788
+ }
3789
+ return null;
3790
+ }
3791
+ function detectSecurityIssue(node) {
3792
+ const parsed = parseCallExpression(node);
3793
+ if (!parsed) {
3794
+ return null;
3795
+ }
3796
+ for (const detector of SECURITY_DETECTORS) {
3797
+ const result = detector(parsed);
3798
+ if (result) {
3799
+ return result;
3389
3800
  }
3390
3801
  }
3391
3802
  return null;
3392
3803
  }
3393
- var COMMAND_EXEC_METHODS, PATH_METHODS;
3804
+ var COMMAND_EXEC_METHODS, PATH_METHODS, UNTRUSTED_ARG_TYPES, SECURITY_DETECTORS;
3394
3805
  var init_security_detector = __esm({
3395
3806
  "src/lib/@detectors/security/security-detector.ts"() {
3396
3807
  init_esm_shims();
3397
- COMMAND_EXEC_METHODS = ["execSync", "exec", "spawn", "spawnSync"];
3398
- PATH_METHODS = ["join", "resolve"];
3808
+ COMMAND_EXEC_METHODS = /* @__PURE__ */ new Set(["execSync", "exec", "spawn", "spawnSync"]);
3809
+ PATH_METHODS = /* @__PURE__ */ new Set(["join", "resolve"]);
3810
+ UNTRUSTED_ARG_TYPES = /* @__PURE__ */ new Set(["Identifier", "MemberExpression", "CallExpression"]);
3811
+ SECURITY_DETECTORS = [
3812
+ detectCommandInjectionFromCall,
3813
+ detectPathTraversalFromCall
3814
+ ];
3399
3815
  }
3400
3816
  });
3401
3817
 
@@ -5413,12 +5829,17 @@ var init_issue_factory = __esm({
5413
5829
  });
5414
5830
 
5415
5831
  // src/lib/@detectors/patterns/context.ts
5416
- function isInsideStringLiteral(line, position) {
5417
- const beforeDirective = line.slice(0, position);
5418
- const singleQuotes = (beforeDirective.match(/'/g) || []).length;
5419
- const doubleQuotes = (beforeDirective.match(/"/g) || []).length;
5420
- const backticks = (beforeDirective.match(/`/g) || []).length;
5421
- return singleQuotes % 2 !== 0 || doubleQuotes % 2 !== 0 || backticks % 2 !== 0;
5832
+ function isRealTsDirective(line, matchIndex) {
5833
+ const beforeMatch = line.slice(0, matchIndex);
5834
+ const lineCommentMatch = beforeMatch.match(/\/\/\s*$/);
5835
+ if (lineCommentMatch) {
5836
+ return true;
5837
+ }
5838
+ const blockCommentMatch = beforeMatch.match(/\/\*+\s*$/);
5839
+ if (blockCommentMatch) {
5840
+ return true;
5841
+ }
5842
+ return false;
5422
5843
  }
5423
5844
  function createSnippet(line) {
5424
5845
  const trimmed = line.trim();
@@ -5427,24 +5848,27 @@ function createSnippet(line) {
5427
5848
  function checkTsDirectives(content, filepath) {
5428
5849
  const issues = [];
5429
5850
  const lines = content.split("\n");
5851
+ let charOffset = 0;
5430
5852
  for (let i = 0; i < lines.length; i++) {
5431
5853
  const line = lines[i] ?? "";
5432
- const trimmed = line.trim();
5433
- if (!trimmed) {
5434
- continue;
5435
- }
5854
+ const lineStartOffset = charOffset;
5436
5855
  for (const { pattern, type } of DIRECTIVE_PATTERNS) {
5437
- const match = pattern.exec(trimmed);
5856
+ const match = pattern.exec(line);
5438
5857
  if (!match) {
5439
5858
  continue;
5440
5859
  }
5441
- if (isInsideStringLiteral(trimmed, match.index)) {
5860
+ const absoluteOffset = lineStartOffset + match.index;
5861
+ if (!isInsideComment2(content, absoluteOffset)) {
5862
+ continue;
5863
+ }
5864
+ if (!isRealTsDirective(line, match.index)) {
5442
5865
  continue;
5443
5866
  }
5444
- const snippet = createSnippet(trimmed);
5867
+ const snippet = createSnippet(line);
5445
5868
  const issue = createTsDirectiveIssue(type, i + 1, snippet, filepath);
5446
5869
  issues.push(issue);
5447
5870
  }
5871
+ charOffset = lineStartOffset + line.length + 1;
5448
5872
  }
5449
5873
  return issues;
5450
5874
  }
@@ -5536,6 +5960,7 @@ var MAX_SNIPPET_LENGTH, DIRECTIVE_PATTERNS, VERB_ENDINGS, VERB_STEM_PATTERNS, VO
5536
5960
  var init_context = __esm({
5537
5961
  "src/lib/@detectors/patterns/context.ts"() {
5538
5962
  init_esm_shims();
5963
+ init_swc();
5539
5964
  init_issue_factory();
5540
5965
  MAX_SNIPPET_LENGTH = 80;
5541
5966
  DIRECTIVE_PATTERNS = [
@@ -16111,6 +16536,132 @@ var init_registry5 = __esm({
16111
16536
  }
16112
16537
  });
16113
16538
 
16539
+ // src/lib/@core/text/morphology.ts
16540
+ function getVowelRatio2(str) {
16541
+ const vowels = str.match(/[aeiou]/gi);
16542
+ return vowels ? vowels.length / str.length : 0;
16543
+ }
16544
+ function isAbbreviation(name) {
16545
+ if (name.length > 5) return false;
16546
+ const vowelRatio = getVowelRatio2(name);
16547
+ return vowelRatio < 0.2;
16548
+ }
16549
+ function estimateSyllables(word) {
16550
+ const lowerWord = word.toLowerCase();
16551
+ const vowelClusters = lowerWord.match(/[aeiouy]+/g);
16552
+ if (!vowelClusters) return 1;
16553
+ let count = vowelClusters.length;
16554
+ if (lowerWord.endsWith("e") && count > 1) {
16555
+ count--;
16556
+ }
16557
+ if (/le$/.test(lowerWord) && count > 1) {
16558
+ const beforeLe = lowerWord.slice(-3, -2);
16559
+ if (!/[aeiouy]/.test(beforeLe)) {
16560
+ count++;
16561
+ }
16562
+ }
16563
+ return Math.max(1, count);
16564
+ }
16565
+ function hasNounSuffix(word) {
16566
+ const lowerWord = word.toLowerCase();
16567
+ const nounPatterns = [
16568
+ /er$/,
16569
+ // handler, listener, helper, manager
16570
+ /or$/,
16571
+ // iterator, selector, constructor
16572
+ /tion$/,
16573
+ // function, action, collection
16574
+ /sion$/,
16575
+ // session, version
16576
+ /ment$/,
16577
+ // element, argument
16578
+ /ness$/,
16579
+ // readiness
16580
+ /ity$/,
16581
+ // utility, entity
16582
+ /ure$/,
16583
+ // structure, closure
16584
+ /ance$/,
16585
+ // instance
16586
+ /ence$/,
16587
+ // reference, sequence
16588
+ /ing$/,
16589
+ // string, thing (as nouns)
16590
+ /ist$/,
16591
+ // list (and -ist words)
16592
+ /ata$/,
16593
+ // data, metadata
16594
+ /xt$/,
16595
+ // context, text
16596
+ /que$/,
16597
+ // queue
16598
+ /ay$/,
16599
+ // array, display
16600
+ /ch$/,
16601
+ // cache, batch
16602
+ /ck$/,
16603
+ // callback, stack
16604
+ /se$/,
16605
+ // response, case
16606
+ /te$/,
16607
+ // state, template
16608
+ /de$/,
16609
+ // node, code
16610
+ /ue$/,
16611
+ // value, queue
16612
+ /pe$/,
16613
+ // type, pipe
16614
+ /me$/,
16615
+ // name, frame
16616
+ /ms$/,
16617
+ // params, items
16618
+ /gs$/,
16619
+ // args, flags
16620
+ /ps$/,
16621
+ // props
16622
+ /ns$/,
16623
+ // options, actions
16624
+ /ts$/,
16625
+ // results, events
16626
+ /rd$/,
16627
+ // record
16628
+ /ry$/,
16629
+ // factory, entry
16630
+ /ol$/,
16631
+ // control, protocol
16632
+ /et$/,
16633
+ // object, set
16634
+ /ap$/,
16635
+ // map
16636
+ /lt$/,
16637
+ // result
16638
+ /ig$/,
16639
+ // config
16640
+ /fo$/
16641
+ // info
16642
+ ];
16643
+ return nounPatterns.some((pattern) => pattern.test(lowerWord));
16644
+ }
16645
+ function splitIntoSegments(name) {
16646
+ if (name.includes("_")) {
16647
+ return name.split("_").filter((s) => s.length > 0);
16648
+ }
16649
+ return name.split(/(?=[A-Z])/).filter((s) => s.length > 0);
16650
+ }
16651
+ var init_morphology = __esm({
16652
+ "src/lib/@core/text/morphology.ts"() {
16653
+ init_esm_shims();
16654
+ }
16655
+ });
16656
+
16657
+ // src/lib/@core/text/index.ts
16658
+ var init_text3 = __esm({
16659
+ "src/lib/@core/text/index.ts"() {
16660
+ init_esm_shims();
16661
+ init_morphology();
16662
+ }
16663
+ });
16664
+
16114
16665
  // src/lib/@core/utils/grouping.ts
16115
16666
  function groupBy(items, keyFn) {
16116
16667
  const grouped = /* @__PURE__ */ new Map();
@@ -16170,6 +16721,7 @@ var init_core2 = __esm({
16170
16721
  init_logger2();
16171
16722
  init_registry5();
16172
16723
  init_shell2();
16724
+ init_text3();
16173
16725
  init_time();
16174
16726
  init_utils2();
16175
16727
  }
@@ -16527,7 +17079,8 @@ var init_alert = __esm({
16527
17079
  init_utils3();
16528
17080
  metadata = createFixerMetadata("alert", "Alert Statements", "lint", {
16529
17081
  description: "Remove alert() calls",
16530
- difficulty: "trivial",
17082
+ difficulty: "risky",
17083
+ // TODO: not production-ready
16531
17084
  cliFlag: "--fix-alert",
16532
17085
  tags: ["trivial", "safe-to-autofix", "debugging"]
16533
17086
  });
@@ -16613,7 +17166,8 @@ var init_any_type = __esm({
16613
17166
  init_utils3();
16614
17167
  metadata2 = createFixerMetadata("any-type", "Any Type Usage", "type-safety", {
16615
17168
  description: "Replace `any` with `unknown`",
16616
- difficulty: "safe",
17169
+ difficulty: "risky",
17170
+ // TODO: not production-ready
16617
17171
  cliFlag: "--fix-any",
16618
17172
  negateFlag: "--no-any",
16619
17173
  tags: ["safe", "type-safety"]
@@ -16900,7 +17454,8 @@ var init_backwards_compat2 = __esm({
16900
17454
  "backwards-compat",
16901
17455
  {
16902
17456
  description: "Delete deprecated shim files and update imports",
16903
- difficulty: "safe",
17457
+ difficulty: "risky",
17458
+ // TODO: not production-ready
16904
17459
  cliFlag: "--cleanup-deprecated",
16905
17460
  tags: ["safe", "refactoring", "cleanup"]
16906
17461
  }
@@ -16917,7 +17472,7 @@ var init_backwards_compat2 = __esm({
16917
17472
  });
16918
17473
 
16919
17474
  // src/commands/fix/fixers/complexity/index.ts
16920
- function calculateComplexity(lines) {
17475
+ function calculateComplexity2(lines) {
16921
17476
  let complexity = 1;
16922
17477
  for (const line of lines) {
16923
17478
  for (const pattern of COMPLEXITY_PATTERNS) {
@@ -16941,7 +17496,7 @@ function findFunctionsWithComplexity(content, filePath) {
16941
17496
  name: f.name,
16942
17497
  startLine: f.startLine,
16943
17498
  endLine: f.endLine,
16944
- complexity: calculateComplexity(funcLines),
17499
+ complexity: calculateComplexity2(funcLines),
16945
17500
  isAsync: f.isAsync
16946
17501
  };
16947
17502
  });
@@ -16978,7 +17533,7 @@ function findFunctionsRegex(content) {
16978
17533
  if (currentFunction && braceCount <= functionStartBrace && line.includes("}")) {
16979
17534
  currentFunction.endLine = i + 1;
16980
17535
  const funcLines = lines.slice(currentFunction.startLine - 1, currentFunction.endLine);
16981
- currentFunction.complexity = calculateComplexity(funcLines);
17536
+ currentFunction.complexity = calculateComplexity2(funcLines);
16982
17537
  functions.push(currentFunction);
16983
17538
  currentFunction = null;
16984
17539
  }
@@ -17228,7 +17783,8 @@ var init_console = __esm({
17228
17783
  init_fixer();
17229
17784
  metadata5 = createFixerMetadata("console", "Console Statements", "lint", {
17230
17785
  description: "Remove console.log/warn/error statements",
17231
- difficulty: "trivial",
17786
+ difficulty: "risky",
17787
+ // TODO: not production-ready
17232
17788
  cliFlag: "--fix-console",
17233
17789
  negateFlag: "--no-console",
17234
17790
  tags: ["trivial", "safe-to-autofix", "debugging"]
@@ -17301,7 +17857,8 @@ var init_debugger = __esm({
17301
17857
  init_utils3();
17302
17858
  metadata6 = createFixerMetadata("debugger", "Debugger Statements", "lint", {
17303
17859
  description: "Remove debugger statements",
17304
- difficulty: "trivial",
17860
+ difficulty: "risky",
17861
+ // TODO: not production-ready
17305
17862
  cliFlag: "--fix-debugger",
17306
17863
  negateFlag: "--no-debugger",
17307
17864
  tags: ["trivial", "safe-to-autofix", "debugging"]
@@ -17375,7 +17932,8 @@ var init_duplicate = __esm({
17375
17932
  init_registry6();
17376
17933
  metadata7 = createFixerMetadata("duplicate", "Duplicate Functions", "lint", {
17377
17934
  description: "Merge duplicate functions from refactor analysis",
17378
- difficulty: "safe",
17935
+ difficulty: "risky",
17936
+ // TODO: not production-ready
17379
17937
  cliFlag: "--fix-duplicate",
17380
17938
  tags: ["safe", "refactoring", "deduplication"]
17381
17939
  });
@@ -17491,7 +18049,8 @@ var init_equality = __esm({
17491
18049
  init_fixer2();
17492
18050
  metadata8 = createFixerMetadata("equality", "Strict Equality", "type-safety", {
17493
18051
  description: "Replace == with === and != with !==",
17494
- difficulty: "safe",
18052
+ difficulty: "risky",
18053
+ // TODO: not production-ready
17495
18054
  cliFlag: "--fix-equality",
17496
18055
  negateFlag: "--no-equality",
17497
18056
  tags: ["safe", "type-safety", "eslint"]
@@ -17637,7 +18196,8 @@ var init_eval = __esm({
17637
18196
  init_fixer3();
17638
18197
  metadata9 = createFixerMetadata("eval", "Eval Security", "type-safety", {
17639
18198
  description: "Detect and fix eval() security risks",
17640
- difficulty: "safe",
18199
+ difficulty: "risky",
18200
+ // TODO: not production-ready
17641
18201
  cliFlag: "--fix-eval",
17642
18202
  negateFlag: "--no-eval",
17643
18203
  tags: ["security", "safe", "eval"]
@@ -17741,7 +18301,8 @@ var init_hardcoded_urls = __esm({
17741
18301
  init_registry6();
17742
18302
  metadata10 = createFixerMetadata("hardcoded-urls", "Hardcoded URLs", "hardcoded", {
17743
18303
  description: "Extract hardcoded URLs to constants",
17744
- difficulty: "safe",
18304
+ difficulty: "risky",
18305
+ // TODO: not production-ready
17745
18306
  cliFlag: "--fix-urls",
17746
18307
  tags: ["safe", "hardcoded", "refactoring"]
17747
18308
  });
@@ -20000,6 +20561,33 @@ var init_ast_transformer = __esm({
20000
20561
  "src/lib/@i18n/ast-transformer.ts"() {
20001
20562
  init_esm_shims();
20002
20563
  init_key_resolver();
20564
+ /* @__PURE__ */ new Set([
20565
+ SyntaxKind.ImportDeclaration,
20566
+ SyntaxKind.ExportDeclaration,
20567
+ SyntaxKind.ImportSpecifier,
20568
+ SyntaxKind.ExportSpecifier
20569
+ ]);
20570
+ /* @__PURE__ */ new Set([
20571
+ SyntaxKind.TypeLiteral,
20572
+ SyntaxKind.TypeAliasDeclaration,
20573
+ SyntaxKind.InterfaceDeclaration,
20574
+ SyntaxKind.LiteralType
20575
+ ]);
20576
+ /* @__PURE__ */ new Set([
20577
+ SyntaxKind.BinaryExpression,
20578
+ SyntaxKind.ConditionalExpression
20579
+ ]);
20580
+ /* @__PURE__ */ new Map([
20581
+ [SyntaxKind.JsxAttribute, "jsx-attribute"],
20582
+ [SyntaxKind.JsxExpression, "jsx-expression"],
20583
+ [SyntaxKind.PropertyAssignment, "object-property"],
20584
+ [SyntaxKind.ShorthandPropertyAssignment, "object-property"],
20585
+ [SyntaxKind.CallExpression, "function-argument"],
20586
+ [SyntaxKind.ArrayLiteralExpression, "array-element"],
20587
+ [SyntaxKind.VariableDeclaration, "variable"],
20588
+ [SyntaxKind.TemplateSpan, "template-literal"],
20589
+ [SyntaxKind.TemplateExpression, "template-literal"]
20590
+ ]);
20003
20591
  }
20004
20592
  });
20005
20593
 
@@ -20390,7 +20978,8 @@ var init_i18n2 = __esm({
20390
20978
  init_replacer();
20391
20979
  metadata11 = createFixerMetadata("i18n", "I18n Hardcoded Strings", "i18n", {
20392
20980
  description: "Extract hardcoded Russian text to i18n translation keys",
20393
- difficulty: "safe",
20981
+ difficulty: "risky",
20982
+ // TODO: not production-ready
20394
20983
  cliFlag: "--fix-i18n",
20395
20984
  negateFlag: "--no-i18n",
20396
20985
  tags: ["i18n", "localization", "russian", "catalog-first"]
@@ -20739,7 +21328,8 @@ var init_magic_numbers = __esm({
20739
21328
  init_registry6();
20740
21329
  metadata13 = createFixerMetadata("magic-numbers", "Magic Numbers", "hardcoded", {
20741
21330
  description: "Extract magic numbers to named constants",
20742
- difficulty: "safe",
21331
+ difficulty: "risky",
21332
+ // TODO: not production-ready
20743
21333
  cliFlag: "--fix-magic-numbers",
20744
21334
  tags: ["safe", "hardcoded", "refactoring"]
20745
21335
  });
@@ -21098,7 +21688,8 @@ var init_ts_ignore = __esm({
21098
21688
  init_registry6();
21099
21689
  metadata16 = createFixerMetadata("ts-ignore", "TS-Ignore Comments", "type-safety", {
21100
21690
  description: "Remove @ts-ignore/@ts-nocheck comments",
21101
- difficulty: "safe",
21691
+ difficulty: "risky",
21692
+ // TODO: not production-ready
21102
21693
  cliFlag: "--fix-ts-ignore",
21103
21694
  tags: ["safe", "type-safety"]
21104
21695
  });
@@ -21336,7 +21927,8 @@ var init_unused_imports = __esm({
21336
21927
  init_fixer5();
21337
21928
  metadata17 = createFixerMetadata("unused-imports", "Unused Imports", "lint", {
21338
21929
  description: "Remove unused imports",
21339
- difficulty: "safe",
21930
+ difficulty: "risky",
21931
+ // TODO: not production-ready
21340
21932
  cliFlag: "--fix-imports",
21341
21933
  negateFlag: "--no-imports",
21342
21934
  tags: ["safe", "imports", "cleanup"]
@@ -26369,117 +26961,6 @@ var init_constants10 = __esm({
26369
26961
  });
26370
26962
 
26371
26963
  // src/commands/refactor/analyzers/core/duplicates/linguistic.ts
26372
- function splitIntoSegments(name) {
26373
- if (name.includes("_")) {
26374
- return name.split("_").filter((s) => s.length > 0);
26375
- }
26376
- return name.split(/(?=[A-Z])/).filter((s) => s.length > 0);
26377
- }
26378
- function getVowelRatio2(str) {
26379
- const vowels = str.match(/[aeiou]/gi);
26380
- return vowels ? vowels.length / str.length : 0;
26381
- }
26382
- function isAbbreviation(name) {
26383
- if (name.length > 5) return false;
26384
- const vowelRatio = getVowelRatio2(name);
26385
- return vowelRatio < 0.2;
26386
- }
26387
- function estimateSyllables(word) {
26388
- const lowerWord = word.toLowerCase();
26389
- const vowelClusters = lowerWord.match(/[aeiouy]+/g);
26390
- if (!vowelClusters) return 1;
26391
- let count = vowelClusters.length;
26392
- if (lowerWord.endsWith("e") && count > 1) {
26393
- count--;
26394
- }
26395
- if (/le$/.test(lowerWord) && count > 1) {
26396
- const beforeLe = lowerWord.slice(-3, -2);
26397
- if (!/[aeiouy]/.test(beforeLe)) {
26398
- count++;
26399
- }
26400
- }
26401
- return Math.max(1, count);
26402
- }
26403
- function hasNounSuffix(word) {
26404
- const lowerWord = word.toLowerCase();
26405
- const nounPatterns = [
26406
- /er$/,
26407
- // handler, listener, helper, manager
26408
- /or$/,
26409
- // iterator, selector, constructor
26410
- /tion$/,
26411
- // function, action, collection
26412
- /sion$/,
26413
- // session, version
26414
- /ment$/,
26415
- // element, argument
26416
- /ness$/,
26417
- // readiness
26418
- /ity$/,
26419
- // utility, entity
26420
- /ure$/,
26421
- // structure, closure
26422
- /ance$/,
26423
- // instance
26424
- /ence$/,
26425
- // reference, sequence
26426
- /ing$/,
26427
- // string, thing (as nouns)
26428
- /ist$/,
26429
- // list (and -ist words)
26430
- /ata$/,
26431
- // data, metadata
26432
- /xt$/,
26433
- // context, text
26434
- /que$/,
26435
- // queue
26436
- /ay$/,
26437
- // array, display
26438
- /ch$/,
26439
- // cache, batch
26440
- /ck$/,
26441
- // callback, stack
26442
- /se$/,
26443
- // response, case
26444
- /te$/,
26445
- // state, template
26446
- /de$/,
26447
- // node, code
26448
- /ue$/,
26449
- // value, queue
26450
- /pe$/,
26451
- // type, pipe
26452
- /me$/,
26453
- // name, frame
26454
- /ms$/,
26455
- // params, items
26456
- /gs$/,
26457
- // args, flags
26458
- /ps$/,
26459
- // props
26460
- /ns$/,
26461
- // options, actions
26462
- /ts$/,
26463
- // results, events
26464
- /rd$/,
26465
- // record
26466
- /ry$/,
26467
- // factory, entry
26468
- /ol$/,
26469
- // control, protocol
26470
- /et$/,
26471
- // object, set
26472
- /ap$/,
26473
- // map
26474
- /lt$/,
26475
- // result
26476
- /ig$/,
26477
- // config
26478
- /fo$/
26479
- // info
26480
- ];
26481
- return nounPatterns.some((pattern) => pattern.test(lowerWord));
26482
- }
26483
26964
  function hasVerbPrefix(word) {
26484
26965
  return extractVerbPrefix(word) !== null;
26485
26966
  }
@@ -26487,6 +26968,7 @@ var init_linguistic = __esm({
26487
26968
  "src/commands/refactor/analyzers/core/duplicates/linguistic.ts"() {
26488
26969
  init_esm_shims();
26489
26970
  init_detectors3();
26971
+ init_text3();
26490
26972
  }
26491
26973
  });
26492
26974
 
@@ -26538,6 +27020,58 @@ function isSuffixOnlyName(name) {
26538
27020
  }
26539
27021
  return false;
26540
27022
  }
27023
+ function isCommonVariableName(name) {
27024
+ const lowerName = name.toLowerCase();
27025
+ const commonCollectionNames = [
27026
+ // File system
27027
+ /^(files|dirs|directories|folders|paths|sources)$/i,
27028
+ // Array operations
27029
+ /^(items|entries|elements|records|rows|lines|parts|chunks|segments|tokens)$/i,
27030
+ // Results
27031
+ /^(results|matches|hits|findings|issues|errors|warnings)$/i,
27032
+ // Data
27033
+ /^(keys|values|pairs|fields|props|attrs|args|params)$/i,
27034
+ // Strings
27035
+ /^(words|chars|names|labels|tags|ids)$/i
27036
+ ];
27037
+ for (const pattern of commonCollectionNames) {
27038
+ if (pattern.test(lowerName)) {
27039
+ return true;
27040
+ }
27041
+ }
27042
+ const transformationPatterns = [
27043
+ /^(sorted|filtered|mapped|reduced|grouped|merged|joined|split|parsed|formatted)$/i,
27044
+ /^(processed|transformed|converted|normalized|validated|sanitized|cleaned)$/i,
27045
+ /^(matched|found|selected|picked|extracted|collected|gathered)$/i,
27046
+ /^(updated|modified|changed|fixed|patched|adjusted)$/i
27047
+ ];
27048
+ for (const pattern of transformationPatterns) {
27049
+ if (pattern.test(lowerName)) {
27050
+ return true;
27051
+ }
27052
+ }
27053
+ const iterationPatterns = [
27054
+ /^(line|path|file|dir|item|entry|key|value|name|index|offset)$/i,
27055
+ /^(current|next|prev|first|last|head|tail)$/i,
27056
+ /^(left|right|start|end|begin|stop)$/i
27057
+ ];
27058
+ for (const pattern of iterationPatterns) {
27059
+ if (pattern.test(lowerName)) {
27060
+ return true;
27061
+ }
27062
+ }
27063
+ const tempPatterns = [
27064
+ /^(temp|tmp|buf|buffer|acc|accumulator|memo|cache)$/i,
27065
+ /^(output|input|source|target|dest|destination)$/i,
27066
+ /^(raw|clean|final|base|root|parent|child)$/i
27067
+ ];
27068
+ for (const pattern of tempPatterns) {
27069
+ if (pattern.test(lowerName)) {
27070
+ return true;
27071
+ }
27072
+ }
27073
+ return false;
27074
+ }
26541
27075
  function isPlaceholderName(name) {
26542
27076
  const lowerName = name.toLowerCase();
26543
27077
  if (/^(foo|bar|baz|qux|quux|corge|grault|garply|waldo|fred|plugh|xyzzy|thud)$/i.test(lowerName)) {
@@ -26662,6 +27196,7 @@ function isMeaningfulFunctionName(name) {
26662
27196
  if (isGenericFunctionName(name)) return false;
26663
27197
  if (name.length < 4) return false;
26664
27198
  if (isCommonCallbackPattern(name)) return false;
27199
+ if (isCommonVariableName(name)) return false;
26665
27200
  const namedPattern = detectNamingPattern(name);
26666
27201
  if (namedPattern) {
26667
27202
  return true;
@@ -26700,13 +27235,16 @@ var init_name_detection = __esm({
26700
27235
  function extractFunctionsSwc(filePath, content) {
26701
27236
  const functions = [];
26702
27237
  try {
26703
- const ast = parseSync(content, {
26704
- syntax: "typescript",
26705
- tsx: filePath.endsWith(".tsx")
26706
- });
26707
- const lineOffsets = calculateLineOffsets2(content);
27238
+ const { ast, lineOffsets, baseOffset } = parseFile(filePath, content);
26708
27239
  visitNode2(ast, (node, context) => {
26709
- const funcInfo = extractFunctionInfo(node, filePath, content, lineOffsets, context);
27240
+ const funcInfo = extractFunctionInfo(
27241
+ node,
27242
+ filePath,
27243
+ content,
27244
+ lineOffsets,
27245
+ context,
27246
+ baseOffset
27247
+ );
26710
27248
  if (funcInfo) {
26711
27249
  functions.push(funcInfo);
26712
27250
  }
@@ -26718,13 +27256,9 @@ function extractFunctionsSwc(filePath, content) {
26718
27256
  function extractTypesSwc(filePath, content) {
26719
27257
  const types = [];
26720
27258
  try {
26721
- const ast = parseSync(content, {
26722
- syntax: "typescript",
26723
- tsx: filePath.endsWith(".tsx")
26724
- });
26725
- const lineOffsets = calculateLineOffsets2(content);
27259
+ const { ast, lineOffsets, baseOffset } = parseFile(filePath, content);
26726
27260
  visitNode2(ast, (node, context) => {
26727
- const typeInfo = extractTypeInfo(node, filePath, content, lineOffsets, context);
27261
+ const typeInfo = extractTypeInfo(node, filePath, content, lineOffsets, context, baseOffset);
26728
27262
  if (typeInfo) {
26729
27263
  types.push(typeInfo);
26730
27264
  }
@@ -26733,23 +27267,37 @@ function extractTypesSwc(filePath, content) {
26733
27267
  }
26734
27268
  return types;
26735
27269
  }
26736
- function extractTypeInfo(node, filePath, content, lineOffsets, context) {
27270
+ function extractTypeInfo(node, filePath, content, lineOffsets, context, baseOffset) {
26737
27271
  const nodeType = node.type;
26738
27272
  if (nodeType === "TsInterfaceDeclaration") {
26739
- return extractInterfaceInfo(node, filePath, content, lineOffsets, context.isExported);
27273
+ return extractInterfaceInfo(
27274
+ node,
27275
+ filePath,
27276
+ content,
27277
+ lineOffsets,
27278
+ context.isExported,
27279
+ baseOffset
27280
+ );
26740
27281
  }
26741
27282
  if (nodeType === "TsTypeAliasDeclaration") {
26742
- return extractTypeAliasInfo(node, filePath, content, lineOffsets, context.isExported);
27283
+ return extractTypeAliasInfo(
27284
+ node,
27285
+ filePath,
27286
+ content,
27287
+ lineOffsets,
27288
+ context.isExported,
27289
+ baseOffset
27290
+ );
26743
27291
  }
26744
27292
  return null;
26745
27293
  }
26746
- function extractInterfaceInfo(node, filePath, content, lineOffsets, isExported) {
27294
+ function extractInterfaceInfo(node, filePath, content, lineOffsets, isExported, baseOffset) {
26747
27295
  const iface = node;
26748
27296
  const name = iface.id?.value;
26749
27297
  if (!name) return null;
26750
27298
  const span = iface.span;
26751
- const start = span?.start ?? 0;
26752
- const end = span?.end ?? content.length;
27299
+ const start = Math.max(0, (span?.start ?? 1) - baseOffset - 1);
27300
+ const end = Math.min(content.length, (span?.end ?? content.length + 1) - baseOffset - 1);
26753
27301
  const position = offsetToPosition2(start, lineOffsets);
26754
27302
  const fields = [];
26755
27303
  const fieldDefs = [];
@@ -26758,7 +27306,7 @@ function extractInterfaceInfo(node, filePath, content, lineOffsets, isExported)
26758
27306
  const propName = member.key.value;
26759
27307
  if (propName) {
26760
27308
  fields.push(propName);
26761
- const typeText = extractTypeText(member.typeAnnotation, content);
27309
+ const typeText = extractTypeText(member.typeAnnotation, content, baseOffset);
26762
27310
  fieldDefs.push(`${propName}:${normalizeTypeText(typeText)}`);
26763
27311
  }
26764
27312
  } else if (member.type === "TsMethodSignature" && member.key) {
@@ -26783,15 +27331,15 @@ function extractInterfaceInfo(node, filePath, content, lineOffsets, isExported)
26783
27331
  definition
26784
27332
  };
26785
27333
  }
26786
- function extractTypeAliasInfo(node, filePath, content, lineOffsets, isExported) {
27334
+ function extractTypeAliasInfo(node, filePath, content, lineOffsets, isExported, baseOffset) {
26787
27335
  const typeAlias = node;
26788
27336
  const name = typeAlias.id?.value;
26789
27337
  if (!name) return null;
26790
27338
  const span = typeAlias.span;
26791
- const start = span?.start ?? 0;
26792
- const end = span?.end ?? content.length;
27339
+ const start = Math.max(0, (span?.start ?? 1) - baseOffset - 1);
27340
+ const end = Math.min(content.length, (span?.end ?? content.length + 1) - baseOffset - 1);
26793
27341
  const position = offsetToPosition2(start, lineOffsets);
26794
- const typeText = extractTypeText(typeAlias.typeAnnotation, content);
27342
+ const typeText = extractTypeText(typeAlias.typeAnnotation, content, baseOffset);
26795
27343
  const normalizedStructure = normalizeTypeText(typeText);
26796
27344
  const definition = content.slice(start, Math.min(end, start + 500));
26797
27345
  return {
@@ -26805,24 +27353,17 @@ function extractTypeAliasInfo(node, filePath, content, lineOffsets, isExported)
26805
27353
  definition
26806
27354
  };
26807
27355
  }
26808
- function extractTypeText(typeAnnotation, content) {
27356
+ function extractTypeText(typeAnnotation, content, baseOffset) {
26809
27357
  if (!typeAnnotation || typeof typeAnnotation !== "object") return "unknown";
26810
27358
  const span = typeAnnotation.span;
26811
27359
  if (!span) return "unknown";
26812
- return content.slice(span.start, span.end);
27360
+ const start = Math.max(0, span.start - baseOffset - 1);
27361
+ const end = Math.min(content.length, span.end - baseOffset - 1);
27362
+ return content.slice(start, end);
26813
27363
  }
26814
27364
  function normalizeTypeText(typeText) {
26815
27365
  return typeText.replace(/import\([^)]+\)\./g, "").replace(/\s+/g, "").split(/[|&]/).map((t) => t.trim()).sort().join("|");
26816
27366
  }
26817
- function calculateLineOffsets2(content) {
26818
- const offsets = [0];
26819
- for (let i = 0; i < content.length; i++) {
26820
- if (content[i] === "\n") {
26821
- offsets.push(i + 1);
26822
- }
26823
- }
26824
- return offsets;
26825
- }
26826
27367
  function offsetToPosition2(offset, lineOffsets) {
26827
27368
  let line = 0;
26828
27369
  for (let i = 0; i < lineOffsets.length; i++) {
@@ -26849,7 +27390,7 @@ function visitNode2(node, callback, context = { isExported: false }) {
26849
27390
  currentContext.variableName = decl.id.value;
26850
27391
  }
26851
27392
  }
26852
- if (nodeType === "ObjectExpression" || nodeType === "ArrayExpression") {
27393
+ if (nodeType === "ObjectExpression" || nodeType === "ArrayExpression" || nodeType === "CallExpression") {
26853
27394
  currentContext.variableName = void 0;
26854
27395
  }
26855
27396
  callback(node, currentContext);
@@ -26866,17 +27407,33 @@ function visitNode2(node, callback, context = { isExported: false }) {
26866
27407
  }
26867
27408
  }
26868
27409
  }
26869
- function extractFunctionInfo(node, filePath, content, lineOffsets, context) {
27410
+ function extractFunctionInfo(node, filePath, content, lineOffsets, context, baseOffset) {
26870
27411
  const nodeType = node.type;
26871
27412
  if (nodeType === "FunctionDeclaration") {
26872
27413
  const func = node;
26873
27414
  const name = func.identifier?.value ?? "anonymous";
26874
- return createFunctionInfo(func, name, filePath, content, lineOffsets, context.isExported);
27415
+ return createFunctionInfo(
27416
+ func,
27417
+ name,
27418
+ filePath,
27419
+ content,
27420
+ lineOffsets,
27421
+ context.isExported,
27422
+ baseOffset
27423
+ );
26875
27424
  }
26876
27425
  if (nodeType === "FunctionExpression") {
26877
27426
  const func = node;
26878
27427
  const name = func.identifier?.value ?? "anonymous";
26879
- return createFunctionInfo(func, name, filePath, content, lineOffsets, context.isExported);
27428
+ return createFunctionInfo(
27429
+ func,
27430
+ name,
27431
+ filePath,
27432
+ content,
27433
+ lineOffsets,
27434
+ context.isExported,
27435
+ baseOffset
27436
+ );
26880
27437
  }
26881
27438
  if (nodeType === "ArrowFunctionExpression") {
26882
27439
  const func = node;
@@ -26884,14 +27441,22 @@ function extractFunctionInfo(node, filePath, content, lineOffsets, context) {
26884
27441
  if (!name) {
26885
27442
  return null;
26886
27443
  }
26887
- return createFunctionInfo(func, name, filePath, content, lineOffsets, context.isExported);
27444
+ return createFunctionInfo(
27445
+ func,
27446
+ name,
27447
+ filePath,
27448
+ content,
27449
+ lineOffsets,
27450
+ context.isExported,
27451
+ baseOffset
27452
+ );
26888
27453
  }
26889
27454
  return null;
26890
27455
  }
26891
- function createFunctionInfo(func, name, filePath, content, lineOffsets, isExported) {
27456
+ function createFunctionInfo(func, name, filePath, content, lineOffsets, isExported, baseOffset) {
26892
27457
  const span = func.span;
26893
- const start = span?.start ?? 0;
26894
- const end = span?.end ?? content.length;
27458
+ const start = Math.max(0, (span?.start ?? 1) - baseOffset - 1);
27459
+ const end = Math.min(content.length, (span?.end ?? content.length + 1) - baseOffset - 1);
26895
27460
  const position = offsetToPosition2(start, lineOffsets);
26896
27461
  const bodyContent = content.slice(start, end);
26897
27462
  const bodyHash = crypto.createHash("md5").update(bodyContent).digest("hex");
@@ -26912,6 +27477,7 @@ function createFunctionInfo(func, name, filePath, content, lineOffsets, isExport
26912
27477
  var init_swc_parser = __esm({
26913
27478
  "src/commands/refactor/analyzers/core/swc-parser.ts"() {
26914
27479
  init_esm_shims();
27480
+ init_parser();
26915
27481
  }
26916
27482
  });
26917
27483
 
@@ -27006,6 +27572,7 @@ function parseFileWithSwc(file, projectRoot, verbose) {
27006
27572
  const bodyText = content.slice(swcFunc.bodyStart, swcFunc.bodyEnd);
27007
27573
  const normalizedBodyText = normalizeBody(bodyText);
27008
27574
  const tokens = new Set(normalizedBodyText.split(/\s+/).filter((t) => t.length > 0));
27575
+ const fpResult = generateFingerprint(bodyText);
27009
27576
  functions.push({
27010
27577
  name: swcFunc.name,
27011
27578
  file: relPath,
@@ -27016,7 +27583,10 @@ function parseFileWithSwc(file, projectRoot, verbose) {
27016
27583
  exported: swcFunc.isExported,
27017
27584
  bodyHash: swcFunc.bodyHash,
27018
27585
  normalizedBody: normalizedBodyText,
27019
- tokens
27586
+ tokens,
27587
+ // Fingerprint catches renamed clones (same structure, different identifiers)
27588
+ ...fpResult.fingerprint && { fingerprint: fpResult.fingerprint },
27589
+ ...fpResult.complexity > 0 && { complexity: fpResult.complexity }
27020
27590
  });
27021
27591
  }
27022
27592
  } catch (error) {
@@ -27198,6 +27768,7 @@ async function findDuplicates(targetPath, projectRoot, options = {}) {
27198
27768
  existing.push(func);
27199
27769
  byHash.set(func.bodyHash, existing);
27200
27770
  }
27771
+ const reportedLocations = /* @__PURE__ */ new Set();
27201
27772
  for (const [, funcs] of byHash) {
27202
27773
  if (funcs.length < 2) continue;
27203
27774
  const { locations, uniqueFileCount } = deduplicateLocations(funcs);
@@ -27212,6 +27783,9 @@ async function findDuplicates(targetPath, projectRoot, options = {}) {
27212
27783
  if (!aExported && bExported) return 1;
27213
27784
  return a.localeCompare(b);
27214
27785
  });
27786
+ for (const loc of locations) {
27787
+ reportedLocations.add(`${loc.file}:${loc.line}`);
27788
+ }
27215
27789
  duplicates.push({
27216
27790
  name: `[identical body] ${sortedNames.join(" / ")}`,
27217
27791
  locations,
@@ -27219,6 +27793,38 @@ async function findDuplicates(targetPath, projectRoot, options = {}) {
27219
27793
  recommendation: "merge"
27220
27794
  });
27221
27795
  }
27796
+ const byFingerprint = /* @__PURE__ */ new Map();
27797
+ for (const func of allFunctions) {
27798
+ if (!isLargeEnoughForDuplication(func)) continue;
27799
+ if (!func.fingerprint) continue;
27800
+ const locKey = `${func.file}:${func.line}`;
27801
+ if (reportedLocations.has(locKey)) continue;
27802
+ const existing = byFingerprint.get(func.fingerprint) ?? [];
27803
+ existing.push(func);
27804
+ byFingerprint.set(func.fingerprint, existing);
27805
+ }
27806
+ for (const [, funcs] of byFingerprint) {
27807
+ if (funcs.length < 2) continue;
27808
+ const { locations, uniqueFileCount } = deduplicateLocations(funcs);
27809
+ if (uniqueFileCount < 2) continue;
27810
+ if (locations.length < 2) continue;
27811
+ const uniqueNames = new Set(funcs.map((f) => f.name));
27812
+ if (uniqueNames.size < 2) continue;
27813
+ const sortedNames = [...uniqueNames].sort((a, b) => {
27814
+ const aExported = funcs.some((f) => f.name === a && f.exported);
27815
+ const bExported = funcs.some((f) => f.name === b && f.exported);
27816
+ if (aExported && !bExported) return -1;
27817
+ if (!aExported && bExported) return 1;
27818
+ return a.localeCompare(b);
27819
+ });
27820
+ duplicates.push({
27821
+ name: `[structural clone] ${sortedNames.join(" / ")}`,
27822
+ locations,
27823
+ similarity: 0.95,
27824
+ // Slightly less than identical body to differentiate
27825
+ recommendation: "merge"
27826
+ });
27827
+ }
27222
27828
  return duplicates;
27223
27829
  }
27224
27830
  async function quickScanDuplicates(targetPath) {
@@ -34648,7 +35254,7 @@ function formatMarkdown4(context) {
34648
35254
  return lines.join("\n");
34649
35255
  }
34650
35256
  var MAX_BODY_LENGTH;
34651
- var init_text3 = __esm({
35257
+ var init_text4 = __esm({
34652
35258
  "src/commands/context/formatters/text.ts"() {
34653
35259
  init_esm_shims();
34654
35260
  init_format();
@@ -34661,7 +35267,7 @@ var init_formatters2 = __esm({
34661
35267
  "src/commands/context/formatters/index.ts"() {
34662
35268
  init_esm_shims();
34663
35269
  init_ai2();
34664
- init_text3();
35270
+ init_text4();
34665
35271
  }
34666
35272
  });
34667
35273
 
@@ -39672,7 +40278,7 @@ function parseBiomeOutput(output) {
39672
40278
  function getBiomeVersion(projectRoot) {
39673
40279
  try {
39674
40280
  const biome = getBiomePath(projectRoot);
39675
- const result = execSync(`${biome} --version`, {
40281
+ const result = execFileSync(biome, ["--version"], {
39676
40282
  cwd: projectRoot,
39677
40283
  encoding: "utf8",
39678
40284
  stdio: "pipe"
@@ -46515,11 +47121,11 @@ var init_duplicates_section = __esm({
46515
47121
  const sortedFunctions = [...data.functions].sort((a, b) => b.similarity - a.similarity);
46516
47122
  lines.push(" <!-- FUNCTION DUPLICATES - Consider merging or extracting -->");
46517
47123
  lines.push(` <function-duplicates count="${data.functions.length}">`);
46518
- for (const dup of sortedFunctions.slice(0, 10)) {
47124
+ for (const dup of sortedFunctions.slice(0, 100)) {
46519
47125
  formatFunctionDuplicate(lines, dup, " ");
46520
47126
  }
46521
- if (sortedFunctions.length > 10) {
46522
- lines.push(` <!-- +${sortedFunctions.length - 10} more function duplicates -->`);
47127
+ if (sortedFunctions.length > 100) {
47128
+ lines.push(` <!-- +${sortedFunctions.length - 100} more function duplicates -->`);
46523
47129
  }
46524
47130
  lines.push(" </function-duplicates>");
46525
47131
  }
@@ -46527,11 +47133,11 @@ var init_duplicates_section = __esm({
46527
47133
  const sortedTypes = [...data.types].sort((a, b) => b.similarity - a.similarity);
46528
47134
  lines.push(" <!-- TYPE DUPLICATES - Consider consolidating -->");
46529
47135
  lines.push(` <type-duplicates count="${data.types.length}">`);
46530
- for (const dup of sortedTypes.slice(0, 10)) {
47136
+ for (const dup of sortedTypes.slice(0, 100)) {
46531
47137
  formatTypeDuplicate(lines, dup, " ");
46532
47138
  }
46533
- if (sortedTypes.length > 10) {
46534
- lines.push(` <!-- +${sortedTypes.length - 10} more type duplicates -->`);
47139
+ if (sortedTypes.length > 100) {
47140
+ lines.push(` <!-- +${sortedTypes.length - 100} more type duplicates -->`);
46535
47141
  }
46536
47142
  lines.push(" </type-duplicates>");
46537
47143
  }
@@ -47730,7 +48336,7 @@ function formatMigrationPreview(plan) {
47730
48336
  lines.push("");
47731
48337
  return lines.join("\n");
47732
48338
  }
47733
- var init_text4 = __esm({
48339
+ var init_text5 = __esm({
47734
48340
  "src/commands/refactor/output/text.ts"() {
47735
48341
  init_esm_shims();
47736
48342
  }
@@ -47814,7 +48420,7 @@ function formatRefactor(analysis, format2 = "text") {
47814
48420
  return formatRefactorJson2(analysis);
47815
48421
  }
47816
48422
  default: {
47817
- const { formatRefactorText: formatRefactorText2 } = (init_text4(), __toCommonJS(text_exports));
48423
+ const { formatRefactorText: formatRefactorText2 } = (init_text5(), __toCommonJS(text_exports));
47818
48424
  return formatRefactorText2(analysis);
47819
48425
  }
47820
48426
  }
@@ -47824,7 +48430,7 @@ var init_output7 = __esm({
47824
48430
  init_esm_shims();
47825
48431
  init_ai_native();
47826
48432
  init_json2();
47827
- init_text4();
48433
+ init_text5();
47828
48434
  init_xml3();
47829
48435
  }
47830
48436
  });
@@ -50026,86 +50632,117 @@ __export(setup_exports, {
50026
50632
  printDiagnostics: () => printDiagnostics,
50027
50633
  runSetup: () => runSetup
50028
50634
  });
50029
- async function runSetup(ctx) {
50030
- const { logger: logger2, options, config } = ctx;
50031
- const projectRoot = config.projectRoot ?? process.cwd();
50032
- const { all, plugins, agents, mem, mcp, i18n, update: update2, check, dryRun, force } = options;
50033
- if (check) {
50034
- printDiagnostics();
50035
- return;
50036
- }
50037
- if (update2) {
50038
- await runUpdate({ dryRun: dryRun ?? false, logger: logger2 });
50039
- return;
50040
- }
50041
- if (i18n) {
50042
- logger2.info("\u{1F430} Krolik Setup\n");
50043
- if (dryRun) {
50044
- logger2.info(" [DRY RUN] No changes will be made\n");
50045
- }
50046
- await installI18nextCli(projectRoot, {
50047
- dryRun: dryRun ?? false,
50048
- force: force ?? false,
50049
- logger: logger2
50635
+ function handleCheck(ctx) {
50636
+ if (!ctx.options.check) return false;
50637
+ printDiagnostics();
50638
+ return true;
50639
+ }
50640
+ async function handleUpdate2(ctx) {
50641
+ if (!ctx.options.update) return false;
50642
+ await runUpdate({ dryRun: ctx.options.dryRun ?? false, logger: ctx.logger });
50643
+ return true;
50644
+ }
50645
+ async function handleI18n(ctx) {
50646
+ if (!ctx.options.i18n) return false;
50647
+ logHeader(ctx.logger, ctx.options.dryRun);
50648
+ await installI18nextCli(ctx.projectRoot, {
50649
+ dryRun: ctx.options.dryRun ?? false,
50650
+ force: ctx.options.force ?? false,
50651
+ logger: ctx.logger
50652
+ });
50653
+ return true;
50654
+ }
50655
+ async function handleMcp(ctx) {
50656
+ if (!ctx.options.mcp) return false;
50657
+ logHeader(ctx.logger, ctx.options.dryRun);
50658
+ if (typeof ctx.options.mcp === "string") {
50659
+ await installMcpServer(ctx.options.mcp, {
50660
+ dryRun: ctx.options.dryRun ?? false,
50661
+ logger: ctx.logger
50662
+ });
50663
+ } else {
50664
+ await installAllMcpServers({
50665
+ dryRun: ctx.options.dryRun ?? false,
50666
+ logger: ctx.logger
50050
50667
  });
50051
- return;
50052
- }
50053
- if (mcp) {
50054
- logger2.info("\u{1F430} Krolik Setup\n");
50055
- if (dryRun) {
50056
- logger2.info(" [DRY RUN] No changes will be made\n");
50057
- }
50058
- if (typeof mcp === "string") {
50059
- await installMcpServer(mcp, { dryRun: dryRun ?? false, logger: logger2 });
50060
- } else {
50061
- await installAllMcpServers({ dryRun: dryRun ?? false, logger: logger2 });
50062
- }
50063
- return;
50064
50668
  }
50669
+ return true;
50670
+ }
50671
+ function determineInstallTargets(options) {
50672
+ const { all, plugins, agents, mem } = options;
50065
50673
  const noSelection = !all && !plugins && !agents && !mem;
50066
- const shouldInstallPlugins = all || plugins || mem || noSelection;
50067
- const shouldInstallAgents = all || agents || noSelection;
50068
- const shouldInstallMcp = all;
50674
+ return {
50675
+ shouldInstallPlugins: all || plugins || mem || noSelection,
50676
+ shouldInstallAgents: all || agents || noSelection,
50677
+ shouldInstallMcp: all ?? false
50678
+ };
50679
+ }
50680
+ function logHeader(logger2, dryRun) {
50069
50681
  logger2.info("\u{1F430} Krolik Setup\n");
50070
50682
  if (dryRun) {
50071
50683
  logger2.info(" [DRY RUN] No changes will be made\n");
50072
50684
  }
50073
- if (!dryRun) {
50074
- ensureDirectories();
50075
- }
50076
- if (shouldInstallPlugins) {
50077
- logger2.info("\u{1F4E6} Installing Claude Code plugins...\n");
50078
- if (mem || all || noSelection) {
50079
- await installMcpPlugin("claude-mem", {
50080
- dryRun: dryRun ?? false,
50081
- force: force ?? false,
50082
- logger: logger2
50083
- });
50084
- }
50085
- }
50086
- if (shouldInstallAgents) {
50087
- logger2.info("\n\u{1F916} Installing AI agents...\n");
50088
- await installAgentsRepo({
50089
- dryRun: dryRun ?? false,
50090
- force: force ?? false,
50091
- logger: logger2
50685
+ }
50686
+ async function installPlugins(ctx, targets) {
50687
+ if (!targets.shouldInstallPlugins) return;
50688
+ const { all, mem } = ctx.options;
50689
+ const noSelection = !all && !ctx.options.plugins && !ctx.options.agents && !mem;
50690
+ ctx.logger.info("\u{1F4E6} Installing Claude Code plugins...\n");
50691
+ if (mem || all || noSelection) {
50692
+ await installMcpPlugin("claude-mem", {
50693
+ dryRun: ctx.options.dryRun ?? false,
50694
+ force: ctx.options.force ?? false,
50695
+ logger: ctx.logger
50092
50696
  });
50093
50697
  }
50094
- if (shouldInstallMcp) {
50095
- logger2.info("\n");
50096
- await installAllMcpServers({ dryRun: dryRun ?? false, logger: logger2 });
50097
- }
50698
+ }
50699
+ async function installAgents(ctx, targets) {
50700
+ if (!targets.shouldInstallAgents) return;
50701
+ ctx.logger.info("\n\u{1F916} Installing AI agents...\n");
50702
+ await installAgentsRepo({
50703
+ dryRun: ctx.options.dryRun ?? false,
50704
+ force: ctx.options.force ?? false,
50705
+ logger: ctx.logger
50706
+ });
50707
+ }
50708
+ async function installMcpServers(ctx, targets) {
50709
+ if (!targets.shouldInstallMcp) return;
50710
+ ctx.logger.info("\n");
50711
+ await installAllMcpServers({
50712
+ dryRun: ctx.options.dryRun ?? false,
50713
+ logger: ctx.logger
50714
+ });
50715
+ }
50716
+ function printSuccessMessage(logger2, targets) {
50098
50717
  logger2.info("\n\u2705 Setup complete!");
50099
50718
  logger2.info("\n\u{1F4DD} Next steps:");
50100
50719
  logger2.info(" 1. Restart Claude Code to activate plugins");
50101
- if (shouldInstallPlugins) {
50720
+ if (targets.shouldInstallPlugins) {
50102
50721
  logger2.info(" 2. claude-mem Web UI: http://localhost:37777");
50103
50722
  }
50104
- if (shouldInstallAgents) {
50723
+ if (targets.shouldInstallAgents) {
50105
50724
  logger2.info(" 3. Run: krolik agent --list");
50106
50725
  }
50107
50726
  logger2.info("\n\u{1F4A1} Run: krolik setup --check to see full status");
50108
50727
  }
50728
+ async function runSetup(ctx) {
50729
+ const { logger: logger2, options, config } = ctx;
50730
+ const projectRoot = config.projectRoot ?? process.cwd();
50731
+ const setupCtx = { logger: logger2, projectRoot, options };
50732
+ if (handleCheck(setupCtx)) return;
50733
+ if (await handleUpdate2(setupCtx)) return;
50734
+ if (await handleI18n(setupCtx)) return;
50735
+ if (await handleMcp(setupCtx)) return;
50736
+ const targets = determineInstallTargets(options);
50737
+ logHeader(logger2, options.dryRun);
50738
+ if (!options.dryRun) {
50739
+ ensureDirectories();
50740
+ }
50741
+ await installPlugins(setupCtx, targets);
50742
+ await installAgents(setupCtx, targets);
50743
+ await installMcpServers(setupCtx, targets);
50744
+ printSuccessMessage(logger2, targets);
50745
+ }
50109
50746
  async function runUpdate(opts) {
50110
50747
  const { dryRun, logger: logger2 } = opts;
50111
50748
  logger2.info("\u{1F504} Updating installed components...\n");