@atlisp/lint 0.1.5 → 0.1.8

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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -58
  3. package/atlisp-lint.default.json +32 -1
  4. package/dist/atlisp-lint.default.json +90 -0
  5. package/dist/cache.d.ts +2 -2
  6. package/dist/cache.js +6 -6
  7. package/dist/checks/append-single.d.ts +3 -0
  8. package/dist/checks/append-single.js +17 -0
  9. package/dist/checks/arg-count.d.ts +3 -0
  10. package/dist/checks/arg-count.js +123 -0
  11. package/dist/checks/assoc-without-cdr.d.ts +5 -0
  12. package/dist/checks/assoc-without-cdr.js +32 -0
  13. package/dist/checks/comment-style.d.ts +3 -0
  14. package/dist/checks/comment-style.js +24 -0
  15. package/dist/checks/cond-duplicate.d.ts +5 -0
  16. package/dist/checks/cond-duplicate.js +52 -0
  17. package/dist/checks/cond-simplify.d.ts +3 -0
  18. package/dist/checks/cond-simplify.js +45 -0
  19. package/dist/checks/constant-condition.js +4 -4
  20. package/dist/checks/dangerous-calls.js +2 -2
  21. package/dist/checks/dangling-defun.d.ts +3 -0
  22. package/dist/checks/dangling-defun.js +10 -28
  23. package/dist/checks/double-not.js +1 -1
  24. package/dist/checks/duplicate-defun.d.ts +6 -0
  25. package/dist/checks/duplicate-defun.js +50 -0
  26. package/dist/checks/dynamic-doc.d.ts +3 -0
  27. package/dist/checks/dynamic-doc.js +21 -0
  28. package/dist/checks/empty-branch.js +1 -1
  29. package/dist/checks/empty-catch.d.ts +3 -0
  30. package/dist/checks/empty-catch.js +34 -0
  31. package/dist/checks/eq-usage.d.ts +3 -0
  32. package/dist/checks/eq-usage.js +25 -0
  33. package/dist/checks/error-handling.d.ts +3 -0
  34. package/dist/checks/error-handling.js +56 -0
  35. package/dist/checks/extra-parens.d.ts +3 -0
  36. package/dist/checks/extra-parens.js +45 -0
  37. package/dist/checks/format-indent.d.ts +3 -0
  38. package/dist/checks/format-indent.js +29 -0
  39. package/dist/checks/function-complexity.js +1 -1
  40. package/dist/checks/function-order.d.ts +3 -0
  41. package/dist/checks/function-order.js +33 -0
  42. package/dist/checks/global-naming.d.ts +3 -0
  43. package/dist/checks/global-naming.js +62 -0
  44. package/dist/checks/identical-branches.d.ts +5 -0
  45. package/dist/checks/identical-branches.js +54 -0
  46. package/dist/checks/index.d.ts +3 -0
  47. package/dist/checks/index.js +117 -0
  48. package/dist/checks/lambda-syntax.d.ts +3 -0
  49. package/dist/checks/lambda-syntax.js +25 -0
  50. package/dist/checks/long-function-call.d.ts +3 -0
  51. package/dist/checks/long-function-call.js +54 -0
  52. package/dist/checks/loop-optimization.d.ts +3 -0
  53. package/dist/checks/loop-optimization.js +17 -0
  54. package/dist/checks/magic-number.d.ts +3 -0
  55. package/dist/checks/magic-number.js +21 -0
  56. package/dist/checks/misplaced-else.js +1 -1
  57. package/dist/checks/missing-export.js +2 -2
  58. package/dist/checks/mixed-indent.d.ts +3 -0
  59. package/dist/checks/mixed-indent.js +19 -0
  60. package/dist/checks/module-reg.js +1 -1
  61. package/dist/checks/multiple-setq.js +1 -1
  62. package/dist/checks/no-return.d.ts +3 -0
  63. package/dist/checks/no-return.js +51 -0
  64. package/dist/checks/nth-usage.d.ts +3 -0
  65. package/dist/checks/nth-usage.js +17 -0
  66. package/dist/checks/quote-style.d.ts +3 -0
  67. package/dist/checks/quote-style.js +25 -0
  68. package/dist/checks/quote-vs-function.js +1 -1
  69. package/dist/checks/recursive-call.js +1 -1
  70. package/dist/checks/redundant-if.d.ts +3 -0
  71. package/dist/checks/redundant-if.js +17 -0
  72. package/dist/checks/redundant-let.js +1 -1
  73. package/dist/checks/redundant-nil-else.js +1 -1
  74. package/dist/checks/redundant-progn.js +1 -1
  75. package/dist/checks/redundant-quotes.js +1 -1
  76. package/dist/checks/redundant-setq.js +1 -1
  77. package/dist/checks/self-compare.js +1 -1
  78. package/dist/checks/setq-multiple.d.ts +3 -0
  79. package/dist/checks/setq-multiple.js +17 -0
  80. package/dist/checks/setq-single-arg.d.ts +5 -0
  81. package/dist/checks/setq-single-arg.js +30 -0
  82. package/dist/checks/shadow-builtin.d.ts +3 -0
  83. package/dist/checks/shadow-builtin.js +77 -0
  84. package/dist/checks/single-arg-and-or.js +1 -1
  85. package/dist/checks/strcat-usage.d.ts +3 -0
  86. package/dist/checks/strcat-usage.js +25 -0
  87. package/dist/checks/type-check.d.ts +3 -0
  88. package/dist/checks/type-check.js +26 -0
  89. package/dist/checks/unused-let.js +1 -1
  90. package/dist/checks/unused-package-dep.js +3 -3
  91. package/dist/checks/variable-shadow.js +1 -1
  92. package/dist/checks/while-constant.d.ts +5 -0
  93. package/dist/checks/while-constant.js +40 -0
  94. package/dist/config.d.ts +1 -0
  95. package/dist/config.js +71 -2
  96. package/dist/disable.js +1 -1
  97. package/dist/formatter.d.ts +2 -0
  98. package/dist/formatter.js +51 -0
  99. package/dist/formatters.d.ts +1 -0
  100. package/dist/formatters.js +18 -2
  101. package/dist/index.js +172 -32
  102. package/dist/lib/lint-sbcl.lisp +161 -0
  103. package/dist/locale.js +76 -0
  104. package/dist/presets.d.ts +4 -0
  105. package/dist/presets.js +159 -0
  106. package/dist/project.js +37 -6
  107. package/dist/rules.d.ts +9 -0
  108. package/dist/rules.js +239 -0
  109. package/dist/runner.d.ts +2 -0
  110. package/dist/runner.js +329 -12
  111. package/dist/sbcl.js +1 -1
  112. package/dist/stub-packages.json +41 -0
  113. package/dist/types.d.ts +6 -0
  114. package/dist/validate.d.ts +8 -0
  115. package/dist/validate.js +126 -0
  116. package/dist/watch.d.ts +9 -0
  117. package/dist/watch.js +113 -0
  118. package/package.json +1 -1
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkArgCount = checkArgCount;
4
+ const locale_1 = require("../locale");
5
+ const utils_1 = require("../utils");
6
+ function checkArgCount(content, file) {
7
+ const issues = [];
8
+ const lines = content.split('\n');
9
+ const defuns = [];
10
+ // Collect all defun definitions
11
+ for (let i = 0; i < lines.length; i++) {
12
+ const stripped = (0, utils_1.stripLine)(lines[i]);
13
+ const m = stripped.match(/\(defun\s+(\S+)\s+\(([^)]*)\)/);
14
+ if (m) {
15
+ const all = m[2];
16
+ const slashIdx = all.indexOf('/');
17
+ const paramStr = slashIdx >= 0 ? all.slice(0, slashIdx).trim() : all;
18
+ const params = paramStr.split(/\s+/).filter(Boolean);
19
+ defuns.push({ name: m[1], line: i + 1, paramCount: params.length });
20
+ }
21
+ }
22
+ // Check calls against definitions
23
+ for (const fn of defuns) {
24
+ if (fn.name.startsWith('c:') || fn.name.includes('/'))
25
+ continue;
26
+ if (fn.paramCount === 0)
27
+ continue;
28
+ for (let i = 0; i < lines.length; i++) {
29
+ const stripped = (0, utils_1.stripLine)(lines[i]);
30
+ const re = new RegExp('\\(\\s*' + fn.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\s+');
31
+ // Don't match the definition line
32
+ if (stripped.includes('defun') && stripped.includes(fn.name))
33
+ continue;
34
+ const callMatch = re.exec(stripped);
35
+ if (callMatch) {
36
+ // Collect all text from the call across lines
37
+ let callText = stripped.slice(callMatch.index);
38
+ let parenDepth = 0;
39
+ for (const ch of callText) {
40
+ if (ch === '(')
41
+ parenDepth++;
42
+ else if (ch === ')')
43
+ parenDepth--;
44
+ }
45
+ // If not closed on this line, read next lines
46
+ let lineIdx = i + 1;
47
+ while (parenDepth > 0 && lineIdx < lines.length) {
48
+ const nextStripped = (0, utils_1.stripLine)(lines[lineIdx]);
49
+ callText += ' ' + nextStripped;
50
+ for (const ch of nextStripped) {
51
+ if (ch === '(')
52
+ parenDepth++;
53
+ else if (ch === ')')
54
+ parenDepth--;
55
+ }
56
+ lineIdx++;
57
+ }
58
+ // Extract args: remove the function call prefix
59
+ const afterName = callText.slice(callMatch[0].length - 1); // keep the leading (
60
+ // Find matching closing paren
61
+ let closeIdx = -1;
62
+ let pd = 1;
63
+ for (let j = 0; j < afterName.length; j++) {
64
+ if (afterName[j] === '(')
65
+ pd++;
66
+ else if (afterName[j] === ')') {
67
+ pd--;
68
+ if (pd === 0) {
69
+ closeIdx = j;
70
+ break;
71
+ }
72
+ }
73
+ }
74
+ const argsStr = closeIdx >= 0 ? afterName.slice(0, closeIdx).trim() : afterName.trim();
75
+ if (!argsStr)
76
+ continue;
77
+ // Count space-delimited args at depth 0
78
+ let argCount = 0;
79
+ let depth = 0;
80
+ let inArg = false;
81
+ for (const ch of argsStr) {
82
+ if (ch === '(') {
83
+ depth++;
84
+ if (!inArg) {
85
+ inArg = true;
86
+ }
87
+ }
88
+ else if (ch === ')') {
89
+ depth--;
90
+ if (depth === 0) {
91
+ if (inArg) {
92
+ argCount++;
93
+ inArg = false;
94
+ }
95
+ }
96
+ }
97
+ else if (depth === 0 && (ch === ' ' || ch === '\t')) {
98
+ if (inArg) {
99
+ argCount++;
100
+ inArg = false;
101
+ }
102
+ }
103
+ else if (!inArg) {
104
+ inArg = true;
105
+ }
106
+ }
107
+ if (inArg)
108
+ argCount++;
109
+ if (argCount !== fn.paramCount) {
110
+ issues.push({
111
+ file,
112
+ line: i + 1,
113
+ severity: 'warn',
114
+ rule: 'arg_count',
115
+ message: (0, locale_1.t)('arg_count', fn.name, argCount, fn.paramCount),
116
+ });
117
+ }
118
+ }
119
+ }
120
+ }
121
+ return issues;
122
+ }
123
+ //# sourceMappingURL=arg-count.js.map
@@ -0,0 +1,5 @@
1
+ import { Issue } from '../types';
2
+ import { AstNode } from '@atlisp/parser';
3
+ export declare function checkAssocWithoutCdr(content: string, file: string): Issue[];
4
+ export declare function checkAssocWithoutCdrAst(ast: AstNode, file: string): Issue[];
5
+ //# sourceMappingURL=assoc-without-cdr.d.ts.map
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkAssocWithoutCdr = checkAssocWithoutCdr;
4
+ exports.checkAssocWithoutCdrAst = checkAssocWithoutCdrAst;
5
+ const locale_1 = require("../locale");
6
+ const parser_1 = require("@atlisp/parser");
7
+ function checkAssocWithoutCdr(content, file) {
8
+ const ast = (0, parser_1.parseAst)(content);
9
+ return checkAssocWithoutCdrAst(ast, file);
10
+ }
11
+ function checkAssocWithoutCdrAst(ast, file) {
12
+ const issues = [];
13
+ const assocNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'assoc'));
14
+ for (const node of assocNodes) {
15
+ if (!node.parent)
16
+ continue;
17
+ // Check if parent is not a cdr or cadr call
18
+ const parent = node.parent;
19
+ const isWrapped = (0, parser_1.astIsList)(parent, 'cdr') || (0, parser_1.astIsList)(parent, 'cadr');
20
+ if (!isWrapped) {
21
+ issues.push({
22
+ file,
23
+ line: node.pos.line,
24
+ severity: 'warn',
25
+ rule: 'assoc_without_cdr',
26
+ message: (0, locale_1.t)('assoc_without_cdr'),
27
+ });
28
+ }
29
+ }
30
+ return issues;
31
+ }
32
+ //# sourceMappingURL=assoc-without-cdr.js.map
@@ -0,0 +1,3 @@
1
+ import { Issue } from '../types';
2
+ export declare function checkCommentStyle(content: string, file: string): Issue[];
3
+ //# sourceMappingURL=comment-style.d.ts.map
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkCommentStyle = checkCommentStyle;
4
+ const locale_1 = require("../locale");
5
+ function checkCommentStyle(content, file) {
6
+ const issues = [];
7
+ const lines = content.split('\n');
8
+ for (let i = 0; i < lines.length; i++) {
9
+ const line = lines[i];
10
+ const trimmed = line.trim();
11
+ // Detect ; not followed by space (except ;; and ;|)
12
+ if (/^;[^ ;|]/.test(trimmed)) {
13
+ issues.push({
14
+ file,
15
+ line: i + 1,
16
+ severity: 'warn',
17
+ rule: 'comment_style',
18
+ message: (0, locale_1.t)('comment_style'),
19
+ });
20
+ }
21
+ }
22
+ return issues;
23
+ }
24
+ //# sourceMappingURL=comment-style.js.map
@@ -0,0 +1,5 @@
1
+ import { Issue } from '../types';
2
+ import { AstNode } from '@atlisp/parser';
3
+ export declare function checkCondDuplicate(content: string, file: string): Issue[];
4
+ export declare function checkCondDuplicateAst(ast: AstNode, file: string): Issue[];
5
+ //# sourceMappingURL=cond-duplicate.d.ts.map
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkCondDuplicate = checkCondDuplicate;
4
+ exports.checkCondDuplicateAst = checkCondDuplicateAst;
5
+ const locale_1 = require("../locale");
6
+ const parser_1 = require("@atlisp/parser");
7
+ function condKey(node) {
8
+ if (node.type === 'symbol')
9
+ return `s:${node.name}`;
10
+ if (node.type === 'number')
11
+ return `n:${node.value}`;
12
+ if (node.type === 'string')
13
+ return `str:${node.value}`;
14
+ if (node.type === 'list') {
15
+ const children = node.children || [];
16
+ return `list(${children.map(condKey).join(',')})`;
17
+ }
18
+ return '';
19
+ }
20
+ function checkCondDuplicate(content, file) {
21
+ const ast = (0, parser_1.parseAst)(content);
22
+ return checkCondDuplicateAst(ast, file);
23
+ }
24
+ function checkCondDuplicateAst(ast, file) {
25
+ const issues = [];
26
+ const condNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'cond'));
27
+ for (const node of condNodes) {
28
+ if (!node.children)
29
+ continue;
30
+ const seen = new Map();
31
+ for (let i = 1; i < node.children.length; i++) {
32
+ const clause = node.children[i];
33
+ if (!clause.children || clause.children.length < 1)
34
+ continue;
35
+ const condition = clause.children[0];
36
+ const key = condKey(condition);
37
+ if (seen.has(key)) {
38
+ issues.push({
39
+ file,
40
+ line: node.pos.line,
41
+ severity: 'warn',
42
+ rule: 'cond_duplicate',
43
+ message: (0, locale_1.t)('cond_duplicate'),
44
+ });
45
+ break;
46
+ }
47
+ seen.set(key, i);
48
+ }
49
+ }
50
+ return issues;
51
+ }
52
+ //# sourceMappingURL=cond-duplicate.js.map
@@ -0,0 +1,3 @@
1
+ import { Issue } from '../types';
2
+ export declare function checkCondSimplify(content: string, file: string): Issue[];
3
+ //# sourceMappingURL=cond-simplify.d.ts.map
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkCondSimplify = checkCondSimplify;
4
+ const locale_1 = require("../locale");
5
+ const utils_1 = require("../utils");
6
+ function checkCondSimplify(content, file) {
7
+ const issues = [];
8
+ const lines = content.split('\n');
9
+ for (let i = 0; i < lines.length; i++) {
10
+ const stripped = (0, utils_1.stripLine)(lines[i]);
11
+ // Match (cond (t ...)) or (cond (expr ...)) - single branch cond
12
+ // A single-branch cond typically looks like: (cond (expr body)) on one line
13
+ const m = stripped.match(/\(cond\s+\(/);
14
+ if (m) {
15
+ // Count how many top-level branches inside this cond
16
+ const condStart = stripped.indexOf('(cond');
17
+ const afterCond = stripped.slice(condStart + 5).trim();
18
+ let branchCount = 0;
19
+ let parenDepth = 0;
20
+ for (const ch of afterCond) {
21
+ if (ch === '(') {
22
+ if (parenDepth === 0)
23
+ branchCount++;
24
+ parenDepth++;
25
+ }
26
+ else if (ch === ')') {
27
+ parenDepth--;
28
+ if (parenDepth < 0)
29
+ break;
30
+ }
31
+ }
32
+ if (branchCount === 1) {
33
+ issues.push({
34
+ file,
35
+ line: i + 1,
36
+ severity: 'warn',
37
+ rule: 'cond_simplify',
38
+ message: (0, locale_1.t)('cond_simplify'),
39
+ });
40
+ }
41
+ }
42
+ }
43
+ return issues;
44
+ }
45
+ //# sourceMappingURL=cond-simplify.js.map
@@ -14,7 +14,7 @@ function checkConstantConditionAst(ast, file) {
14
14
  const issues = [];
15
15
  // Check if / while with constant condition
16
16
  for (const form of CONDITION_FORMS) {
17
- const nodes = (0, parser_1.astFindAll)(ast, (n) => (0, parser_1.astIsList)(n, form));
17
+ const nodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, form));
18
18
  for (const node of nodes) {
19
19
  if (!node.children || node.children.length < 2)
20
20
  continue;
@@ -31,7 +31,7 @@ function checkConstantConditionAst(ast, file) {
31
31
  }
32
32
  }
33
33
  // Check (cond (T ...))
34
- const condNodes = (0, parser_1.astFindAll)(ast, (n) => (0, parser_1.astIsList)(n, 'cond'));
34
+ const condNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'cond'));
35
35
  for (const condNode of condNodes) {
36
36
  if (!condNode.children)
37
37
  continue;
@@ -52,7 +52,7 @@ function checkConstantConditionAst(ast, file) {
52
52
  }
53
53
  }
54
54
  // Check (or T ...) — first arg is T, always short-circuits to T
55
- const orNodes = (0, parser_1.astFindAll)(ast, (n) => (0, parser_1.astIsList)(n, 'or'));
55
+ const orNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'or'));
56
56
  for (const node of orNodes) {
57
57
  if (!node.children || node.children.length < 2)
58
58
  continue;
@@ -68,7 +68,7 @@ function checkConstantConditionAst(ast, file) {
68
68
  }
69
69
  }
70
70
  // Check (and nil ...) — first arg is nil, always short-circuits to nil
71
- const andNodes = (0, parser_1.astFindAll)(ast, (n) => (0, parser_1.astIsList)(n, 'and'));
71
+ const andNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'and'));
72
72
  for (const node of andNodes) {
73
73
  if (!node.children || node.children.length < 2)
74
74
  continue;
@@ -47,7 +47,7 @@ function checkDangerousCallsAst(ast, file, config) {
47
47
  continue;
48
48
  if (pattern.commandShell) {
49
49
  // Find (command "shell" ...) calls
50
- const commandCalls = (0, parser_1.astFindAll)(ast, (n) => isFunctionCall(n, 'command'));
50
+ const commandCalls = (0, parser_1.astFindAll)(ast, n => isFunctionCall(n, 'command'));
51
51
  for (const call of commandCalls) {
52
52
  if (isInQuote(call))
53
53
  continue;
@@ -67,7 +67,7 @@ function checkDangerousCallsAst(ast, file, config) {
67
67
  }
68
68
  else if (pattern.functionCall) {
69
69
  // Find actual function calls
70
- const calls = (0, parser_1.astFindAll)(ast, (n) => isFunctionCall(n, pattern.rule));
70
+ const calls = (0, parser_1.astFindAll)(ast, n => isFunctionCall(n, pattern.rule));
71
71
  for (const call of calls) {
72
72
  if (isInQuote(call))
73
73
  continue;
@@ -2,5 +2,8 @@ import { Issue } from '../types';
2
2
  export declare function checkDanglingDefun(file: string, allDefuns: Map<string, {
3
3
  file: string;
4
4
  line: number;
5
+ }[]>, allReferences: Map<string, {
6
+ file: string;
7
+ line: number;
5
8
  }[]>): Issue[];
6
9
  //# sourceMappingURL=dangling-defun.d.ts.map
@@ -37,46 +37,28 @@ exports.checkDanglingDefun = checkDanglingDefun;
37
37
  const locale_1 = require("../locale");
38
38
  const parser_1 = require("@atlisp/parser");
39
39
  const fs = __importStar(require("fs"));
40
- function checkDanglingDefun(file, allDefuns) {
40
+ function checkDanglingDefun(file, allDefuns, allReferences) {
41
41
  const issues = [];
42
42
  const content = fs.readFileSync(file, 'utf-8');
43
43
  const ast = (0, parser_1.parseAst)(content);
44
44
  const localDefuns = [];
45
- const defunNodes = (0, parser_1.astFindAll)(ast, (n) => (0, parser_1.astIsList)(n, 'defun') || (0, parser_1.astIsList)(n, 'defun-q'));
45
+ const defunNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'defun') || (0, parser_1.astIsList)(n, 'defun-q'));
46
46
  for (const node of defunNodes) {
47
47
  if (node.children && node.children.length >= 2 && (0, parser_1.astIsSymbol)(node.children[1])) {
48
- localDefuns.push(node.children[1].name);
48
+ localDefuns.push({ name: node.children[1].name, line: node.pos.line });
49
49
  }
50
50
  }
51
- const calledSymbols = new Set();
52
- const allCalls = (0, parser_1.astFindAll)(ast, (n) => {
53
- if (n.type !== 'list' || !n.children || n.children.length === 0)
54
- return false;
55
- return (0, parser_1.astIsSymbol)(n.children[0]);
56
- });
57
- for (const call of allCalls) {
58
- const name = call.children[0].name;
59
- if (name !== 'defun' && name !== 'defun-q' && !name.startsWith('c:') && !name.includes(':')) {
60
- calledSymbols.add(name);
61
- }
62
- }
63
- for (const defunName of localDefuns) {
64
- let isCalled = calledSymbols.has(defunName);
65
- if (!isCalled) {
66
- for (const [, defs] of allDefuns) {
67
- if (defs.some((d) => d.file !== file)) {
68
- isCalled = true;
69
- break;
70
- }
71
- }
72
- }
73
- if (!isCalled) {
51
+ for (const defun of localDefuns) {
52
+ const refs = allReferences.get(defun.name) || [];
53
+ const calledElsewhere = refs.some(r => r.file !== file);
54
+ const calledLocally = refs.some(r => r.file === file);
55
+ if (!calledLocally && !calledElsewhere) {
74
56
  issues.push({
75
57
  file,
76
- line: defunNodes.find((n) => n.children && n.children.length >= 2 && (0, parser_1.astIsSymbol)(n.children[1]) && n.children[1].name === defunName).pos.line,
58
+ line: defun.line,
77
59
  severity: 'warn',
78
60
  rule: 'dangling_defun',
79
- message: (0, locale_1.t)('dangling_defun', defunName),
61
+ message: (0, locale_1.t)('dangling_defun', defun.name),
80
62
  });
81
63
  }
82
64
  }
@@ -10,7 +10,7 @@ function checkDoubleNot(content, file) {
10
10
  }
11
11
  function checkDoubleNotAst(ast, file) {
12
12
  const issues = [];
13
- const notNodes = (0, parser_1.astFindAll)(ast, (n) => (0, parser_1.astIsList)(n, 'not'));
13
+ const notNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'not'));
14
14
  for (const node of notNodes) {
15
15
  if (!node.children || node.children.length < 2)
16
16
  continue;
@@ -0,0 +1,6 @@
1
+ import { Issue } from '../types';
2
+ export declare function checkDuplicateDefun(filepath: string, allDefuns: Map<string, {
3
+ file: string;
4
+ line: number;
5
+ }[]>): Issue[];
6
+ //# sourceMappingURL=duplicate-defun.d.ts.map
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkDuplicateDefun = checkDuplicateDefun;
4
+ const locale_1 = require("../locale");
5
+ function checkDuplicateDefun(filepath, allDefuns) {
6
+ const issues = [];
7
+ const defunsInFile = new Map();
8
+ // Collect defuns in this file
9
+ for (const [name, locations] of allDefuns) {
10
+ for (const loc of locations) {
11
+ if (loc.file === filepath) {
12
+ const lines = defunsInFile.get(name) || [];
13
+ lines.push(loc.line);
14
+ defunsInFile.set(name, lines);
15
+ }
16
+ }
17
+ }
18
+ // Check for duplicates within this file
19
+ for (const [name, lines] of defunsInFile) {
20
+ if (lines.length > 1) {
21
+ for (const line of lines.slice(1)) {
22
+ issues.push({
23
+ file: filepath,
24
+ line,
25
+ severity: 'warn',
26
+ rule: 'duplicate_defun',
27
+ message: (0, locale_1.t)('duplicate_defun', name),
28
+ });
29
+ }
30
+ }
31
+ }
32
+ // Check for duplicates across files
33
+ for (const [name, locations] of allDefuns) {
34
+ const matchingFiles = locations.filter(l => l.file !== filepath);
35
+ if (matchingFiles.length > 0) {
36
+ const thisFileLoc = locations.find(l => l.file === filepath);
37
+ if (thisFileLoc) {
38
+ issues.push({
39
+ file: filepath,
40
+ line: thisFileLoc.line,
41
+ severity: 'warn',
42
+ rule: 'duplicate_defun',
43
+ message: (0, locale_1.t)('duplicate_defun', name),
44
+ });
45
+ }
46
+ }
47
+ }
48
+ return issues;
49
+ }
50
+ //# sourceMappingURL=duplicate-defun.js.map
@@ -0,0 +1,3 @@
1
+ import { Issue } from '../types';
2
+ export declare function checkDynamicDoc(content: string, file: string): Issue[];
3
+ //# sourceMappingURL=dynamic-doc.d.ts.map
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkDynamicDoc = checkDynamicDoc;
4
+ const locale_1 = require("../locale");
5
+ const utils_1 = require("../utils");
6
+ function checkDynamicDoc(content, file) {
7
+ const issues = [];
8
+ const lines = content.split('\n');
9
+ for (let i = 0; i < lines.length; i++) {
10
+ const stripped = (0, utils_1.stripLine)(lines[i]);
11
+ const m = stripped.match(/\(defun\s+(C:\S+)\b/);
12
+ if (m) {
13
+ const body = lines.slice(i + 1).join('\n');
14
+ if (!body.includes('(princ)')) {
15
+ issues.push({ file, line: i + 1, severity: 'warn', rule: 'dynamic_doc', message: (0, locale_1.t)('dynamic_doc', m[1]) });
16
+ }
17
+ }
18
+ }
19
+ return issues;
20
+ }
21
+ //# sourceMappingURL=dynamic-doc.js.map
@@ -13,7 +13,7 @@ function checkEmptyBranch(content, file) {
13
13
  function checkEmptyBranchAst(ast, file) {
14
14
  const issues = [];
15
15
  for (const form of BRANCH_FORMS) {
16
- const nodes = (0, parser_1.astFindAll)(ast, (n) => (0, parser_1.astIsList)(n, form));
16
+ const nodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, form));
17
17
  for (const node of nodes) {
18
18
  if (!node.children)
19
19
  continue;
@@ -0,0 +1,3 @@
1
+ import { Issue } from '../types';
2
+ export declare function checkEmptyCatch(content: string, file: string): Issue[];
3
+ //# sourceMappingURL=empty-catch.d.ts.map
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkEmptyCatch = checkEmptyCatch;
4
+ const locale_1 = require("../locale");
5
+ const utils_1 = require("../utils");
6
+ function checkEmptyCatch(content, file) {
7
+ const issues = [];
8
+ const lines = content.split('\n');
9
+ for (let i = 0; i < lines.length; i++) {
10
+ const stripped = (0, utils_1.stripLine)(lines[i]);
11
+ // Detect (vl-catch-all-apply ...) not inside (vl-catch-all-error-p ...) check
12
+ if (stripped.includes('vl-catch-all-apply')) {
13
+ // Check subsequent lines for error-p check
14
+ let hasErrorCheck = false;
15
+ for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) {
16
+ if ((0, utils_1.stripLine)(lines[j]).includes('vl-catch-all-error-p')) {
17
+ hasErrorCheck = true;
18
+ break;
19
+ }
20
+ }
21
+ if (!hasErrorCheck) {
22
+ issues.push({
23
+ file,
24
+ line: i + 1,
25
+ severity: 'warn',
26
+ rule: 'empty_catch',
27
+ message: (0, locale_1.t)('empty_catch'),
28
+ });
29
+ }
30
+ }
31
+ }
32
+ return issues;
33
+ }
34
+ //# sourceMappingURL=empty-catch.js.map
@@ -0,0 +1,3 @@
1
+ import { Issue } from '../types';
2
+ export declare function checkEqUsage(content: string, file: string): Issue[];
3
+ //# sourceMappingURL=eq-usage.d.ts.map
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkEqUsage = checkEqUsage;
4
+ const locale_1 = require("../locale");
5
+ const utils_1 = require("../utils");
6
+ function checkEqUsage(content, file) {
7
+ const issues = [];
8
+ const lines = content.split('\n');
9
+ for (let i = 0; i < lines.length; i++) {
10
+ const stripped = (0, utils_1.stripLine)(lines[i]);
11
+ // Detect (eq num ...) where num is literal number
12
+ const m = stripped.match(/\(eq\s+\S+\s+(\d+)/);
13
+ if (m) {
14
+ issues.push({
15
+ file,
16
+ line: i + 1,
17
+ severity: 'warn',
18
+ rule: 'eq_usage',
19
+ message: (0, locale_1.t)('eq_usage'),
20
+ });
21
+ }
22
+ }
23
+ return issues;
24
+ }
25
+ //# sourceMappingURL=eq-usage.js.map
@@ -0,0 +1,3 @@
1
+ import { Issue } from '../types';
2
+ export declare function checkErrorHandling(content: string, file: string): Issue[];
3
+ //# sourceMappingURL=error-handling.d.ts.map