@atlisp/lint 0.1.5 → 0.1.6

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 (114) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +182 -58
  3. package/atlisp-lint.default.json +31 -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/function-complexity.js +1 -1
  38. package/dist/checks/function-order.d.ts +3 -0
  39. package/dist/checks/function-order.js +33 -0
  40. package/dist/checks/global-naming.d.ts +3 -0
  41. package/dist/checks/global-naming.js +62 -0
  42. package/dist/checks/identical-branches.d.ts +5 -0
  43. package/dist/checks/identical-branches.js +54 -0
  44. package/dist/checks/index.d.ts +3 -0
  45. package/dist/checks/index.js +117 -0
  46. package/dist/checks/lambda-syntax.d.ts +3 -0
  47. package/dist/checks/lambda-syntax.js +25 -0
  48. package/dist/checks/long-function-call.d.ts +3 -0
  49. package/dist/checks/long-function-call.js +54 -0
  50. package/dist/checks/loop-optimization.d.ts +3 -0
  51. package/dist/checks/loop-optimization.js +17 -0
  52. package/dist/checks/magic-number.d.ts +3 -0
  53. package/dist/checks/magic-number.js +21 -0
  54. package/dist/checks/misplaced-else.js +1 -1
  55. package/dist/checks/missing-export.js +2 -2
  56. package/dist/checks/mixed-indent.d.ts +3 -0
  57. package/dist/checks/mixed-indent.js +19 -0
  58. package/dist/checks/module-reg.js +1 -1
  59. package/dist/checks/multiple-setq.js +1 -1
  60. package/dist/checks/no-return.d.ts +3 -0
  61. package/dist/checks/no-return.js +51 -0
  62. package/dist/checks/nth-usage.d.ts +3 -0
  63. package/dist/checks/nth-usage.js +17 -0
  64. package/dist/checks/quote-style.d.ts +3 -0
  65. package/dist/checks/quote-style.js +25 -0
  66. package/dist/checks/quote-vs-function.js +1 -1
  67. package/dist/checks/recursive-call.js +1 -1
  68. package/dist/checks/redundant-if.d.ts +3 -0
  69. package/dist/checks/redundant-if.js +17 -0
  70. package/dist/checks/redundant-let.js +1 -1
  71. package/dist/checks/redundant-nil-else.js +1 -1
  72. package/dist/checks/redundant-progn.js +1 -1
  73. package/dist/checks/redundant-quotes.js +1 -1
  74. package/dist/checks/redundant-setq.js +1 -1
  75. package/dist/checks/self-compare.js +1 -1
  76. package/dist/checks/setq-multiple.d.ts +3 -0
  77. package/dist/checks/setq-multiple.js +17 -0
  78. package/dist/checks/setq-single-arg.d.ts +5 -0
  79. package/dist/checks/setq-single-arg.js +30 -0
  80. package/dist/checks/shadow-builtin.d.ts +3 -0
  81. package/dist/checks/shadow-builtin.js +77 -0
  82. package/dist/checks/single-arg-and-or.js +1 -1
  83. package/dist/checks/strcat-usage.d.ts +3 -0
  84. package/dist/checks/strcat-usage.js +25 -0
  85. package/dist/checks/type-check.d.ts +3 -0
  86. package/dist/checks/type-check.js +26 -0
  87. package/dist/checks/unused-let.js +1 -1
  88. package/dist/checks/unused-package-dep.js +3 -3
  89. package/dist/checks/variable-shadow.js +1 -1
  90. package/dist/checks/while-constant.d.ts +5 -0
  91. package/dist/checks/while-constant.js +40 -0
  92. package/dist/config.d.ts +1 -0
  93. package/dist/config.js +70 -2
  94. package/dist/disable.js +1 -1
  95. package/dist/formatters.d.ts +1 -0
  96. package/dist/formatters.js +18 -2
  97. package/dist/index.js +53 -13
  98. package/dist/lib/lint-sbcl.lisp +161 -0
  99. package/dist/locale.js +24 -0
  100. package/dist/presets.d.ts +4 -0
  101. package/dist/presets.js +158 -0
  102. package/dist/project.js +37 -6
  103. package/dist/rules.d.ts +9 -0
  104. package/dist/rules.js +238 -0
  105. package/dist/runner.d.ts +2 -0
  106. package/dist/runner.js +198 -11
  107. package/dist/sbcl.js +1 -1
  108. package/dist/stub-packages.json +41 -0
  109. package/dist/types.d.ts +6 -0
  110. package/dist/validate.d.ts +8 -0
  111. package/dist/validate.js +125 -0
  112. package/dist/watch.d.ts +9 -0
  113. package/dist/watch.js +113 -0
  114. package/package.json +1 -1
package/dist/rules.js ADDED
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULES = void 0;
4
+ exports.generateRulesMarkdown = generateRulesMarkdown;
5
+ exports.RULES = [
6
+ {
7
+ name: 'append_single',
8
+ defaultSeverity: 'warn',
9
+ description: '检测 (append (list ...)) 推荐使用 cons',
10
+ category: '风格',
11
+ },
12
+ {
13
+ name: 'arg_count',
14
+ defaultSeverity: 'warn',
15
+ description: '检查函数调用参数数量是否与定义匹配',
16
+ category: '最佳实践',
17
+ },
18
+ {
19
+ name: 'assoc_without_cdr',
20
+ defaultSeverity: 'warn',
21
+ description: '检测 assoc 结果未用 cdr 提取值',
22
+ category: '正确性',
23
+ },
24
+ {
25
+ name: 'bare_function_names',
26
+ defaultSeverity: 'off',
27
+ description: '检测缺少命名空间前缀的 defun',
28
+ category: '风格',
29
+ },
30
+ { name: 'cl_syntax', defaultSeverity: 'warn', description: '检测 Common Lisp 特有语法(如 &key)', category: '语法' },
31
+ {
32
+ name: 'command_shell',
33
+ defaultSeverity: 'error',
34
+ description: '检测 command shell 调用(命令注入风险)',
35
+ category: '安全',
36
+ },
37
+ { name: 'comment_style', defaultSeverity: 'warn', description: '检测 ; 后缺少空格的注释', category: '风格' },
38
+ { name: 'commented_code', defaultSeverity: 'warn', description: '检测被注释掉的代码', category: '风格' },
39
+ { name: 'cond_simplify', defaultSeverity: 'warn', description: '检测可简化为 if 的单分支 cond', category: '风格' },
40
+ {
41
+ name: 'constant_condition',
42
+ defaultSeverity: 'warn',
43
+ description: '检测 if/while/cond 中使用的常量条件',
44
+ category: '风格',
45
+ },
46
+ { name: 'cond_duplicate', defaultSeverity: 'warn', description: '检测 cond 中的重复条件', category: '正确性' },
47
+ {
48
+ name: 'dangling_defun',
49
+ defaultSeverity: 'warn',
50
+ description: '检测定义了但从未被调用的函数(跨文件)',
51
+ category: '正确性',
52
+ },
53
+ { name: 'duplicate_defun', defaultSeverity: 'warn', description: '检测多次定义的函数名', category: '正确性' },
54
+ {
55
+ name: 'double_not',
56
+ defaultSeverity: 'warn',
57
+ description: '检测双重 (not (not ...)) 可简化为单个 not',
58
+ category: '风格',
59
+ },
60
+ { name: 'dynamic_doc', defaultSeverity: 'warn', description: '检测 C: 命令是否缺少 (princ)', category: '最佳实践' },
61
+ { name: 'empty_branch', defaultSeverity: 'warn', description: '检测 if/cond 中为空的分支', category: '风格' },
62
+ {
63
+ name: 'empty_catch',
64
+ defaultSeverity: 'warn',
65
+ description: '检测未检查结果的 vl-catch-all-apply',
66
+ category: '最佳实践',
67
+ },
68
+ { name: 'empty_comment', defaultSeverity: 'warn', description: '检测空注释行(只有分号没有内容)', category: '风格' },
69
+ { name: 'encoding', defaultSeverity: 'error', description: '检查 UTF-8 BOM 和非 UTF-8 编码', category: '编码' },
70
+ { name: 'eq_usage', defaultSeverity: 'warn', description: '检测 eq 与数字比较', category: '最佳实践' },
71
+ {
72
+ name: 'error_handling',
73
+ defaultSeverity: 'warn',
74
+ description: '检测使用 command 但缺少 *error* 处理',
75
+ category: '最佳实践',
76
+ },
77
+ { name: 'extra_parens', defaultSeverity: 'warn', description: '检测可疑的连续右括号', category: '语法' },
78
+ {
79
+ name: 'function_complexity',
80
+ defaultSeverity: 'warn',
81
+ description: '检查函数行数和嵌套深度是否超限',
82
+ category: '复杂度',
83
+ },
84
+ { name: 'function_order', defaultSeverity: 'off', description: '检测函数定义在使用之后', category: '风格' },
85
+ {
86
+ name: 'identical_branches',
87
+ defaultSeverity: 'warn',
88
+ description: '检测 if 中 then 和 else 分支完全相同',
89
+ category: '正确性',
90
+ },
91
+ { name: 'global_naming', defaultSeverity: 'warn', description: '检测全局变量是否使用 *...* 命名', category: '风格' },
92
+ {
93
+ name: 'lambda_syntax',
94
+ defaultSeverity: 'warn',
95
+ description: '检测 (function (lambda ...)) 建议使用缩写',
96
+ category: '风格',
97
+ },
98
+ { name: 'line_length', defaultSeverity: 'warn', description: '检查行长度是否超过限制', category: '风格' },
99
+ { name: 'long_function_call', defaultSeverity: 'warn', description: '检测单行参数过多的函数调用', category: '风格' },
100
+ {
101
+ name: 'loop_optimization',
102
+ defaultSeverity: 'off',
103
+ description: '检测可优化的 while/assoc 循环',
104
+ category: '最佳实践',
105
+ },
106
+ { name: 'magic_number', defaultSeverity: 'off', description: '检测硬编码的魔法数字', category: '风格' },
107
+ {
108
+ name: 'misplaced_else',
109
+ defaultSeverity: 'warn',
110
+ description: '检测 (if (not ...) ...) 建议交换分支',
111
+ category: '风格',
112
+ },
113
+ { name: 'missing_doc', defaultSeverity: 'warn', description: '检测缺少注释说明的函数', category: '风格' },
114
+ { name: 'missing_export', defaultSeverity: 'warn', description: '检测函数未注册到 @::*modules*', category: '架构' },
115
+ { name: 'mixed_indent', defaultSeverity: 'warn', description: '检测缩进中混用 tab 和空格', category: '风格' },
116
+ {
117
+ name: 'module_registration',
118
+ defaultSeverity: 'off',
119
+ description: '检测模块文件是否注册到 *modules*',
120
+ category: '架构',
121
+ },
122
+ { name: 'multiple_setq', defaultSeverity: 'warn', description: '检测多个连续 setq 可以合并', category: '风格' },
123
+ {
124
+ name: 'namespace_header',
125
+ defaultSeverity: 'off',
126
+ description: '检测文件是否包含 (in-package ...) 头',
127
+ category: '架构',
128
+ },
129
+ { name: 'no_return', defaultSeverity: 'warn', description: '检测没有返回值的函数', category: '最佳实践' },
130
+ { name: 'nth_usage', defaultSeverity: 'warn', description: '检测 (nth 0 ...) 推荐使用 car', category: '风格' },
131
+ {
132
+ name: 'open_without_close',
133
+ defaultSeverity: 'warn',
134
+ description: '检测 open/close 数量不匹配(资源泄露)',
135
+ category: '最佳实践',
136
+ },
137
+ { name: 'parameter_naming', defaultSeverity: 'warn', description: '检查参数名是否首字母大写', category: '风格' },
138
+ { name: 'parens', defaultSeverity: 'error', description: '检查括号是否匹配(考虑字符串和注释)', category: '语法' },
139
+ {
140
+ name: 'quit_exit',
141
+ defaultSeverity: 'error',
142
+ description: '检测 quit/exit 调用(会终止 CAD 进程)',
143
+ category: '安全',
144
+ },
145
+ { name: 'quote_style', defaultSeverity: 'warn', description: '检测 (quote ...) 建议使用缩写', category: '风格' },
146
+ {
147
+ name: 'quote_vs_function',
148
+ defaultSeverity: 'warn',
149
+ description: '检测在 mapcar/apply 中使用引用 lambda 而非 function',
150
+ category: '风格',
151
+ },
152
+ {
153
+ name: 'recursive_call',
154
+ defaultSeverity: 'warn',
155
+ description: '检测函数调用自身(可能无限递归)',
156
+ category: '正确性',
157
+ },
158
+ { name: 'redundant_cond', defaultSeverity: 'warn', description: '检测单子句 cond 或末尾 T 子句', category: '风格' },
159
+ { name: 'redundant_if', defaultSeverity: 'warn', description: '检测 if/when 分支中冗余 progn', category: '风格' },
160
+ { name: 'redundant_let', defaultSeverity: 'warn', description: '检测无绑定的 let 建议改用 progn', category: '风格' },
161
+ { name: 'redundant_nil_else', defaultSeverity: 'warn', description: '检测 if 中冗余的 nil 分支', category: '风格' },
162
+ { name: 'redundant_progn', defaultSeverity: 'warn', description: '检测分支中多余的 progn', category: '风格' },
163
+ { name: 'redundant_quotes', defaultSeverity: 'warn', description: "检测冗余双引号(如 ''x)", category: '风格' },
164
+ { name: 'redundant_setq', defaultSeverity: 'warn', description: '检测 setq 变量赋值为自身', category: '风格' },
165
+ {
166
+ name: 'self_compare',
167
+ defaultSeverity: 'warn',
168
+ description: '检测自比较表达式(恒为 T 或 nil)',
169
+ category: '正确性',
170
+ },
171
+ { name: 'setq_single_arg', defaultSeverity: 'warn', description: '检测 setq 只有变量名缺少值', category: '正确性' },
172
+ { name: 'setq_multiple', defaultSeverity: 'warn', description: '检测多个连续 setq 可以合并', category: '风格' },
173
+ { name: 'shadow_builtin', defaultSeverity: 'warn', description: '检测覆盖内置函数的 defun', category: '最佳实践' },
174
+ { name: 'single_arg_and_or', defaultSeverity: 'warn', description: '检测单参数 and/or 建议简化', category: '风格' },
175
+ { name: 'startapp', defaultSeverity: 'warn', description: '检测 startapp 调用(启动外部程序)', category: '安全' },
176
+ {
177
+ name: 'strcat_usage',
178
+ defaultSeverity: 'warn',
179
+ description: '检测是否使用 + 而非 strcat 拼接字符串',
180
+ category: '最佳实践',
181
+ },
182
+ {
183
+ name: 'token_in_url',
184
+ defaultSeverity: 'warn',
185
+ description: '检测字符串中的 token= 参数(凭据泄露)',
186
+ category: '安全',
187
+ },
188
+ { name: 'trailing_paren', defaultSeverity: 'warn', description: '检测多余闭合括号', category: '语法' },
189
+ { name: 'trailing_whitespace', defaultSeverity: 'warn', description: '检测行尾多余空格', category: '风格' },
190
+ { name: 'type_check', defaultSeverity: 'off', description: '检测 getvar 结果未做类型检查', category: '最佳实践' },
191
+ {
192
+ name: 'unused_let_binding',
193
+ defaultSeverity: 'warn',
194
+ description: '检测 let 绑定但未使用的变量',
195
+ category: '最佳实践',
196
+ },
197
+ {
198
+ name: 'unused_local_fun',
199
+ defaultSeverity: 'warn',
200
+ description: '检测 defun / 后定义的局部函数/变量未使用',
201
+ category: '最佳实践',
202
+ },
203
+ { name: 'unused_package_dep', defaultSeverity: 'warn', description: '检测导入的包从未被引用', category: '架构' },
204
+ { name: 'unused_parameter', defaultSeverity: 'warn', description: '检测函数中未使用的参数', category: '最佳实践' },
205
+ { name: 'unused_variable', defaultSeverity: 'warn', description: '检测被赋值但未使用的变量', category: '最佳实践' },
206
+ {
207
+ name: 'variable_shadow',
208
+ defaultSeverity: 'warn',
209
+ description: '检测 setq 覆盖了 let 绑定的同名变量',
210
+ category: '最佳实践',
211
+ },
212
+ { name: 'while_constant', defaultSeverity: 'warn', description: '检测 while 使用常量条件', category: '正确性' },
213
+ {
214
+ name: 'vl_registry_write',
215
+ defaultSeverity: 'warn',
216
+ description: '检测 vl-registry-write(修改注册表)',
217
+ category: '安全',
218
+ },
219
+ {
220
+ name: 'vlax_without_loading',
221
+ defaultSeverity: 'off',
222
+ description: '检测 vlax-* 使用前未调用 vl-load-com',
223
+ category: '最佳实践',
224
+ },
225
+ ];
226
+ function generateRulesMarkdown() {
227
+ const lines = [
228
+ '# @atlisp/lint Rules',
229
+ '',
230
+ '| Rule | Default | Category | Description |',
231
+ '|------|---------|----------|-------------|',
232
+ ];
233
+ for (const r of exports.RULES) {
234
+ lines.push(`| \`${r.name}\` | ${r.defaultSeverity} | ${r.category} | ${r.description} |`);
235
+ }
236
+ return lines.join('\n');
237
+ }
238
+ //# sourceMappingURL=rules.js.map
package/dist/runner.d.ts CHANGED
@@ -4,5 +4,7 @@ export declare function runChecks(content: string, file: string, config: LintCon
4
4
  export declare function runChecksWithVisitor(content: string, file: string, config: LintConfig): Issue[];
5
5
  export declare function lintFiles(files: string[], config: LintConfig, rootDir: string): Issue[];
6
6
  export declare function lintFilesParallel(files: string[], config: LintConfig, rootDir: string): Promise<Issue[]>;
7
+ export declare function applyFixes(issues: Issue[], content: string, filepath: string): string;
8
+ export declare function parseIgnoreFile(rootDir: string): string[];
7
9
  export declare function fixFile(filepath: string): string[];
8
10
  //# sourceMappingURL=runner.d.ts.map
package/dist/runner.js CHANGED
@@ -37,6 +37,8 @@ exports.runChecks = runChecks;
37
37
  exports.runChecksWithVisitor = runChecksWithVisitor;
38
38
  exports.lintFiles = lintFiles;
39
39
  exports.lintFilesParallel = lintFilesParallel;
40
+ exports.applyFixes = applyFixes;
41
+ exports.parseIgnoreFile = parseIgnoreFile;
40
42
  exports.fixFile = fixFile;
41
43
  const fs = __importStar(require("fs"));
42
44
  const path = __importStar(require("path"));
@@ -80,6 +82,35 @@ const misplaced_else_1 = require("./checks/misplaced-else");
80
82
  const quote_vs_function_1 = require("./checks/quote-vs-function");
81
83
  const commented_code_1 = require("./checks/commented-code");
82
84
  const double_not_1 = require("./checks/double-not");
85
+ const setq_single_arg_1 = require("./checks/setq-single-arg");
86
+ const assoc_without_cdr_1 = require("./checks/assoc-without-cdr");
87
+ const identical_branches_1 = require("./checks/identical-branches");
88
+ const while_constant_1 = require("./checks/while-constant");
89
+ const cond_duplicate_1 = require("./checks/cond-duplicate");
90
+ const error_handling_1 = require("./checks/error-handling");
91
+ const global_naming_1 = require("./checks/global-naming");
92
+ const extra_parens_1 = require("./checks/extra-parens");
93
+ const arg_count_1 = require("./checks/arg-count");
94
+ const strcat_usage_1 = require("./checks/strcat-usage");
95
+ const cond_simplify_1 = require("./checks/cond-simplify");
96
+ const quote_style_1 = require("./checks/quote-style");
97
+ const eq_usage_1 = require("./checks/eq-usage");
98
+ const lambda_syntax_1 = require("./checks/lambda-syntax");
99
+ const comment_style_1 = require("./checks/comment-style");
100
+ const empty_catch_1 = require("./checks/empty-catch");
101
+ const nth_usage_1 = require("./checks/nth-usage");
102
+ const append_single_1 = require("./checks/append-single");
103
+ const setq_multiple_1 = require("./checks/setq-multiple");
104
+ const function_order_1 = require("./checks/function-order");
105
+ const magic_number_1 = require("./checks/magic-number");
106
+ const mixed_indent_1 = require("./checks/mixed-indent");
107
+ const long_function_call_1 = require("./checks/long-function-call");
108
+ const no_return_1 = require("./checks/no-return");
109
+ const shadow_builtin_1 = require("./checks/shadow-builtin");
110
+ const dynamic_doc_1 = require("./checks/dynamic-doc");
111
+ const loop_optimization_1 = require("./checks/loop-optimization");
112
+ const type_check_1 = require("./checks/type-check");
113
+ const redundant_if_1 = require("./checks/redundant-if");
83
114
  const config_1 = require("./config");
84
115
  const locale_1 = require("./locale");
85
116
  const disable_1 = require("./disable");
@@ -87,8 +118,44 @@ const parser_1 = require("@atlisp/parser");
87
118
  function runChecks(content, file, config) {
88
119
  const issues = [];
89
120
  const disableMap = (0, disable_1.parseDisableComments)(content);
90
- // Parse AST once for all checks that need it
91
- const ast = (0, parser_1.parseAst)(content);
121
+ // Deferred AST parsing: only parse if any AST-based checks are enabled
122
+ const astRules = new Set([
123
+ 'quit_exit',
124
+ 'open_without_close',
125
+ 'function_complexity',
126
+ 'parameter_naming',
127
+ 'unused_variable',
128
+ 'unused_parameter',
129
+ 'constant_condition',
130
+ 'redundant_progn',
131
+ 'empty_branch',
132
+ 'unused_let_binding',
133
+ 'recursive_call',
134
+ 'variable_shadow',
135
+ 'redundant_cond',
136
+ 'unused_local_fun',
137
+ 'multiple_setq',
138
+ 'redundant_quotes',
139
+ 'redundant_setq',
140
+ 'redundant_nil_else',
141
+ 'single_arg_and_or',
142
+ 'redundant_let',
143
+ 'self_compare',
144
+ 'misplaced_else',
145
+ 'quote_vs_function',
146
+ 'double_not',
147
+ 'setq_single_arg',
148
+ 'assoc_without_cdr',
149
+ 'identical_branches',
150
+ 'while_constant',
151
+ 'cond_duplicate',
152
+ ]);
153
+ const checks = config.checks;
154
+ const needsAst = Array.from(astRules).some(r => checks[r] !== 'off');
155
+ let ast;
156
+ if (needsAst) {
157
+ ast = (0, parser_1.parseAst)(content);
158
+ }
92
159
  function addIfEnabled(rule, fn) {
93
160
  const severity = config.checks[rule];
94
161
  if (severity === 'off')
@@ -138,6 +205,35 @@ function runChecks(content, file, config) {
138
205
  addIfEnabled('quote_vs_function', () => (0, quote_vs_function_1.checkQuoteVsFunctionAst)(ast, file));
139
206
  addIfEnabled('commented_code', () => (0, commented_code_1.checkCommentedCode)(content, file));
140
207
  addIfEnabled('double_not', () => (0, double_not_1.checkDoubleNotAst)(ast, file));
208
+ addIfEnabled('setq_single_arg', () => (0, setq_single_arg_1.checkSetqSingleArgAst)(ast, file));
209
+ addIfEnabled('assoc_without_cdr', () => (0, assoc_without_cdr_1.checkAssocWithoutCdrAst)(ast, file));
210
+ addIfEnabled('identical_branches', () => (0, identical_branches_1.checkIdenticalBranchesAst)(ast, file));
211
+ addIfEnabled('while_constant', () => (0, while_constant_1.checkWhileConstantAst)(ast, file));
212
+ addIfEnabled('cond_duplicate', () => (0, cond_duplicate_1.checkCondDuplicateAst)(ast, file));
213
+ addIfEnabled('error_handling', () => (0, error_handling_1.checkErrorHandling)(content, file));
214
+ addIfEnabled('global_naming', () => (0, global_naming_1.checkGlobalNaming)(content, file));
215
+ addIfEnabled('extra_parens', () => (0, extra_parens_1.checkExtraParens)(content, file));
216
+ addIfEnabled('arg_count', () => (0, arg_count_1.checkArgCount)(content, file));
217
+ addIfEnabled('strcat_usage', () => (0, strcat_usage_1.checkStrcatUsage)(content, file));
218
+ addIfEnabled('cond_simplify', () => (0, cond_simplify_1.checkCondSimplify)(content, file));
219
+ addIfEnabled('quote_style', () => (0, quote_style_1.checkQuoteStyle)(content, file));
220
+ addIfEnabled('eq_usage', () => (0, eq_usage_1.checkEqUsage)(content, file));
221
+ addIfEnabled('lambda_syntax', () => (0, lambda_syntax_1.checkLambdaSyntax)(content, file));
222
+ addIfEnabled('comment_style', () => (0, comment_style_1.checkCommentStyle)(content, file));
223
+ addIfEnabled('empty_catch', () => (0, empty_catch_1.checkEmptyCatch)(content, file));
224
+ addIfEnabled('nth_usage', () => (0, nth_usage_1.checkNthUsage)(content, file));
225
+ addIfEnabled('append_single', () => (0, append_single_1.checkAppendSingle)(content, file));
226
+ addIfEnabled('setq_multiple', () => (0, setq_multiple_1.checkSetqMultiple)(content, file));
227
+ addIfEnabled('function_order', () => (0, function_order_1.checkFunctionOrder)(content, file));
228
+ addIfEnabled('magic_number', () => (0, magic_number_1.checkMagicNumber)(content, file));
229
+ addIfEnabled('mixed_indent', () => (0, mixed_indent_1.checkMixedIndent)(content, file));
230
+ addIfEnabled('long_function_call', () => (0, long_function_call_1.checkLongFunctionCall)(content, file, 6));
231
+ addIfEnabled('no_return', () => (0, no_return_1.checkNoReturn)(content, file));
232
+ addIfEnabled('shadow_builtin', () => (0, shadow_builtin_1.checkShadowBuiltin)(content, file));
233
+ addIfEnabled('dynamic_doc', () => (0, dynamic_doc_1.checkDynamicDoc)(content, file));
234
+ addIfEnabled('loop_optimization', () => (0, loop_optimization_1.checkLoopOptimization)(content, file));
235
+ addIfEnabled('type_check', () => (0, type_check_1.checkTypeCheck)(content, file));
236
+ addIfEnabled('redundant_if', () => (0, redundant_if_1.checkRedundantIf)(content, file));
141
237
  return issues;
142
238
  }
143
239
  /** Single-pass visitor-based runChecks using AstVisitor */
@@ -156,7 +252,7 @@ function runChecksWithVisitor(content, file, config) {
156
252
  }
157
253
  // Register all AST-based checks as visitor handlers
158
254
  if (checks['redundant_quotes'] !== 'off') {
159
- visitor.on('quote', (node) => {
255
+ visitor.on('quote', node => {
160
256
  if (node.children &&
161
257
  node.children.length >= 2 &&
162
258
  node.children[1].type === 'list' &&
@@ -169,7 +265,7 @@ function runChecksWithVisitor(content, file, config) {
169
265
  });
170
266
  }
171
267
  if (checks['double_not'] !== 'off') {
172
- visitor.on('not', (node) => {
268
+ visitor.on('not', node => {
173
269
  if (node.children &&
174
270
  node.children.length >= 2 &&
175
271
  node.children[1].type === 'list' &&
@@ -182,7 +278,7 @@ function runChecksWithVisitor(content, file, config) {
182
278
  });
183
279
  }
184
280
  if (checks['misplaced_else'] !== 'off') {
185
- visitor.on('if', (node) => {
281
+ visitor.on('if', node => {
186
282
  if (node.children && node.children.length === 4) {
187
283
  const cond = node.children[1];
188
284
  if (cond.type === 'list' &&
@@ -209,7 +305,7 @@ function runChecksWithVisitor(content, file, config) {
209
305
  'vl-some',
210
306
  'vl-every',
211
307
  ]);
212
- visitor.on('quote', (node) => {
308
+ visitor.on('quote', node => {
213
309
  if (node.children &&
214
310
  node.children.length >= 2 &&
215
311
  node.children[1].type === 'list' &&
@@ -258,7 +354,7 @@ function lintFiles(files, config, rootDir) {
258
354
  }
259
355
  function lintFilesParallel(files, config, rootDir) {
260
356
  (0, locale_1.setLocale)(config.locale || 'zh');
261
- return new Promise((resolve) => {
357
+ return new Promise(resolve => {
262
358
  const allIssues = [];
263
359
  let completed = 0;
264
360
  const maxWorkers = Math.min(os.cpus().length, files.length);
@@ -280,7 +376,7 @@ function lintFilesParallel(files, config, rootDir) {
280
376
  if (completed >= files.length)
281
377
  resolve(allIssues);
282
378
  });
283
- worker.on('error', (err) => {
379
+ worker.on('error', err => {
284
380
  allIssues.push({
285
381
  file: relPath,
286
382
  line: 1,
@@ -294,7 +390,7 @@ function lintFilesParallel(files, config, rootDir) {
294
390
  if (completed >= files.length)
295
391
  resolve(allIssues);
296
392
  });
297
- worker.on('exit', (code) => {
393
+ worker.on('exit', code => {
298
394
  if (code !== 0) {
299
395
  allIssues.push({
300
396
  file: relPath,
@@ -317,6 +413,97 @@ function lintFilesParallel(files, config, rootDir) {
317
413
  resolve([]);
318
414
  });
319
415
  }
416
+ function applyFixes(issues, content, filepath) {
417
+ const lines = content.split('\n');
418
+ const fixesByRule = new Map();
419
+ for (const iss of issues) {
420
+ if (iss.file !== filepath)
421
+ continue;
422
+ const set = fixesByRule.get(iss.rule) || new Set();
423
+ set.add(iss.line);
424
+ fixesByRule.set(iss.rule, set);
425
+ }
426
+ // Apply fixes from higher line numbers first to preserve offsets
427
+ const fixableRules = new Set([
428
+ 'redundant_quotes',
429
+ 'double_not',
430
+ 'redundant_nil_else',
431
+ 'single_arg_and_or',
432
+ 'redundant_setq',
433
+ ]);
434
+ let result = content;
435
+ for (const [rule, lineSet] of fixesByRule) {
436
+ if (!fixableRules.has(rule))
437
+ continue;
438
+ const sortedLines = Array.from(lineSet).sort((a, b) => b - a);
439
+ for (const line of sortedLines) {
440
+ const idx = line - 1;
441
+ if (idx < 0 || idx >= lines.length)
442
+ continue;
443
+ const lineContent = result.split('\n')[idx];
444
+ switch (rule) {
445
+ case 'redundant_quotes': {
446
+ // Replace ''x with 'x on this line
447
+ const fixed = lineContent.replace(/''(\S)/g, "'$1");
448
+ if (fixed !== lineContent) {
449
+ result = replaceLine(result, idx, fixed);
450
+ }
451
+ break;
452
+ }
453
+ case 'double_not': {
454
+ // Replace (not (not ...)) with (not ...)
455
+ const fixed = lineContent.replace(/\(not\s+\(not\s+/g, '(not ');
456
+ if (fixed !== lineContent) {
457
+ result = replaceLine(result, idx, fixed);
458
+ }
459
+ break;
460
+ }
461
+ case 'redundant_nil_else': {
462
+ // Replace (if ... nil) with (if ...)
463
+ const fixed = lineContent.replace(/\s+nil\s*\)$/, ')');
464
+ if (fixed !== lineContent) {
465
+ result = replaceLine(result, idx, fixed);
466
+ }
467
+ break;
468
+ }
469
+ case 'single_arg_and_or': {
470
+ // Replace (and x) with x, (or x) with x
471
+ const m = lineContent.match(/\((and|or)\s+([^)\s]+)\s*\)/);
472
+ if (m) {
473
+ result = replaceLine(result, idx, m[2]);
474
+ }
475
+ break;
476
+ }
477
+ case 'redundant_setq': {
478
+ // Remove (setq x x) lines
479
+ const allLines = result.split('\n');
480
+ const trimmed = allLines[idx].trim();
481
+ if (/^\(setq\s+(\S+)\s+\1\s*\)$/.test(trimmed)) {
482
+ allLines.splice(idx, 1);
483
+ result = allLines.join('\n');
484
+ }
485
+ break;
486
+ }
487
+ }
488
+ }
489
+ }
490
+ return result;
491
+ }
492
+ function replaceLine(content, lineIdx, newLine) {
493
+ const lines = content.split('\n');
494
+ lines[lineIdx] = newLine;
495
+ return lines.join('\n');
496
+ }
497
+ function parseIgnoreFile(rootDir) {
498
+ const ignoreFile = path.join(rootDir, '.atlisp-lint-ignore');
499
+ if (!fs.existsSync(ignoreFile))
500
+ return [];
501
+ const content = fs.readFileSync(ignoreFile, 'utf-8');
502
+ return content
503
+ .split('\n')
504
+ .map(l => l.trim())
505
+ .filter(l => l.length > 0 && !l.startsWith('#'));
506
+ }
320
507
  const UTF8_BOM = 0xfeff;
321
508
  function fixFile(filepath) {
322
509
  const fixes = [];
@@ -324,7 +511,7 @@ function fixFile(filepath) {
324
511
  // Fix trailing whitespace
325
512
  const wsFixed = content
326
513
  .split('\n')
327
- .map((l) => l.replace(/[ \t]+$/, ''))
514
+ .map(l => l.replace(/[ \t]+$/, ''))
328
515
  .join('\n');
329
516
  if (wsFixed !== content) {
330
517
  content = wsFixed;
@@ -362,7 +549,7 @@ function fixFile(filepath) {
362
549
  // Fix empty comments: remove lines that are only ';' with optional whitespace
363
550
  const lines = content.split('\n');
364
551
  let changed = false;
365
- const fixedLines = lines.map((l) => {
552
+ const fixedLines = lines.map(l => {
366
553
  if (/^\s*;\s*$/.test(l)) {
367
554
  changed = true;
368
555
  return '';
package/dist/sbcl.js CHANGED
@@ -72,7 +72,7 @@ function runSbclLint(srcDir, sbclConfig, stubPackagesPath) {
72
72
  }
73
73
  try {
74
74
  // Convert arrays to Lisp-readable format: ("a" "b")
75
- const toLispList = (arr) => '(' + arr.map((s) => `"${s}"`).join(' ') + ')';
75
+ const toLispList = (arr) => '(' + arr.map(s => `"${s}"`).join(' ') + ')';
76
76
  const result = (0, child_process_1.execFileSync)(sbcl, [
77
77
  '--script',
78
78
  scriptPath,
@@ -0,0 +1,41 @@
1
+ ((AUTOLISP ())
2
+ (@ (COMMON-LISP AUTOLISP))
3
+ (C ())
4
+ (JSON ())
5
+ (SIDEBAR ())
6
+ (FUN ())
7
+ (DCL ())
8
+ (BASE ())
9
+ (STRING ())
10
+ (BLOCK ())
11
+ (ENTITY ())
12
+ (LAYER ())
13
+ (LAYOUT ())
14
+ (CURVE ())
15
+ (LIST ())
16
+ (M ())
17
+ (P ())
18
+ (UI ())
19
+ (SYS ())
20
+ (EXCEL ())
21
+ (WORD ())
22
+ (RE ())
23
+ (VECTRA ())
24
+ (DOS ())
25
+ (AT-PM ())
26
+ (QRENCODE ())
27
+ (NETWORK ())
28
+ (PKGMAN ())
29
+ (DATETIME ())
30
+ (DICT ())
31
+ (@NLP ())
32
+ (AT-SIDEBAR ())
33
+ (S ())
34
+ (VL ())
35
+ (VLR ())
36
+ (VLAX ())
37
+ (THEME ())
38
+ (AJAX ())
39
+ (ATLISP ())
40
+ (LOG ())
41
+ (ATLISP-CORE ()))
package/dist/types.d.ts CHANGED
@@ -1,10 +1,15 @@
1
1
  export type Severity = 'off' | 'warn' | 'error';
2
+ export interface Fix {
3
+ range: [number, number];
4
+ replacement: string;
5
+ }
2
6
  export interface Issue {
3
7
  file: string;
4
8
  line: number;
5
9
  severity: Severity;
6
10
  rule: string;
7
11
  message: string;
12
+ fix?: Fix;
8
13
  }
9
14
  export interface CheckConfig {
10
15
  severity: Severity;
@@ -61,6 +66,7 @@ export interface LintConfig {
61
66
  namespace_header: NamespaceHeaderConfig;
62
67
  bare_function_names: BareFunctionNamesConfig;
63
68
  sbcl: SbclConfig;
69
+ preset?: string;
64
70
  }
65
71
  export interface FormattedResult {
66
72
  issues: Issue[];
@@ -0,0 +1,8 @@
1
+ import { LintConfig } from './types';
2
+ interface ValidationError {
3
+ path: string;
4
+ message: string;
5
+ }
6
+ export declare function validateConfig(config: LintConfig): ValidationError[];
7
+ export {};
8
+ //# sourceMappingURL=validate.d.ts.map