@atlisp/lint 0.1.2 → 0.1.4

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 (183) hide show
  1. package/README.md +3 -0
  2. package/atlisp-lint.default.json +26 -7
  3. package/dist/checks/bare-names.js +4 -1
  4. package/dist/checks/cl-syntax.js +4 -1
  5. package/dist/checks/commented-code.d.ts +3 -0
  6. package/dist/checks/commented-code.js +46 -0
  7. package/dist/checks/constant-condition.d.ts +5 -0
  8. package/dist/checks/constant-condition.js +88 -0
  9. package/dist/checks/dangerous-calls.d.ts +2 -0
  10. package/dist/checks/dangerous-calls.js +70 -29
  11. package/dist/checks/dangling-defun.d.ts +6 -0
  12. package/dist/checks/dangling-defun.js +85 -0
  13. package/dist/checks/double-not.d.ts +5 -0
  14. package/dist/checks/double-not.js +30 -0
  15. package/dist/checks/empty-branch.d.ts +5 -0
  16. package/dist/checks/empty-branch.js +34 -0
  17. package/dist/checks/empty-comments.d.ts +3 -0
  18. package/dist/checks/empty-comments.js +26 -0
  19. package/dist/checks/encoding.js +13 -4
  20. package/dist/checks/function-complexity.d.ts +2 -0
  21. package/dist/checks/function-complexity.js +89 -45
  22. package/dist/checks/line-length.js +4 -1
  23. package/dist/checks/misplaced-else.d.ts +5 -0
  24. package/dist/checks/misplaced-else.js +31 -0
  25. package/dist/checks/missing-doc.js +5 -2
  26. package/dist/checks/missing-export.d.ts +13 -0
  27. package/dist/checks/missing-export.js +94 -0
  28. package/dist/checks/module-reg.js +9 -4
  29. package/dist/checks/multiple-setq.d.ts +5 -0
  30. package/dist/checks/multiple-setq.js +51 -0
  31. package/dist/checks/namespace-header.js +16 -6
  32. package/dist/checks/open-close.d.ts +2 -0
  33. package/dist/checks/open-close.js +18 -13
  34. package/dist/checks/parameter-naming.d.ts +2 -0
  35. package/dist/checks/parameter-naming.js +26 -14
  36. package/dist/checks/parens.js +8 -3
  37. package/dist/checks/quote-vs-function.d.ts +5 -0
  38. package/dist/checks/quote-vs-function.js +50 -0
  39. package/dist/checks/recursive-call.d.ts +5 -0
  40. package/dist/checks/recursive-call.js +49 -0
  41. package/dist/checks/redundant-cond.d.ts +5 -0
  42. package/dist/checks/redundant-cond.js +50 -0
  43. package/dist/checks/redundant-let.d.ts +5 -0
  44. package/dist/checks/redundant-let.js +31 -0
  45. package/dist/checks/redundant-nil-else.d.ts +5 -0
  46. package/dist/checks/redundant-nil-else.js +32 -0
  47. package/dist/checks/redundant-progn.d.ts +5 -0
  48. package/dist/checks/redundant-progn.js +31 -0
  49. package/dist/checks/redundant-quotes.d.ts +5 -0
  50. package/dist/checks/redundant-quotes.js +33 -0
  51. package/dist/checks/redundant-setq.d.ts +5 -0
  52. package/dist/checks/redundant-setq.js +34 -0
  53. package/dist/checks/self-compare.d.ts +5 -0
  54. package/dist/checks/self-compare.js +37 -0
  55. package/dist/checks/single-arg-and-or.d.ts +5 -0
  56. package/dist/checks/single-arg-and-or.js +32 -0
  57. package/dist/checks/token-in-url.js +4 -1
  58. package/dist/checks/trailing-paren.d.ts +3 -0
  59. package/dist/checks/trailing-paren.js +44 -0
  60. package/dist/checks/trailing-ws.js +4 -1
  61. package/dist/checks/unused-let.d.ts +5 -0
  62. package/dist/checks/unused-let.js +57 -0
  63. package/dist/checks/unused-local-fun.d.ts +5 -0
  64. package/dist/checks/unused-local-fun.js +62 -0
  65. package/dist/checks/unused-package-dep.d.ts +3 -0
  66. package/dist/checks/unused-package-dep.js +93 -0
  67. package/dist/checks/unused-param.d.ts +5 -0
  68. package/dist/checks/unused-param.js +63 -0
  69. package/dist/checks/unused-variable.d.ts +2 -0
  70. package/dist/checks/unused-variable.js +70 -30
  71. package/dist/checks/variable-shadow.d.ts +5 -0
  72. package/dist/checks/variable-shadow.js +66 -0
  73. package/dist/checks/vlax.js +8 -3
  74. package/dist/config.d.ts +1 -2
  75. package/dist/config.js +0 -13
  76. package/dist/disable.js +4 -1
  77. package/dist/formatters.d.ts +1 -2
  78. package/dist/formatters.js +8 -61
  79. package/dist/index.d.ts +1 -1
  80. package/dist/index.js +137 -117
  81. package/dist/locale.js +83 -47
  82. package/dist/project.d.ts +3 -0
  83. package/dist/project.js +138 -0
  84. package/dist/runner.d.ts +3 -0
  85. package/dist/runner.js +215 -23
  86. package/dist/sbcl.js +16 -11
  87. package/dist/worker.js +10 -50
  88. package/lib/lint-sbcl.lisp +19 -54
  89. package/package.json +18 -4
  90. package/dist/atlisp-lint.default.json +0 -71
  91. package/dist/cache.d.ts.map +0 -1
  92. package/dist/cache.js.map +0 -1
  93. package/dist/checks/arg-count.d.ts +0 -3
  94. package/dist/checks/arg-count.d.ts.map +0 -1
  95. package/dist/checks/arg-count.js +0 -120
  96. package/dist/checks/arg-count.js.map +0 -1
  97. package/dist/checks/bare-names.d.ts.map +0 -1
  98. package/dist/checks/bare-names.js.map +0 -1
  99. package/dist/checks/cl-syntax.d.ts.map +0 -1
  100. package/dist/checks/cl-syntax.js.map +0 -1
  101. package/dist/checks/cond-simplify.d.ts +0 -3
  102. package/dist/checks/cond-simplify.d.ts.map +0 -1
  103. package/dist/checks/cond-simplify.js +0 -42
  104. package/dist/checks/cond-simplify.js.map +0 -1
  105. package/dist/checks/dangerous-calls.d.ts.map +0 -1
  106. package/dist/checks/dangerous-calls.js.map +0 -1
  107. package/dist/checks/encoding.d.ts.map +0 -1
  108. package/dist/checks/encoding.js.map +0 -1
  109. package/dist/checks/error-handling.d.ts +0 -3
  110. package/dist/checks/error-handling.d.ts.map +0 -1
  111. package/dist/checks/error-handling.js +0 -53
  112. package/dist/checks/error-handling.js.map +0 -1
  113. package/dist/checks/extra-parens.d.ts +0 -3
  114. package/dist/checks/extra-parens.d.ts.map +0 -1
  115. package/dist/checks/extra-parens.js +0 -42
  116. package/dist/checks/extra-parens.js.map +0 -1
  117. package/dist/checks/function-complexity.d.ts.map +0 -1
  118. package/dist/checks/function-complexity.js.map +0 -1
  119. package/dist/checks/global-naming.d.ts +0 -3
  120. package/dist/checks/global-naming.d.ts.map +0 -1
  121. package/dist/checks/global-naming.js +0 -51
  122. package/dist/checks/global-naming.js.map +0 -1
  123. package/dist/checks/line-length.d.ts.map +0 -1
  124. package/dist/checks/line-length.js.map +0 -1
  125. package/dist/checks/missing-doc.d.ts.map +0 -1
  126. package/dist/checks/missing-doc.js.map +0 -1
  127. package/dist/checks/module-reg.d.ts.map +0 -1
  128. package/dist/checks/module-reg.js.map +0 -1
  129. package/dist/checks/namespace-header.d.ts.map +0 -1
  130. package/dist/checks/namespace-header.js.map +0 -1
  131. package/dist/checks/open-close.d.ts.map +0 -1
  132. package/dist/checks/open-close.js.map +0 -1
  133. package/dist/checks/parameter-naming.d.ts.map +0 -1
  134. package/dist/checks/parameter-naming.js.map +0 -1
  135. package/dist/checks/parens.d.ts.map +0 -1
  136. package/dist/checks/parens.js.map +0 -1
  137. package/dist/checks/strcat-usage.d.ts +0 -3
  138. package/dist/checks/strcat-usage.d.ts.map +0 -1
  139. package/dist/checks/strcat-usage.js +0 -22
  140. package/dist/checks/strcat-usage.js.map +0 -1
  141. package/dist/checks/token-in-url.d.ts.map +0 -1
  142. package/dist/checks/token-in-url.js.map +0 -1
  143. package/dist/checks/trailing-ws.d.ts.map +0 -1
  144. package/dist/checks/trailing-ws.js.map +0 -1
  145. package/dist/checks/unused-variable.d.ts.map +0 -1
  146. package/dist/checks/unused-variable.js.map +0 -1
  147. package/dist/checks/vlax.d.ts.map +0 -1
  148. package/dist/checks/vlax.js.map +0 -1
  149. package/dist/config.d.ts.map +0 -1
  150. package/dist/config.js.map +0 -1
  151. package/dist/disable.d.ts.map +0 -1
  152. package/dist/disable.js.map +0 -1
  153. package/dist/formatters.d.ts.map +0 -1
  154. package/dist/formatters.js.map +0 -1
  155. package/dist/index.d.ts.map +0 -1
  156. package/dist/index.js.map +0 -1
  157. package/dist/lib/lint-sbcl.lisp +0 -161
  158. package/dist/locale.d.ts.map +0 -1
  159. package/dist/locale.js.map +0 -1
  160. package/dist/rules.d.ts +0 -9
  161. package/dist/rules.d.ts.map +0 -1
  162. package/dist/rules.js +0 -39
  163. package/dist/rules.js.map +0 -1
  164. package/dist/runner.d.ts.map +0 -1
  165. package/dist/runner.js.map +0 -1
  166. package/dist/sbcl.d.ts.map +0 -1
  167. package/dist/sbcl.js.map +0 -1
  168. package/dist/stub-packages.json +0 -41
  169. package/dist/types.d.ts.map +0 -1
  170. package/dist/types.js.map +0 -1
  171. package/dist/utils.d.ts.map +0 -1
  172. package/dist/utils.js.map +0 -1
  173. package/dist/validate.d.ts +0 -8
  174. package/dist/validate.d.ts.map +0 -1
  175. package/dist/validate.js +0 -55
  176. package/dist/validate.js.map +0 -1
  177. package/dist/watch.d.ts +0 -9
  178. package/dist/watch.d.ts.map +0 -1
  179. package/dist/watch.js +0 -109
  180. package/dist/watch.js.map +0 -1
  181. package/dist/worker.d.ts.map +0 -1
  182. package/dist/worker.js.map +0 -1
  183. package/pre-commit/hook.sh +0 -4
package/dist/runner.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import { Issue, LintConfig } from './types';
2
+ export declare function runChecks(content: string, file: string, config: LintConfig): Issue[];
3
+ /** Single-pass visitor-based runChecks using AstVisitor */
4
+ export declare function runChecksWithVisitor(content: string, file: string, config: LintConfig): Issue[];
2
5
  export declare function lintFiles(files: string[], config: LintConfig, rootDir: string): Issue[];
3
6
  export declare function lintFilesParallel(files: string[], config: LintConfig, rootDir: string): Promise<Issue[]>;
4
7
  export declare function fixFile(filepath: string): string[];
package/dist/runner.js CHANGED
@@ -33,6 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runChecks = runChecks;
37
+ exports.runChecksWithVisitor = runChecksWithVisitor;
36
38
  exports.lintFiles = lintFiles;
37
39
  exports.lintFilesParallel = lintFilesParallel;
38
40
  exports.fixFile = fixFile;
@@ -56,19 +58,41 @@ const function_complexity_1 = require("./checks/function-complexity");
56
58
  const parameter_naming_1 = require("./checks/parameter-naming");
57
59
  const unused_variable_1 = require("./checks/unused-variable");
58
60
  const missing_doc_1 = require("./checks/missing-doc");
59
- const error_handling_1 = require("./checks/error-handling");
60
- const global_naming_1 = require("./checks/global-naming");
61
- const extra_parens_1 = require("./checks/extra-parens");
62
- const arg_count_1 = require("./checks/arg-count");
63
- const strcat_usage_1 = require("./checks/strcat-usage");
64
- const cond_simplify_1 = require("./checks/cond-simplify");
61
+ const unused_param_1 = require("./checks/unused-param");
62
+ const constant_condition_1 = require("./checks/constant-condition");
63
+ const redundant_progn_1 = require("./checks/redundant-progn");
64
+ const empty_branch_1 = require("./checks/empty-branch");
65
+ const unused_let_1 = require("./checks/unused-let");
66
+ const recursive_call_1 = require("./checks/recursive-call");
67
+ const variable_shadow_1 = require("./checks/variable-shadow");
68
+ const redundant_cond_1 = require("./checks/redundant-cond");
69
+ const unused_local_fun_1 = require("./checks/unused-local-fun");
70
+ const multiple_setq_1 = require("./checks/multiple-setq");
71
+ const redundant_quotes_1 = require("./checks/redundant-quotes");
72
+ const trailing_paren_1 = require("./checks/trailing-paren");
73
+ const empty_comments_1 = require("./checks/empty-comments");
74
+ const redundant_setq_1 = require("./checks/redundant-setq");
75
+ const redundant_nil_else_1 = require("./checks/redundant-nil-else");
76
+ const single_arg_and_or_1 = require("./checks/single-arg-and-or");
77
+ const redundant_let_1 = require("./checks/redundant-let");
78
+ const self_compare_1 = require("./checks/self-compare");
79
+ const misplaced_else_1 = require("./checks/misplaced-else");
80
+ const quote_vs_function_1 = require("./checks/quote-vs-function");
81
+ const commented_code_1 = require("./checks/commented-code");
82
+ const double_not_1 = require("./checks/double-not");
65
83
  const config_1 = require("./config");
66
84
  const locale_1 = require("./locale");
67
85
  const disable_1 = require("./disable");
86
+ const parser_1 = require("@atlisp/parser");
68
87
  function runChecks(content, file, config) {
69
88
  const issues = [];
70
89
  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);
71
92
  function addIfEnabled(rule, fn) {
93
+ const severity = config.checks[rule];
94
+ if (severity === 'off')
95
+ return;
72
96
  const results = fn();
73
97
  for (const r of results) {
74
98
  if (!(0, disable_1.isDisabled)(r.rule, r.line, disableMap)) {
@@ -79,25 +103,131 @@ function runChecks(content, file, config) {
79
103
  addIfEnabled('encoding', () => (0, encoding_1.checkEncoding)(content, file));
80
104
  addIfEnabled('parens', () => (0, parens_1.checkParens)(content, file));
81
105
  addIfEnabled('cl_syntax', () => (0, cl_syntax_1.checkClSyntax)(content, file, config.cl_syntax.keywords));
82
- addIfEnabled('quit_exit', () => (0, dangerous_calls_1.checkDangerousCalls)(content, file, config.dangerous_calls));
106
+ addIfEnabled('quit_exit', () => (0, dangerous_calls_1.checkDangerousCallsAst)(ast, file, config.dangerous_calls));
83
107
  addIfEnabled('vlax_without_loading', () => (0, vlax_1.checkVlaxWithoutLoading)(content, file));
84
108
  addIfEnabled('token_in_url', () => (0, token_in_url_1.checkTokenInUrl)(content, file));
85
- addIfEnabled('open_without_close', () => (0, open_close_1.checkOpenWithoutClose)(content, file));
109
+ addIfEnabled('open_without_close', () => (0, open_close_1.checkOpenWithoutCloseAst)(ast, file));
86
110
  addIfEnabled('bare_function_names', () => (0, bare_names_1.checkBareFunctionNames)(content, file, config.bare_function_names.allowlist, config.bare_function_names.namespace_pattern));
87
111
  addIfEnabled('module_registration', () => (0, module_reg_1.checkModuleRegistration)(content, file, file, config.module_registration));
88
112
  addIfEnabled('namespace_header', () => (0, namespace_header_1.checkNamespaceHeader)(content, file, config.namespace_header));
89
113
  addIfEnabled('line_length', () => (0, line_length_1.checkLineLength)(content, file, config.line_length.max, config.line_length.tab_width));
90
- addIfEnabled('function_complexity', () => (0, function_complexity_1.checkFunctionComplexity)(content, file, config.function_complexity.max_lines, config.function_complexity.max_nesting));
91
- addIfEnabled('parameter_naming', () => (0, parameter_naming_1.checkParameterNaming)(content, file));
92
- addIfEnabled('unused_variable', () => (0, unused_variable_1.checkUnusedVariable)(content, file));
114
+ addIfEnabled('function_complexity', () => (0, function_complexity_1.checkFunctionComplexityAst)(ast, file, config.function_complexity.max_lines, config.function_complexity.max_nesting));
115
+ addIfEnabled('parameter_naming', () => (0, parameter_naming_1.checkParameterNamingAst)(ast, file));
116
+ addIfEnabled('unused_variable', () => (0, unused_variable_1.checkUnusedVariableAst)(ast, file));
93
117
  addIfEnabled('missing_doc', () => (0, missing_doc_1.checkMissingDoc)(content, file));
94
- addIfEnabled('error_handling', () => (0, error_handling_1.checkErrorHandling)(content, file));
95
- addIfEnabled('global_naming', () => (0, global_naming_1.checkGlobalNaming)(content, file));
96
- addIfEnabled('extra_parens', () => (0, extra_parens_1.checkExtraParens)(content, file));
97
- addIfEnabled('arg_count', () => (0, arg_count_1.checkArgCount)(content, file));
98
- addIfEnabled('strcat_usage', () => (0, strcat_usage_1.checkStrcatUsage)(content, file));
99
- addIfEnabled('cond_simplify', () => (0, cond_simplify_1.checkCondSimplify)(content, file));
100
118
  addIfEnabled('trailing_whitespace', () => (0, trailing_ws_1.checkTrailingWhitespace)(content, file));
119
+ addIfEnabled('unused_parameter', () => (0, unused_param_1.checkUnusedParameterAst)(ast, file));
120
+ addIfEnabled('constant_condition', () => (0, constant_condition_1.checkConstantConditionAst)(ast, file));
121
+ addIfEnabled('redundant_progn', () => (0, redundant_progn_1.checkRedundantPrognAst)(ast, file));
122
+ addIfEnabled('empty_branch', () => (0, empty_branch_1.checkEmptyBranchAst)(ast, file));
123
+ addIfEnabled('unused_let_binding', () => (0, unused_let_1.checkUnusedLetBindingAst)(ast, file));
124
+ addIfEnabled('recursive_call', () => (0, recursive_call_1.checkRecursiveCallAst)(ast, file));
125
+ addIfEnabled('variable_shadow', () => (0, variable_shadow_1.checkVariableShadowAst)(ast, file));
126
+ addIfEnabled('redundant_cond', () => (0, redundant_cond_1.checkRedundantCondAst)(ast, file));
127
+ addIfEnabled('unused_local_fun', () => (0, unused_local_fun_1.checkUnusedLocalFunAst)(ast, file));
128
+ addIfEnabled('multiple_setq', () => (0, multiple_setq_1.checkMultipleSetqAst)(ast, file));
129
+ addIfEnabled('redundant_quotes', () => (0, redundant_quotes_1.checkRedundantQuotesAst)(ast, file));
130
+ addIfEnabled('trailing_paren', () => (0, trailing_paren_1.checkTrailingParen)(content, file));
131
+ addIfEnabled('empty_comment', () => (0, empty_comments_1.checkEmptyComments)(content, file));
132
+ addIfEnabled('redundant_setq', () => (0, redundant_setq_1.checkRedundantSetqAst)(ast, file));
133
+ addIfEnabled('redundant_nil_else', () => (0, redundant_nil_else_1.checkRedundantNilElseAst)(ast, file));
134
+ addIfEnabled('single_arg_and_or', () => (0, single_arg_and_or_1.checkSingleArgAndOrAst)(ast, file));
135
+ addIfEnabled('redundant_let', () => (0, redundant_let_1.checkRedundantLetAst)(ast, file));
136
+ addIfEnabled('self_compare', () => (0, self_compare_1.checkSelfCompareAst)(ast, file));
137
+ addIfEnabled('misplaced_else', () => (0, misplaced_else_1.checkMisplacedElseAst)(ast, file));
138
+ addIfEnabled('quote_vs_function', () => (0, quote_vs_function_1.checkQuoteVsFunctionAst)(ast, file));
139
+ addIfEnabled('commented_code', () => (0, commented_code_1.checkCommentedCode)(content, file));
140
+ addIfEnabled('double_not', () => (0, double_not_1.checkDoubleNotAst)(ast, file));
141
+ return issues;
142
+ }
143
+ /** Single-pass visitor-based runChecks using AstVisitor */
144
+ function runChecksWithVisitor(content, file, config) {
145
+ const issues = [];
146
+ const disableMap = (0, disable_1.parseDisableComments)(content);
147
+ const ast = (0, parser_1.parseAst)(content);
148
+ const visitor = new parser_1.AstVisitor();
149
+ const checks = config.checks;
150
+ function addIssue(rule, line, severity, message) {
151
+ if (severity === 'off')
152
+ return;
153
+ if (!(0, disable_1.isDisabled)(rule, line, disableMap)) {
154
+ issues.push({ file, line, severity: severity, rule, message });
155
+ }
156
+ }
157
+ // Register all AST-based checks as visitor handlers
158
+ if (checks['redundant_quotes'] !== 'off') {
159
+ visitor.on('quote', (node) => {
160
+ if (node.children &&
161
+ node.children.length >= 2 &&
162
+ node.children[1].type === 'list' &&
163
+ node.children[1].children &&
164
+ node.children[1].children.length > 0 &&
165
+ node.children[1].children[0].type === 'symbol' &&
166
+ node.children[1].children[0].name === 'quote') {
167
+ addIssue('redundant_quotes', node.pos.line, checks['redundant_quotes'], (0, locale_1.t)('redundant_quotes'));
168
+ }
169
+ });
170
+ }
171
+ if (checks['double_not'] !== 'off') {
172
+ visitor.on('not', (node) => {
173
+ if (node.children &&
174
+ node.children.length >= 2 &&
175
+ node.children[1].type === 'list' &&
176
+ node.children[1].children &&
177
+ node.children[1].children.length > 0 &&
178
+ node.children[1].children[0].type === 'symbol' &&
179
+ node.children[1].children[0].name === 'not') {
180
+ addIssue('double_not', node.pos.line, checks['double_not'], (0, locale_1.t)('double_not'));
181
+ }
182
+ });
183
+ }
184
+ if (checks['misplaced_else'] !== 'off') {
185
+ visitor.on('if', (node) => {
186
+ if (node.children && node.children.length === 4) {
187
+ const cond = node.children[1];
188
+ if (cond.type === 'list' &&
189
+ cond.children &&
190
+ cond.children.length >= 2 &&
191
+ cond.children[0].type === 'symbol' &&
192
+ cond.children[0].name === 'not') {
193
+ const innerName = cond.children[1].name || '';
194
+ addIssue('misplaced_else', node.pos.line, checks['misplaced_else'], (0, locale_1.t)('misplaced_else', innerName));
195
+ }
196
+ }
197
+ });
198
+ }
199
+ if (checks['quote_vs_function'] !== 'off') {
200
+ const higherOrder = new Set([
201
+ 'mapcar',
202
+ 'apply',
203
+ 'lambda',
204
+ 'vl-sort',
205
+ 'vl-sort-i',
206
+ 'vl-remove-if',
207
+ 'vl-remove-if-not',
208
+ 'vl-member-if',
209
+ 'vl-some',
210
+ 'vl-every',
211
+ ]);
212
+ visitor.on('quote', (node) => {
213
+ if (node.children &&
214
+ node.children.length >= 2 &&
215
+ node.children[1].type === 'list' &&
216
+ node.children[1].children &&
217
+ node.children[1].children.length > 0 &&
218
+ node.children[1].children[0].type === 'symbol' &&
219
+ node.children[1].children[0].name === 'lambda') {
220
+ const parent = node.parent;
221
+ if (parent && parent.children && parent.children.length > 0) {
222
+ const pName = (parent.children[0].type === 'symbol' && parent.children[0].name) || '';
223
+ if (higherOrder.has(pName)) {
224
+ addIssue('quote_vs_function', node.pos.line, checks['quote_vs_function'], (0, locale_1.t)('quote_vs_function', pName));
225
+ }
226
+ }
227
+ }
228
+ });
229
+ }
230
+ visitor.run(ast);
101
231
  return issues;
102
232
  }
103
233
  function lintFiles(files, config, rootDir) {
@@ -111,7 +241,10 @@ function lintFiles(files, config, rootDir) {
111
241
  }
112
242
  catch {
113
243
  allIssues.push({
114
- file: relPath, line: 1, severity: 'error', rule: 'read',
244
+ file: relPath,
245
+ line: 1,
246
+ severity: 'error',
247
+ rule: 'read',
115
248
  message: (0, locale_1.t)('runner.read_error'),
116
249
  });
117
250
  continue;
@@ -125,7 +258,7 @@ function lintFiles(files, config, rootDir) {
125
258
  }
126
259
  function lintFilesParallel(files, config, rootDir) {
127
260
  (0, locale_1.setLocale)(config.locale || 'zh');
128
- return new Promise((resolve, reject) => {
261
+ return new Promise((resolve) => {
129
262
  const allIssues = [];
130
263
  let completed = 0;
131
264
  const maxWorkers = Math.min(os.cpus().length, files.length);
@@ -138,7 +271,6 @@ function lintFilesParallel(files, config, rootDir) {
138
271
  const relPath = path.relative(rootDir, filepath);
139
272
  const worker = new worker_threads_1.Worker(path.join(__dirname, 'worker.js'), {
140
273
  workerData: { filepath, relPath, config },
141
- eval: false,
142
274
  });
143
275
  worker.on('message', (msg) => {
144
276
  allIssues.push(...msg.issues);
@@ -150,7 +282,10 @@ function lintFilesParallel(files, config, rootDir) {
150
282
  });
151
283
  worker.on('error', (err) => {
152
284
  allIssues.push({
153
- file: relPath, line: 1, severity: 'error', rule: 'worker',
285
+ file: relPath,
286
+ line: 1,
287
+ severity: 'error',
288
+ rule: 'worker',
154
289
  message: err.message,
155
290
  });
156
291
  completed++;
@@ -159,6 +294,21 @@ function lintFilesParallel(files, config, rootDir) {
159
294
  if (completed >= files.length)
160
295
  resolve(allIssues);
161
296
  });
297
+ worker.on('exit', (code) => {
298
+ if (code !== 0) {
299
+ allIssues.push({
300
+ file: relPath,
301
+ line: 1,
302
+ severity: 'error',
303
+ rule: 'worker',
304
+ message: `Worker exited with code ${code}`,
305
+ });
306
+ completed++;
307
+ startWorker();
308
+ if (completed >= files.length)
309
+ resolve(allIssues);
310
+ }
311
+ });
162
312
  }
163
313
  for (let i = 0; i < maxWorkers; i++) {
164
314
  startWorker();
@@ -167,20 +317,62 @@ function lintFilesParallel(files, config, rootDir) {
167
317
  resolve([]);
168
318
  });
169
319
  }
320
+ const UTF8_BOM = 0xfeff;
170
321
  function fixFile(filepath) {
171
322
  const fixes = [];
172
323
  let content = fs.readFileSync(filepath, 'utf-8');
173
324
  // Fix trailing whitespace
174
- const wsFixed = content.split('\n').map(l => l.replace(/[ \t]+$/, '')).join('\n');
325
+ const wsFixed = content
326
+ .split('\n')
327
+ .map((l) => l.replace(/[ \t]+$/, ''))
328
+ .join('\n');
175
329
  if (wsFixed !== content) {
176
330
  content = wsFixed;
177
331
  fixes.push('trailing_whitespace');
178
332
  }
179
333
  // Fix UTF-8 BOM
180
- if (content.charCodeAt(0) === 0xFEFF) {
334
+ if (content.charCodeAt(0) === UTF8_BOM) {
181
335
  content = content.slice(1);
182
336
  fixes.push('encoding (BOM)');
183
337
  }
338
+ // Fix trailing parens: remove excess ')' from end of content
339
+ let openCount = 0;
340
+ let closeCount = 0;
341
+ let fixContent = content;
342
+ for (let i = 0; i < fixContent.length; i++) {
343
+ const ch = fixContent[i];
344
+ if (ch === '(')
345
+ openCount++;
346
+ else if (ch === ')')
347
+ closeCount++;
348
+ }
349
+ if (closeCount > openCount) {
350
+ let excess = closeCount - openCount;
351
+ while (excess > 0 && fixContent.endsWith(')')) {
352
+ fixContent = fixContent.slice(0, -1);
353
+ excess--;
354
+ }
355
+ // Also trim trailing whitespace after removing parens
356
+ fixContent = fixContent.replace(/\n[ \t]*$/, '');
357
+ if (fixContent !== content) {
358
+ content = fixContent;
359
+ fixes.push('trailing_paren');
360
+ }
361
+ }
362
+ // Fix empty comments: remove lines that are only ';' with optional whitespace
363
+ const lines = content.split('\n');
364
+ let changed = false;
365
+ const fixedLines = lines.map((l) => {
366
+ if (/^\s*;\s*$/.test(l)) {
367
+ changed = true;
368
+ return '';
369
+ }
370
+ return l;
371
+ });
372
+ if (changed) {
373
+ content = fixedLines.join('\n');
374
+ fixes.push('empty_comment');
375
+ }
184
376
  if (fixes.length > 0) {
185
377
  fs.writeFileSync(filepath, content, 'utf-8');
186
378
  }
package/dist/sbcl.js CHANGED
@@ -60,22 +60,26 @@ function runSbclLint(srcDir, sbclConfig, stubPackagesPath) {
60
60
  // Locate lint-sbcl.lisp bundled with the package
61
61
  const scriptPath = path.join(__dirname, '..', 'lib', 'lint-sbcl.lisp');
62
62
  if (!fs.existsSync(scriptPath)) {
63
- return [{
64
- file: 'internal', line: 1, severity: 'error', rule: 'sbcl',
63
+ return [
64
+ {
65
+ file: 'internal',
66
+ line: 1,
67
+ severity: 'error',
68
+ rule: 'sbcl',
65
69
  message: (0, locale_1.t)('sbcl.script_not_found', scriptPath),
66
- }];
70
+ },
71
+ ];
67
72
  }
68
73
  try {
69
74
  // Convert arrays to Lisp-readable format: ("a" "b")
70
- const toLispList = (arr) => '(' + arr.map(s => `"${s}"`).join(' ') + ')';
71
- const locale = (0, locale_1.getLocale)();
75
+ const toLispList = (arr) => '(' + arr.map((s) => `"${s}"`).join(' ') + ')';
72
76
  const result = (0, child_process_1.execFileSync)(sbcl, [
73
- '--script', scriptPath,
77
+ '--script',
78
+ scriptPath,
74
79
  srcDir,
75
80
  stubPackagesPath,
76
81
  toLispList(sbclConfig.walk_exclude),
77
82
  toLispList(sbclConfig.defmacro_allow_files),
78
- locale,
79
83
  ], {
80
84
  encoding: 'utf-8',
81
85
  maxBuffer: 50 * 1024 * 1024,
@@ -111,9 +115,7 @@ function parseSbclOutput(output, srcDir) {
111
115
  const severity = sev === 'ERROR' ? 'error' : 'warn';
112
116
  const lineno = lineStr ? parseInt(lineStr, 10) : 1;
113
117
  // Resolve relative path against srcDir
114
- const resolved = filePath.includes(':')
115
- ? filePath
116
- : filePath.replace(/\\/g, '/');
118
+ const resolved = filePath.includes(':') ? filePath : filePath.replace(/\\/g, '/');
117
119
  issues.push({
118
120
  file: resolved,
119
121
  line: lineno,
@@ -128,7 +130,10 @@ function parseSbclOutput(output, srcDir) {
128
130
  if (sev) {
129
131
  const msg = trimmed.replace(/^\[(ERROR|WARN|NOTE)\]\s*/, '');
130
132
  issues.push({
131
- file: srcDir, line: 1, severity: sev, rule: 'sbcl',
133
+ file: srcDir,
134
+ line: 1,
135
+ severity: sev,
136
+ rule: 'sbcl',
132
137
  message: msg,
133
138
  });
134
139
  }
package/dist/worker.js CHANGED
@@ -3,68 +3,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const worker_threads_1 = require("worker_threads");
4
4
  const fs_1 = require("fs");
5
5
  const locale_1 = require("./locale");
6
- const disable_1 = require("./disable");
7
- const encoding_1 = require("./checks/encoding");
8
- const parens_1 = require("./checks/parens");
9
- const cl_syntax_1 = require("./checks/cl-syntax");
10
- const dangerous_calls_1 = require("./checks/dangerous-calls");
11
- const vlax_1 = require("./checks/vlax");
12
- const token_in_url_1 = require("./checks/token-in-url");
13
- const open_close_1 = require("./checks/open-close");
14
- const bare_names_1 = require("./checks/bare-names");
15
- const module_reg_1 = require("./checks/module-reg");
16
- const namespace_header_1 = require("./checks/namespace-header");
17
- const line_length_1 = require("./checks/line-length");
18
- const function_complexity_1 = require("./checks/function-complexity");
19
- const parameter_naming_1 = require("./checks/parameter-naming");
20
- const unused_variable_1 = require("./checks/unused-variable");
21
- const missing_doc_1 = require("./checks/missing-doc");
22
- const trailing_ws_1 = require("./checks/trailing-ws");
23
- function runChecks(content, file, config) {
24
- const issues = [];
25
- const disableMap = (0, disable_1.parseDisableComments)(content);
26
- function addIfEnabled(rule, fn) {
27
- const results = fn();
28
- for (const r of results) {
29
- if (!(0, disable_1.isDisabled)(r.rule, r.line, disableMap)) {
30
- issues.push(r);
31
- }
32
- }
33
- }
34
- addIfEnabled('encoding', () => (0, encoding_1.checkEncoding)(content, file));
35
- addIfEnabled('parens', () => (0, parens_1.checkParens)(content, file));
36
- addIfEnabled('cl_syntax', () => (0, cl_syntax_1.checkClSyntax)(content, file, config.cl_syntax.keywords));
37
- addIfEnabled('quit_exit', () => (0, dangerous_calls_1.checkDangerousCalls)(content, file, config.dangerous_calls));
38
- addIfEnabled('vlax_without_loading', () => (0, vlax_1.checkVlaxWithoutLoading)(content, file));
39
- addIfEnabled('token_in_url', () => (0, token_in_url_1.checkTokenInUrl)(content, file));
40
- addIfEnabled('open_without_close', () => (0, open_close_1.checkOpenWithoutClose)(content, file));
41
- addIfEnabled('bare_function_names', () => (0, bare_names_1.checkBareFunctionNames)(content, file, config.bare_function_names.allowlist, config.bare_function_names.namespace_pattern));
42
- addIfEnabled('module_registration', () => (0, module_reg_1.checkModuleRegistration)(content, file, file, config.module_registration));
43
- addIfEnabled('namespace_header', () => (0, namespace_header_1.checkNamespaceHeader)(content, file, config.namespace_header));
44
- addIfEnabled('line_length', () => (0, line_length_1.checkLineLength)(content, file, config.line_length.max, config.line_length.tab_width));
45
- addIfEnabled('function_complexity', () => (0, function_complexity_1.checkFunctionComplexity)(content, file, config.function_complexity.max_lines, config.function_complexity.max_nesting));
46
- addIfEnabled('parameter_naming', () => (0, parameter_naming_1.checkParameterNaming)(content, file));
47
- addIfEnabled('unused_variable', () => (0, unused_variable_1.checkUnusedVariable)(content, file));
48
- addIfEnabled('missing_doc', () => (0, missing_doc_1.checkMissingDoc)(content, file));
49
- addIfEnabled('trailing_whitespace', () => (0, trailing_ws_1.checkTrailingWhitespace)(content, file));
50
- return issues;
51
- }
6
+ const runner_1 = require("./runner");
52
7
  if (worker_threads_1.parentPort && worker_threads_1.workerData) {
53
8
  const input = worker_threads_1.workerData;
54
9
  (0, locale_1.setLocale)(input.config.locale || 'zh');
55
10
  try {
56
11
  const content = (0, fs_1.readFileSync)(input.filepath, 'utf-8');
57
- const issues = runChecks(content, input.relPath, input.config);
12
+ const issues = (0, runner_1.runChecks)(content, input.relPath, input.config);
58
13
  const output = { filepath: input.filepath, issues };
59
14
  worker_threads_1.parentPort.postMessage(output);
60
15
  }
61
16
  catch {
62
17
  const output = {
63
18
  filepath: input.filepath,
64
- issues: [{
65
- file: input.relPath, line: 1, severity: 'error', rule: 'read',
19
+ issues: [
20
+ {
21
+ file: input.relPath,
22
+ line: 1,
23
+ severity: 'error',
24
+ rule: 'read',
66
25
  message: 'Cannot read file',
67
- }],
26
+ },
27
+ ],
68
28
  };
69
29
  worker_threads_1.parentPort.postMessage(output);
70
30
  }
@@ -1,49 +1,14 @@
1
1
  ;; @lisp SBCL Lint — syntax validation via SBCL
2
- ;; Usage: sbcl --script lint-sbcl.lisp <src-dir> <stub-packages.json> <walk-exclude-json> <defmacro-allow-files-json> <locale>
2
+ ;; Usage: sbcl --script lint-sbcl.lisp <src-dir> <stub-packages.json> <walk-exclude-json> <defmacro-allow-files-json>
3
3
 
4
4
  (require :uiop)
5
5
 
6
6
  (defparameter *errors* 0)
7
7
  (defparameter *warnings* 0)
8
8
  (defparameter *checked* 0)
9
- (defparameter *locale* "zh")
10
-
11
- ;; ── i18n ──────────────────────────────────────────────────────────────
12
- (defun i18n (key)
13
- (cdr (assoc key
14
- (if (string= *locale* "zh")
15
- '((:not-found . "not found symbol")
16
- (:syntax-error . "syntax error")
17
- (:vec-syntax . "#( vector syntax")
18
- (:char-syntax . "# backslash char literal")
19
- (:eval-syntax . "#. read-time eval")
20
- (:defmacro . "defmacro")
21
- (:trailing-ws . "trailing whitespace")
22
- (:ok . "OK")
23
- (:fail . "FAIL")
24
- (:header . " @lisp SBCL Lint")
25
- (:source . " Source")
26
- (:found . " Found ~d .lsp files")
27
- (:scanned . " Scanned")
28
- (:errors . " Errors")
29
- (:warnings . " Warnings"))
30
- '((:not-found . "not found symbol")
31
- (:syntax-error . "syntax error")
32
- (:vec-syntax . "#( vector syntax")
33
- (:char-syntax . "# backslash char literal")
34
- (:eval-syntax . "#. read-time eval")
35
- (:defmacro . "defmacro")
36
- (:trailing-ws . "trailing whitespace")
37
- (:ok . "OK")
38
- (:fail . "FAIL")
39
- (:header . " @lisp SBCL Lint")
40
- (:source . " Source")
41
- (:found . " Found ~d .lsp files")
42
- (:scanned . " Scanned")
43
- (:errors . " Errors")
44
- (:warnings . " Warnings"))))))
45
9
 
46
10
  ;; ── Stub packages from external file ──────────────────────────────────
11
+ ;; File format: alist ((name use*) ...) — valid Lisp data
47
12
  (defun load-stub-packages (file-path)
48
13
  (flet ((ensure-pkg (name &optional use-list)
49
14
  (unless (find-package name)
@@ -87,10 +52,11 @@
87
52
  (let ((msg (princ-to-string e)))
88
53
  (cond
89
54
  ((search "not found" msg)
90
- (format t "~% [NOTE] ~a: ~a~%" rel (i18n :not-found))
55
+ (format t "~% [NOTE] ~a: ~a~%" rel
56
+ (subseq msg 0 (min 80 (length msg))))
91
57
  (incf *warnings*))
92
58
  (t
93
- (format t "~a~% [ERROR] ~a: ~a~%" (i18n :fail) rel (i18n :syntax-error))
59
+ (format t "FAIL~% [ERROR] ~a: ~a~%" rel e)
94
60
  (incf *errors*)
95
61
  (return-from check-file))))))
96
62
  ;; Trailing whitespace
@@ -101,7 +67,7 @@
101
67
  when (and (> (length line) 0)
102
68
  (find (char line (1- (length line))) '(#\Space #\Tab)))
103
69
  do (progn
104
- (format t "~% [WARN] ~a line ~d: ~a~%" rel lineno (i18n :trailing-ws))
70
+ (format t "~% [WARN] ~a line ~d: trailing whitespace~%" rel lineno)
105
71
  (incf *warnings*))))
106
72
  ;; CL-ism checks: raw content scan
107
73
  (with-open-file (s path :direction :input)
@@ -109,19 +75,19 @@
109
75
  (file-position s 0)
110
76
  (read-sequence content s)
111
77
  (when (search "#(" content)
112
- (format t "~% [WARN] ~a: ~a~%" rel (i18n :vec-syntax))
78
+ (format t "~% [WARN] ~a: #( vector syntax (not valid in AutoLISP)~%" rel)
113
79
  (incf *warnings*))
114
80
  (when (search "#\\" content)
115
- (format t "~% [WARN] ~a: ~a~%" rel (i18n :char-syntax))
81
+ (format t "~% [WARN] ~a: #\\ character literal syntax (not valid in AutoLISP)~%" rel)
116
82
  (incf *warnings*))
117
83
  (when (search "#." content)
118
- (format t "~% [WARN] ~a: ~a~%" rel (i18n :eval-syntax))
84
+ (format t "~% [WARN] ~a: #. read-time eval syntax (not valid in AutoLISP)~%" rel)
119
85
  (incf *warnings*))
120
86
  (when (and (search "defmacro" content)
121
87
  (not (some (lambda (s) (search s fullname)) defmacro-allow-files)))
122
- (format t "~% [WARN] ~a: ~a~%" rel (i18n :defmacro))
88
+ (format t "~% [WARN] ~a: defmacro (not valid in AutoLISP)~%" rel)
123
89
  (incf *warnings*))))
124
- (format t "~a~%" (i18n :ok))))
90
+ (format t "OK~%")))
125
91
 
126
92
  ;; ── Main ──────────────────────────────────────────────────────────────
127
93
  (defun main ()
@@ -133,26 +99,25 @@
133
99
  '(".vscode" "test" "experiment" "tools" ".git")))
134
100
  (defmacro-allow (if (fourth args)
135
101
  (read-from-string (fourth args))
136
- '("compat-cl")))
137
- (locale (fifth args)))
138
- (when locale (setf *locale* locale))
102
+ '("compat-cl"))))
103
+ ;; Load stub packages for CAD runtime symbols
139
104
  (load-stub-packages stub-file)
140
105
 
141
106
  (format t "~%==================================================~%")
142
- (format t "~a~%" (i18n :header))
143
- (format t "~a: ~a~%" (i18n :source) (namestring (pathname src-dir)))
107
+ (format t " @lisp SBCL Lint~%")
108
+ (format t " Source: ~a~%" (namestring (pathname src-dir)))
144
109
  (format t "==================================================~%~%")
145
110
  (setf *errors* 0 *warnings* 0 *checked* 0)
146
111
 
147
112
  (let ((files (walk-tree src-dir walk-exclude)))
148
- (format t "~a~%~%" (format nil (i18n :found) (length files)))
113
+ (format t " Found ~d .lsp files~%~%" (length files))
149
114
  (dolist (f files)
150
115
  (check-file f src-dir defmacro-allow)))
151
116
 
152
117
  (format t "~%==================================================~%")
153
- (format t "~a: ~d~%" (i18n :scanned) *checked*)
154
- (format t "~a: ~d~%" (i18n :errors) *errors*)
155
- (format t "~a: ~d~%" (i18n :warnings) *warnings*)
118
+ (format t " Scanned: ~d files~%" *checked*)
119
+ (format t " Errors: ~d~%" *errors*)
120
+ (format t " Warnings: ~d~%" *warnings*)
156
121
  (format t "==================================================~%~%")
157
122
  (if (> *errors* 0)
158
123
  (uiop:quit 1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlisp/lint",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "AutoLISP static analysis tool — parens, security, conventions, SBCL syntax validation",
5
5
  "keywords": [
6
6
  "CAD",
@@ -17,22 +17,36 @@
17
17
  },
18
18
  "files": [
19
19
  "dist/",
20
+ "!dist/**/*.map",
20
21
  "lib/",
21
22
  "bin/",
22
23
  "atlisp-lint.default.json",
23
- "stub-packages.json",
24
- "pre-commit/"
24
+ "stub-packages.json"
25
25
  ],
26
26
  "scripts": {
27
- "build": "tsc && node -e \"const fs=require('fs'),p=require('path');fs.mkdirSync(p.join('dist','lib'),{recursive:true});fs.readdirSync('lib').forEach(f=>fs.copyFileSync(p.join('lib',f),p.join('dist','lib',f)));['atlisp-lint.default.json','stub-packages.json'].forEach(f=>fs.copyFileSync(f,p.join('dist',f)))\"",
27
+ "build": "tsc",
28
+ "clean": "rm -rf dist",
28
29
  "test": "jest",
30
+ "test:coverage": "jest --coverage",
31
+ "test:watch": "jest --watch",
32
+ "lint": "eslint src",
33
+ "typecheck": "tsc --noEmit",
34
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
35
+ "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
29
36
  "prepublishOnly": "npm run build",
30
37
  "prepack": "npm run build"
31
38
  },
39
+ "dependencies": {
40
+ "@atlisp/parser": "^0.1.0"
41
+ },
32
42
  "devDependencies": {
33
43
  "@types/jest": "^29",
34
44
  "@types/node": "^18",
45
+ "@typescript-eslint/eslint-plugin": "^7",
46
+ "@typescript-eslint/parser": "^7",
47
+ "eslint": "^8",
35
48
  "jest": "^29",
49
+ "prettier": "^3",
36
50
  "ts-jest": "^29",
37
51
  "ts-node": "^10.9.2",
38
52
  "typescript": "^5"