@atlisp/lint 0.1.17 → 0.1.20

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 (38) hide show
  1. package/README.md +3 -4
  2. package/atlisp-lint.default.json +12 -5
  3. package/dist/checks/bare-names.js +2 -0
  4. package/dist/checks/constant-condition.js +3 -0
  5. package/dist/checks/empty-catch.js +68 -23
  6. package/dist/checks/entget-in-loop.d.ts +3 -0
  7. package/dist/checks/entget-in-loop.js +56 -0
  8. package/dist/checks/if-without-else.d.ts +4 -0
  9. package/dist/checks/if-without-else.js +31 -0
  10. package/dist/checks/long-function-call.js +1 -1
  11. package/dist/checks/promise-handler.d.ts +4 -0
  12. package/dist/checks/promise-handler.js +53 -0
  13. package/dist/checks/redundant-list.d.ts +4 -0
  14. package/dist/checks/redundant-list.js +22 -0
  15. package/dist/checks/string-concat-loop.d.ts +3 -0
  16. package/dist/checks/string-concat-loop.js +59 -0
  17. package/dist/checks/undeclared-setq.js +2 -0
  18. package/dist/checks/unused-catch-result.d.ts +3 -0
  19. package/dist/checks/unused-catch-result.js +28 -0
  20. package/dist/config.js +11 -4
  21. package/dist/formatters-html.d.ts +3 -0
  22. package/dist/formatters-html.js +91 -0
  23. package/dist/formatters-sarif.d.ts +3 -0
  24. package/dist/formatters-sarif.js +55 -0
  25. package/dist/index.js +41 -2
  26. package/dist/locale.js +23 -7
  27. package/dist/presets.js +16 -2
  28. package/dist/project.js +180 -2
  29. package/dist/rules.js +8 -1
  30. package/dist/runner.d.ts +1 -2
  31. package/dist/runner.js +106 -224
  32. package/dist/validate.js +8 -1
  33. package/dist/visitor-runner.d.ts +4 -0
  34. package/dist/visitor-runner.js +709 -0
  35. package/package.json +1 -1
  36. package/dist/atlisp-lint.default.json +0 -90
  37. package/dist/lib/lint-sbcl.lisp +0 -161
  38. package/dist/stub-packages.json +0 -41
package/dist/runner.js CHANGED
@@ -34,7 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.runChecks = runChecks;
37
- exports.runChecksWithVisitor = runChecksWithVisitor;
37
+ exports.runChecksWithVisitorWrapper = runChecksWithVisitorWrapper;
38
38
  exports.lintFiles = lintFiles;
39
39
  exports.lintFilesParallel = lintFilesParallel;
40
40
  exports.applyFixes = applyFixes;
@@ -47,46 +47,14 @@ const os = __importStar(require("os"));
47
47
  const encoding_1 = require("./checks/encoding");
48
48
  const parens_1 = require("./checks/parens");
49
49
  const cl_syntax_1 = require("./checks/cl-syntax");
50
- const dangerous_calls_1 = require("./checks/dangerous-calls");
51
50
  const vlax_1 = require("./checks/vlax");
52
51
  const token_in_url_1 = require("./checks/token-in-url");
53
- const open_close_1 = require("./checks/open-close");
54
52
  const bare_names_1 = require("./checks/bare-names");
55
53
  const module_reg_1 = require("./checks/module-reg");
56
54
  const namespace_header_1 = require("./checks/namespace-header");
57
55
  const trailing_ws_1 = require("./checks/trailing-ws");
58
56
  const line_length_1 = require("./checks/line-length");
59
- const function_complexity_1 = require("./checks/function-complexity");
60
- const parameter_naming_1 = require("./checks/parameter-naming");
61
- const unused_variable_1 = require("./checks/unused-variable");
62
57
  const missing_doc_1 = require("./checks/missing-doc");
63
- const unused_param_1 = require("./checks/unused-param");
64
- const constant_condition_1 = require("./checks/constant-condition");
65
- const redundant_progn_1 = require("./checks/redundant-progn");
66
- const empty_branch_1 = require("./checks/empty-branch");
67
- const unused_let_1 = require("./checks/unused-let");
68
- const recursive_call_1 = require("./checks/recursive-call");
69
- const variable_shadow_1 = require("./checks/variable-shadow");
70
- const redundant_cond_1 = require("./checks/redundant-cond");
71
- const unused_local_fun_1 = require("./checks/unused-local-fun");
72
- const multiple_setq_1 = require("./checks/multiple-setq");
73
- const redundant_quotes_1 = require("./checks/redundant-quotes");
74
- const trailing_paren_1 = require("./checks/trailing-paren");
75
- const empty_comments_1 = require("./checks/empty-comments");
76
- const redundant_setq_1 = require("./checks/redundant-setq");
77
- const redundant_nil_else_1 = require("./checks/redundant-nil-else");
78
- const single_arg_and_or_1 = require("./checks/single-arg-and-or");
79
- const redundant_let_1 = require("./checks/redundant-let");
80
- const self_compare_1 = require("./checks/self-compare");
81
- const misplaced_else_1 = require("./checks/misplaced-else");
82
- const quote_vs_function_1 = require("./checks/quote-vs-function");
83
- const commented_code_1 = require("./checks/commented-code");
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
58
  const error_handling_1 = require("./checks/error-handling");
91
59
  const global_naming_1 = require("./checks/global-naming");
92
60
  const extra_parens_1 = require("./checks/extra-parens");
@@ -113,53 +81,22 @@ const type_check_1 = require("./checks/type-check");
113
81
  const redundant_if_1 = require("./checks/redundant-if");
114
82
  const undeclared_setq_1 = require("./checks/undeclared-setq");
115
83
  const format_indent_1 = require("./checks/format-indent");
84
+ const trailing_paren_1 = require("./checks/trailing-paren");
85
+ const empty_comments_1 = require("./checks/empty-comments");
86
+ const commented_code_1 = require("./checks/commented-code");
87
+ const unused_catch_result_1 = require("./checks/unused-catch-result");
88
+ const string_concat_loop_1 = require("./checks/string-concat-loop");
116
89
  const config_1 = require("./config");
117
90
  const locale_1 = require("./locale");
118
91
  const disable_1 = require("./disable");
119
92
  const parser_1 = require("@atlisp/parser");
93
+ const visitor_runner_1 = require("./visitor-runner");
120
94
  function runChecks(content, file, config) {
121
95
  const issues = [];
122
96
  const disableMap = (0, disable_1.parseDisableComments)(content);
123
- // Deferred AST parsing: only parse if any AST-based checks are enabled
124
- const astRules = new Set([
125
- 'quit_exit',
126
- 'open_without_close',
127
- 'function_complexity',
128
- 'parameter_naming',
129
- 'unused_variable',
130
- 'unused_parameter',
131
- 'constant_condition',
132
- 'redundant_progn',
133
- 'empty_branch',
134
- 'unused_let_binding',
135
- 'recursive_call',
136
- 'variable_shadow',
137
- 'redundant_cond',
138
- 'unused_local_fun',
139
- 'multiple_setq',
140
- 'redundant_quotes',
141
- 'redundant_setq',
142
- 'redundant_nil_else',
143
- 'single_arg_and_or',
144
- 'redundant_let',
145
- 'self_compare',
146
- 'misplaced_else',
147
- 'quote_vs_function',
148
- 'double_not',
149
- 'setq_single_arg',
150
- 'assoc_without_cdr',
151
- 'identical_branches',
152
- 'while_constant',
153
- 'cond_duplicate',
154
- ]);
155
97
  const checks = config.checks;
156
- const needsAst = Array.from(astRules).some(r => checks[r] !== 'off');
157
- let ast;
158
- if (needsAst) {
159
- ast = (0, parser_1.parseAst)(content);
160
- }
161
98
  function addIfEnabled(rule, fn) {
162
- const severity = config.checks[rule];
99
+ const severity = checks[rule];
163
100
  if (!severity || severity === 'off')
164
101
  return;
165
102
  const results = fn();
@@ -169,49 +106,28 @@ function runChecks(content, file, config) {
169
106
  }
170
107
  }
171
108
  }
109
+ function addIssues(ruleIssues) {
110
+ for (const r of ruleIssues) {
111
+ if (!(0, disable_1.isDisabled)(r.rule, r.line, disableMap)) {
112
+ issues.push(r);
113
+ }
114
+ }
115
+ }
116
+ // Non-AST checks (run before AST parsing)
172
117
  addIfEnabled('encoding', () => (0, encoding_1.checkEncoding)(content, file));
173
118
  addIfEnabled('parens', () => (0, parens_1.checkParens)(content, file));
174
119
  addIfEnabled('cl_syntax', () => (0, cl_syntax_1.checkClSyntax)(content, file, config.cl_syntax.keywords));
175
- addIfEnabled('quit_exit', () => (0, dangerous_calls_1.checkDangerousCallsAst)(ast, file, config.dangerous_calls));
176
120
  addIfEnabled('vlax_without_loading', () => (0, vlax_1.checkVlaxWithoutLoading)(content, file));
177
121
  addIfEnabled('token_in_url', () => (0, token_in_url_1.checkTokenInUrl)(content, file));
178
- addIfEnabled('open_without_close', () => (0, open_close_1.checkOpenWithoutCloseAst)(ast, file));
179
122
  addIfEnabled('bare_function_names', () => (0, bare_names_1.checkBareFunctionNames)(content, file, config.bare_function_names.allowlist, config.bare_function_names.namespace_pattern));
180
123
  addIfEnabled('module_registration', () => (0, module_reg_1.checkModuleRegistration)(content, file, file, config.module_registration));
181
124
  addIfEnabled('namespace_header', () => (0, namespace_header_1.checkNamespaceHeader)(content, file, config.namespace_header));
182
125
  addIfEnabled('line_length', () => (0, line_length_1.checkLineLength)(content, file, config.line_length.max, config.line_length.tab_width));
183
- addIfEnabled('function_complexity', () => (0, function_complexity_1.checkFunctionComplexityAst)(ast, file, config.function_complexity.max_lines, config.function_complexity.max_nesting));
184
- addIfEnabled('parameter_naming', () => (0, parameter_naming_1.checkParameterNamingAst)(ast, file));
185
- addIfEnabled('unused_variable', () => (0, unused_variable_1.checkUnusedVariableAst)(ast, file));
186
126
  addIfEnabled('missing_doc', () => (0, missing_doc_1.checkMissingDoc)(content, file));
187
127
  addIfEnabled('trailing_whitespace', () => (0, trailing_ws_1.checkTrailingWhitespace)(content, file));
188
- addIfEnabled('unused_parameter', () => (0, unused_param_1.checkUnusedParameterAst)(ast, file));
189
- addIfEnabled('constant_condition', () => (0, constant_condition_1.checkConstantConditionAst)(ast, file));
190
- addIfEnabled('redundant_progn', () => (0, redundant_progn_1.checkRedundantPrognAst)(ast, file));
191
- addIfEnabled('empty_branch', () => (0, empty_branch_1.checkEmptyBranchAst)(ast, file));
192
- addIfEnabled('unused_let_binding', () => (0, unused_let_1.checkUnusedLetBindingAst)(ast, file));
193
- addIfEnabled('recursive_call', () => (0, recursive_call_1.checkRecursiveCallAst)(ast, file));
194
- addIfEnabled('variable_shadow', () => (0, variable_shadow_1.checkVariableShadowAst)(ast, file));
195
- addIfEnabled('redundant_cond', () => (0, redundant_cond_1.checkRedundantCondAst)(ast, file));
196
- addIfEnabled('unused_local_fun', () => (0, unused_local_fun_1.checkUnusedLocalFunAst)(ast, file));
197
- addIfEnabled('multiple_setq', () => (0, multiple_setq_1.checkMultipleSetqAst)(ast, file));
198
- addIfEnabled('redundant_quotes', () => (0, redundant_quotes_1.checkRedundantQuotesAst)(ast, file));
199
128
  addIfEnabled('trailing_paren', () => (0, trailing_paren_1.checkTrailingParen)(content, file));
200
129
  addIfEnabled('empty_comment', () => (0, empty_comments_1.checkEmptyComments)(content, file));
201
- addIfEnabled('redundant_setq', () => (0, redundant_setq_1.checkRedundantSetqAst)(ast, file));
202
- addIfEnabled('redundant_nil_else', () => (0, redundant_nil_else_1.checkRedundantNilElseAst)(ast, file));
203
- addIfEnabled('single_arg_and_or', () => (0, single_arg_and_or_1.checkSingleArgAndOrAst)(ast, file));
204
- addIfEnabled('redundant_let', () => (0, redundant_let_1.checkRedundantLetAst)(ast, file));
205
- addIfEnabled('self_compare', () => (0, self_compare_1.checkSelfCompareAst)(ast, file));
206
- addIfEnabled('misplaced_else', () => (0, misplaced_else_1.checkMisplacedElseAst)(ast, file));
207
- addIfEnabled('quote_vs_function', () => (0, quote_vs_function_1.checkQuoteVsFunctionAst)(ast, file));
208
130
  addIfEnabled('commented_code', () => (0, commented_code_1.checkCommentedCode)(content, file));
209
- addIfEnabled('double_not', () => (0, double_not_1.checkDoubleNotAst)(ast, file));
210
- addIfEnabled('setq_single_arg', () => (0, setq_single_arg_1.checkSetqSingleArgAst)(ast, file));
211
- addIfEnabled('assoc_without_cdr', () => (0, assoc_without_cdr_1.checkAssocWithoutCdrAst)(ast, file));
212
- addIfEnabled('identical_branches', () => (0, identical_branches_1.checkIdenticalBranchesAst)(ast, file));
213
- addIfEnabled('while_constant', () => (0, while_constant_1.checkWhileConstantAst)(ast, file));
214
- addIfEnabled('cond_duplicate', () => (0, cond_duplicate_1.checkCondDuplicateAst)(ast, file));
215
131
  addIfEnabled('error_handling', () => (0, error_handling_1.checkErrorHandling)(content, file));
216
132
  addIfEnabled('global_naming', () => (0, global_naming_1.checkGlobalNaming)(content, file));
217
133
  addIfEnabled('extra_parens', () => (0, extra_parens_1.checkExtraParens)(content, file));
@@ -238,97 +154,34 @@ function runChecks(content, file, config) {
238
154
  addIfEnabled('redundant_if', () => (0, redundant_if_1.checkRedundantIf)(content, file));
239
155
  addIfEnabled('undeclared_setq', () => (0, undeclared_setq_1.checkUndeclaredSetq)(content, file));
240
156
  addIfEnabled('format_indent', () => (0, format_indent_1.checkFormatIndent)(content, file, config.line_length.tab_width));
157
+ // New checks
158
+ addIfEnabled('unused_catch_result', () => (0, unused_catch_result_1.checkUnusedCatchResult)(content, file));
159
+ addIfEnabled('string_concat_loop', () => (0, string_concat_loop_1.checkStringConcatInLoop)(content, file));
160
+ // AST-based checks via single-pass visitor
161
+ const astRules = new Set([
162
+ 'quit_exit', 'open_without_close', 'function_complexity',
163
+ 'parameter_naming', 'unused_variable', 'unused_parameter',
164
+ 'constant_condition', 'redundant_progn', 'empty_branch',
165
+ 'unused_let_binding', 'recursive_call', 'variable_shadow',
166
+ 'unused_local_fun', 'multiple_setq', 'redundant_quotes',
167
+ 'redundant_setq', 'redundant_nil_else', 'single_arg_and_or',
168
+ 'redundant_let', 'self_compare', 'misplaced_else',
169
+ 'quote_vs_function', 'double_not', 'setq_single_arg',
170
+ 'assoc_without_cdr', 'identical_branches', 'while_constant',
171
+ 'cond_duplicate',
172
+ ]);
173
+ const needsAst = Array.from(astRules).some(r => checks[r] !== 'off');
174
+ if (needsAst) {
175
+ const ast = (0, parser_1.parseAst)(content);
176
+ const visitorIssues = (0, visitor_runner_1.runChecksWithVisitor)(ast, file, config, disableMap);
177
+ addIssues(visitorIssues);
178
+ }
241
179
  return issues;
242
180
  }
243
- /** Single-pass visitor-based runChecks using AstVisitor */
244
- function runChecksWithVisitor(content, file, config) {
245
- const issues = [];
181
+ function runChecksWithVisitorWrapper(content, file, config) {
246
182
  const disableMap = (0, disable_1.parseDisableComments)(content);
247
183
  const ast = (0, parser_1.parseAst)(content);
248
- const visitor = new parser_1.AstVisitor();
249
- const checks = config.checks;
250
- function addIssue(rule, line, severity, message) {
251
- if (severity === 'off')
252
- return;
253
- if (!(0, disable_1.isDisabled)(rule, line, disableMap)) {
254
- issues.push({ file, line, severity: severity, rule, message });
255
- }
256
- }
257
- // Register all AST-based checks as visitor handlers
258
- if (checks['redundant_quotes'] !== 'off') {
259
- visitor.on('quote', node => {
260
- if (node.children &&
261
- node.children.length >= 2 &&
262
- node.children[1].type === 'list' &&
263
- node.children[1].children &&
264
- node.children[1].children.length > 0 &&
265
- node.children[1].children[0].type === 'symbol' &&
266
- node.children[1].children[0].name === 'quote') {
267
- addIssue('redundant_quotes', node.pos.line, checks['redundant_quotes'], (0, locale_1.t)('redundant_quotes'));
268
- }
269
- });
270
- }
271
- if (checks['double_not'] !== 'off') {
272
- visitor.on('not', node => {
273
- if (node.children &&
274
- node.children.length >= 2 &&
275
- node.children[1].type === 'list' &&
276
- node.children[1].children &&
277
- node.children[1].children.length > 0 &&
278
- node.children[1].children[0].type === 'symbol' &&
279
- node.children[1].children[0].name === 'not') {
280
- addIssue('double_not', node.pos.line, checks['double_not'], (0, locale_1.t)('double_not'));
281
- }
282
- });
283
- }
284
- if (checks['misplaced_else'] !== 'off') {
285
- visitor.on('if', node => {
286
- if (node.children && node.children.length === 4) {
287
- const cond = node.children[1];
288
- if (cond.type === 'list' &&
289
- cond.children &&
290
- cond.children.length >= 2 &&
291
- cond.children[0].type === 'symbol' &&
292
- cond.children[0].name === 'not') {
293
- const innerName = cond.children[1].name || '';
294
- addIssue('misplaced_else', node.pos.line, checks['misplaced_else'], (0, locale_1.t)('misplaced_else', innerName));
295
- }
296
- }
297
- });
298
- }
299
- if (checks['quote_vs_function'] !== 'off') {
300
- const higherOrder = new Set([
301
- 'mapcar',
302
- 'apply',
303
- 'lambda',
304
- 'vl-sort',
305
- 'vl-sort-i',
306
- 'vl-remove-if',
307
- 'vl-remove-if-not',
308
- 'vl-member-if',
309
- 'vl-some',
310
- 'vl-every',
311
- ]);
312
- visitor.on('quote', node => {
313
- if (node.children &&
314
- node.children.length >= 2 &&
315
- node.children[1].type === 'list' &&
316
- node.children[1].children &&
317
- node.children[1].children.length > 0 &&
318
- node.children[1].children[0].type === 'symbol' &&
319
- node.children[1].children[0].name === 'lambda') {
320
- const parent = node.parent;
321
- if (parent && parent.children && parent.children.length > 0) {
322
- const pName = (parent.children[0].type === 'symbol' && parent.children[0].name) || '';
323
- if (higherOrder.has(pName)) {
324
- addIssue('quote_vs_function', node.pos.line, checks['quote_vs_function'], (0, locale_1.t)('quote_vs_function', pName));
325
- }
326
- }
327
- }
328
- });
329
- }
330
- visitor.run(ast);
331
- return issues;
184
+ return (0, visitor_runner_1.runChecksWithVisitor)(ast, file, config, disableMap);
332
185
  }
333
186
  function lintFiles(files, config, rootDir) {
334
187
  (0, locale_1.setLocale)(config.locale || 'zh');
@@ -349,7 +202,6 @@ function lintFiles(files, config, rootDir) {
349
202
  });
350
203
  continue;
351
204
  }
352
- // Per-directory config override
353
205
  const override = (0, config_1.findOverrides)(filepath);
354
206
  const effectiveConfig = override ? (0, config_1.mergeOverrides)(config, override) : config;
355
207
  allIssues.push(...runChecks(content, relPath, effectiveConfig));
@@ -417,7 +269,6 @@ function lintFilesParallel(files, config, rootDir) {
417
269
  resolve([]);
418
270
  });
419
271
  }
420
- /** Find the matching close paren for the first complete form starting at `start` */
421
272
  function findMatchingParen(s, start) {
422
273
  if (s[start] !== '(')
423
274
  return start;
@@ -442,7 +293,6 @@ function findMatchingParen(s, start) {
442
293
  }
443
294
  return s.length - 1;
444
295
  }
445
- /** Extract the next top-level form(s) from a string starting at `start`. Returns [form, endIdx]. */
446
296
  function extractForm(s, start) {
447
297
  let i = start;
448
298
  while (i < s.length && s[i] === ' ')
@@ -468,18 +318,12 @@ function applyFixes(issues, content, filepath) {
468
318
  set.add(iss.line);
469
319
  fixesByRule.set(iss.rule, set);
470
320
  }
471
- // Apply fixes from higher line numbers first to preserve offsets
472
321
  const fixableRules = new Set([
473
- 'redundant_quotes',
474
- 'double_not',
475
- 'redundant_nil_else',
476
- 'single_arg_and_or',
477
- 'redundant_setq',
478
- 'redundant_progn',
479
- 'redundant_let',
480
- 'quote_style',
481
- 'redundant_if',
482
- 'misplaced_else',
322
+ 'redundant_quotes', 'double_not', 'redundant_nil_else',
323
+ 'single_arg_and_or', 'redundant_setq', 'redundant_progn',
324
+ 'redundant_let', 'quote_style', 'redundant_if', 'misplaced_else',
325
+ 'setq_multiple', 'append_single', 'nth_usage', 'setq_single_arg',
326
+ 'extra_parens', 'empty_branch',
483
327
  ]);
484
328
  let result = content;
485
329
  for (const [rule, lineSet] of fixesByRule) {
@@ -493,39 +337,30 @@ function applyFixes(issues, content, filepath) {
493
337
  const lineContent = result.split('\n')[idx];
494
338
  switch (rule) {
495
339
  case 'redundant_quotes': {
496
- // Replace ''x with 'x on this line
497
340
  const fixed = lineContent.replace(/''(\S)/g, "'$1");
498
- if (fixed !== lineContent) {
341
+ if (fixed !== lineContent)
499
342
  result = replaceLine(result, idx, fixed);
500
- }
501
343
  break;
502
344
  }
503
345
  case 'double_not': {
504
- // Replace (not (not ...)) with (not ...)
505
346
  const fixed = lineContent.replace(/\(not\s+\(not\s+/g, '(not ');
506
- if (fixed !== lineContent) {
347
+ if (fixed !== lineContent)
507
348
  result = replaceLine(result, idx, fixed);
508
- }
509
349
  break;
510
350
  }
511
351
  case 'redundant_nil_else': {
512
- // Replace (if ... nil) with (if ...)
513
352
  const fixed = lineContent.replace(/\s+nil\s*\)$/, ')');
514
- if (fixed !== lineContent) {
353
+ if (fixed !== lineContent)
515
354
  result = replaceLine(result, idx, fixed);
516
- }
517
355
  break;
518
356
  }
519
357
  case 'single_arg_and_or': {
520
- // Replace (and x) with x, (or x) with x
521
358
  const m = lineContent.match(/\((and|or)\s+([^)\s]+)\s*\)/);
522
- if (m) {
359
+ if (m)
523
360
  result = replaceLine(result, idx, m[2]);
524
- }
525
361
  break;
526
362
  }
527
363
  case 'redundant_progn': {
528
- // (progn <single-form>) → <single-form>
529
364
  const proIdx = lineContent.indexOf('(progn ');
530
365
  if (proIdx !== -1) {
531
366
  const beforePro = lineContent.slice(0, proIdx);
@@ -541,14 +376,12 @@ function applyFixes(issues, content, filepath) {
541
376
  break;
542
377
  }
543
378
  case 'redundant_let': {
544
- // (let () body...) → (progn body...)
545
379
  const fixed = lineContent.replace(/\(let\s+\(\)\s*/, '(progn ');
546
380
  if (fixed !== lineContent)
547
381
  result = replaceLine(result, idx, fixed);
548
382
  break;
549
383
  }
550
384
  case 'quote_style': {
551
- // (quote x) → 'x
552
385
  const qIdx = lineContent.indexOf('(quote ');
553
386
  if (qIdx !== -1) {
554
387
  const beforeQ = lineContent.slice(0, qIdx);
@@ -564,7 +397,6 @@ function applyFixes(issues, content, filepath) {
564
397
  break;
565
398
  }
566
399
  case 'redundant_if': {
567
- // (if/when/unless (progn ...) ...) → remove inner progn
568
400
  const pIdx = lineContent.indexOf('(progn ');
569
401
  if (pIdx !== -1) {
570
402
  const beforeP = lineContent.slice(0, pIdx);
@@ -580,7 +412,6 @@ function applyFixes(issues, content, filepath) {
580
412
  break;
581
413
  }
582
414
  case 'misplaced_else': {
583
- // (if (not x) then else) → (if x else then)
584
415
  const ifStart = lineContent.indexOf('(if (not ');
585
416
  if (ifStart !== -1) {
586
417
  const notIdx = lineContent.indexOf('(not ', ifStart);
@@ -607,7 +438,6 @@ function applyFixes(issues, content, filepath) {
607
438
  break;
608
439
  }
609
440
  case 'redundant_setq': {
610
- // Remove (setq x x) lines
611
441
  const allLines = result.split('\n');
612
442
  const trimmed = allLines[idx].trim();
613
443
  if (/^\(setq\s+(\S+)\s+\1\s*\)$/.test(trimmed)) {
@@ -616,6 +446,63 @@ function applyFixes(issues, content, filepath) {
616
446
  }
617
447
  break;
618
448
  }
449
+ case 'setq_multiple': {
450
+ const fixed = lineContent.replace(/\)\(setq\s+/g, ' ');
451
+ if (fixed !== lineContent) {
452
+ const merged = lineContent.replace(/^\(\s*setq\s+/, '(setq ');
453
+ if (merged.includes('setq') && !merged.match(/\)\(/)) {
454
+ result = replaceLine(result, idx, merged);
455
+ }
456
+ else {
457
+ const simplified = lineContent.replace(/\)\(setq\s+/g, '\n');
458
+ if (simplified !== lineContent)
459
+ result = replaceLine(result, idx, simplified);
460
+ }
461
+ }
462
+ break;
463
+ }
464
+ case 'append_single': {
465
+ const aIdx = lineContent.indexOf('(append ');
466
+ if (aIdx !== -1) {
467
+ const close = findMatchingParen(lineContent, aIdx);
468
+ if (close > aIdx) {
469
+ const inner = lineContent.slice(aIdx + 8, close);
470
+ const listMatch = inner.match(/^\(list\s+(.*)\)\s*$/);
471
+ if (listMatch) {
472
+ const fixed = lineContent.slice(0, aIdx) + '(cons ' + listMatch[1] + ')' + lineContent.slice(close + 1);
473
+ if (fixed !== lineContent)
474
+ result = replaceLine(result, idx, fixed);
475
+ }
476
+ }
477
+ }
478
+ break;
479
+ }
480
+ case 'nth_usage': {
481
+ const fixed = lineContent.replace(/\(nth\s+0\s+/g, '(car ');
482
+ if (fixed !== lineContent)
483
+ result = replaceLine(result, idx, fixed);
484
+ break;
485
+ }
486
+ case 'setq_single_arg': {
487
+ const allLines = result.split('\n');
488
+ if (/^\s*\(setq\s+\S+\s*\)\s*$/.test(allLines[idx])) {
489
+ allLines.splice(idx, 1);
490
+ result = allLines.join('\n');
491
+ }
492
+ break;
493
+ }
494
+ case 'extra_parens': {
495
+ const fixed = lineContent.replace(/\){2,}/g, ')');
496
+ if (fixed !== lineContent)
497
+ result = replaceLine(result, idx, fixed);
498
+ break;
499
+ }
500
+ case 'empty_branch': {
501
+ const fixed = lineContent.replace(/\(if\s+\S+\s*\)/g, m => m.slice(0, -1) + ' nil)');
502
+ if (fixed !== lineContent)
503
+ result = replaceLine(result, idx, fixed);
504
+ break;
505
+ }
619
506
  }
620
507
  }
621
508
  }
@@ -640,7 +527,6 @@ const UTF8_BOM = 0xfeff;
640
527
  function fixFile(filepath) {
641
528
  const fixes = [];
642
529
  let content = fs.readFileSync(filepath, 'utf-8');
643
- // Fix trailing whitespace
644
530
  const wsFixed = content
645
531
  .split('\n')
646
532
  .map(l => l.replace(/[ \t]+$/, ''))
@@ -649,12 +535,10 @@ function fixFile(filepath) {
649
535
  content = wsFixed;
650
536
  fixes.push('trailing_whitespace');
651
537
  }
652
- // Fix UTF-8 BOM
653
538
  if (content.charCodeAt(0) === UTF8_BOM) {
654
539
  content = content.slice(1);
655
540
  fixes.push('encoding (BOM)');
656
541
  }
657
- // Fix trailing parens: remove excess ')' from end of content
658
542
  let openCount = 0;
659
543
  let closeCount = 0;
660
544
  let fixContent = content;
@@ -671,14 +555,12 @@ function fixFile(filepath) {
671
555
  fixContent = fixContent.slice(0, -1);
672
556
  excess--;
673
557
  }
674
- // Also trim trailing whitespace after removing parens
675
558
  fixContent = fixContent.replace(/\n[ \t]*$/, '');
676
559
  if (fixContent !== content) {
677
560
  content = fixContent;
678
561
  fixes.push('trailing_paren');
679
562
  }
680
563
  }
681
- // Fix empty comments: remove lines that are only ';' with optional whitespace
682
564
  const lines = content.split('\n');
683
565
  let changed = false;
684
566
  const fixedLines = lines.map(l => {
package/dist/validate.js CHANGED
@@ -50,7 +50,6 @@ const VALID_RULES = [
50
50
  'quote_style',
51
51
  'quote_vs_function',
52
52
  'recursive_call',
53
- 'redundant_cond',
54
53
  'redundant_if',
55
54
  'redundant_let',
56
55
  'redundant_nil_else',
@@ -78,6 +77,14 @@ const VALID_RULES = [
78
77
  'while_constant',
79
78
  'vl_registry_write',
80
79
  'vlax_without_loading',
80
+ 'unused_catch_result',
81
+ 'string_concat_loop',
82
+ 'if_without_else',
83
+ 'redundant_list',
84
+ 'entget_in_loop',
85
+ 'promise_handler',
86
+ 'module_cycle',
87
+ 'arg_count_project',
81
88
  ];
82
89
  const VALID_SEVERITIES = ['off', 'warn', 'error'];
83
90
  function validateConfig(config) {
@@ -0,0 +1,4 @@
1
+ import { Issue, LintConfig } from './types';
2
+ import { AstNode } from '@atlisp/parser';
3
+ export declare function runChecksWithVisitor(ast: AstNode, file: string, config: LintConfig, disableMap: Map<string, Set<number>>): Issue[];
4
+ //# sourceMappingURL=visitor-runner.d.ts.map