@atlisp/lint 0.1.23 → 0.1.24

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 (45) hide show
  1. package/dist/atlisp-lint.default.json +90 -0
  2. package/dist/checks/arg-count.js +1 -1
  3. package/dist/checks/assoc-without-cdr.js +1 -1
  4. package/dist/checks/cond-duplicate.js +1 -1
  5. package/dist/checks/constant-condition.js +3 -3
  6. package/dist/checks/dangerous-calls.js +1 -1
  7. package/dist/checks/dangling-defun.js +1 -1
  8. package/dist/checks/double-not.js +1 -1
  9. package/dist/checks/empty-branch.js +1 -1
  10. package/dist/checks/empty-catch.js +1 -1
  11. package/dist/checks/function-complexity.js +1 -1
  12. package/dist/checks/global-naming.js +1 -1
  13. package/dist/checks/identical-branches.js +1 -1
  14. package/dist/checks/misplaced-else.js +1 -1
  15. package/dist/checks/multiple-setq.js +1 -1
  16. package/dist/checks/no-return.js +1 -1
  17. package/dist/checks/open-close.js +1 -1
  18. package/dist/checks/parameter-naming.js +1 -1
  19. package/dist/checks/quote-vs-function.js +1 -1
  20. package/dist/checks/recursive-call.js +1 -1
  21. package/dist/checks/redundant-let.js +1 -1
  22. package/dist/checks/redundant-nil-else.js +1 -1
  23. package/dist/checks/redundant-progn.js +1 -1
  24. package/dist/checks/redundant-quotes.js +1 -1
  25. package/dist/checks/redundant-setq.js +1 -1
  26. package/dist/checks/self-compare.js +1 -1
  27. package/dist/checks/setq-single-arg.js +1 -1
  28. package/dist/checks/single-arg-and-or.js +1 -1
  29. package/dist/checks/undeclared-setq.js +1 -1
  30. package/dist/checks/unused-catch-result.js +1 -1
  31. package/dist/checks/unused-let.js +1 -1
  32. package/dist/checks/unused-local-fun.js +1 -1
  33. package/dist/checks/unused-package-dep.js +1 -1
  34. package/dist/checks/unused-param.js +1 -1
  35. package/dist/checks/unused-variable.js +1 -1
  36. package/dist/checks/variable-shadow.js +1 -1
  37. package/dist/checks/while-constant.js +1 -1
  38. package/dist/formatters.d.ts +0 -1
  39. package/dist/formatters.js +0 -16
  40. package/dist/lib/lint-sbcl.lisp +161 -0
  41. package/dist/project.js +5 -5
  42. package/dist/runner.d.ts +0 -1
  43. package/dist/runner.js +148 -100
  44. package/dist/stub-packages.json +41 -0
  45. package/package.json +2 -2
@@ -0,0 +1,90 @@
1
+ {
2
+ "locale": "zh",
3
+ "source": {
4
+ "globs": ["**/*.lsp"],
5
+ "exclude": ["**/node_modules/**", "**/vendor/**", "**/.git/**"]
6
+ },
7
+ "checks": {
8
+ "parens": "error",
9
+ "encoding": "warn",
10
+ "cl_syntax": "warn",
11
+ "quit_exit": "error",
12
+ "command_shell": "error",
13
+ "startapp": "warn",
14
+ "vl_registry_write": "warn",
15
+ "vlax_without_loading": "off",
16
+ "token_in_url": "warn",
17
+ "open_without_close": "warn",
18
+ "bare_function_names": "off",
19
+ "line_length": "warn",
20
+ "function_complexity": "warn",
21
+ "parameter_naming": "warn",
22
+ "unused_variable": "warn",
23
+ "missing_doc": "warn",
24
+ "error_handling": "warn",
25
+ "global_naming": "warn",
26
+ "extra_parens": "warn",
27
+ "arg_count": "warn",
28
+ "strcat_usage": "warn",
29
+ "cond_simplify": "warn",
30
+ "redundant_progn": "warn",
31
+ "quote_style": "warn",
32
+ "eq_usage": "warn",
33
+ "lambda_syntax": "warn",
34
+ "comment_style": "warn",
35
+ "empty_catch": "warn",
36
+ "nth_usage": "warn",
37
+ "append_single": "warn",
38
+ "setq_multiple": "warn",
39
+ "function_order": "off",
40
+ "magic_number": "off",
41
+ "mixed_indent": "warn",
42
+ "long_function_call": "warn",
43
+ "no_return": "warn",
44
+ "shadow_builtin": "warn",
45
+ "dynamic_doc": "warn",
46
+ "loop_optimization": "off",
47
+ "type_check": "off",
48
+ "redundant_if": "warn",
49
+ "trailing_whitespace": "warn",
50
+ "module_registration": "off",
51
+ "namespace_header": "off"
52
+ },
53
+ "line_length": {
54
+ "max": 100,
55
+ "tab_width": 2
56
+ },
57
+ "function_complexity": {
58
+ "max_lines": 60,
59
+ "max_nesting": 6
60
+ },
61
+ "cl_syntax": {
62
+ "keywords": ["&key"]
63
+ },
64
+ "dangerous_calls": {
65
+ "quit": "error",
66
+ "exit": "error",
67
+ "command_shell": "error",
68
+ "startapp": "warn",
69
+ "vl_registry_write": "warn"
70
+ },
71
+ "module_registration": {
72
+ "severity": "off",
73
+ "patterns": [],
74
+ "dirs": ["modules"]
75
+ },
76
+ "namespace_header": {
77
+ "severity": "off",
78
+ "package": "",
79
+ "deps": [],
80
+ "search_lines": 15
81
+ },
82
+ "bare_function_names": {
83
+ "allowlist": ["T", "nil"],
84
+ "namespace_pattern": "^[a-z]+:"
85
+ },
86
+ "sbcl": {
87
+ "walk_exclude": [".vscode", "vendor", ".git"],
88
+ "defmacro_allow_files": ["compat-cl"]
89
+ }
90
+ }
@@ -5,7 +5,7 @@ const locale_1 = require("../locale");
5
5
  const parser_1 = require("@atlisp/parser");
6
6
  function checkArgCount(content, file) {
7
7
  const issues = [];
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  const defuns = (0, parser_1.astFindListWithHead)(ast, 'defun');
10
10
  for (const defun of defuns) {
11
11
  if (!defun.children || defun.children.length < 3)
@@ -5,7 +5,7 @@ exports.checkAssocWithoutCdrAst = checkAssocWithoutCdrAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkAssocWithoutCdr(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkAssocWithoutCdrAst(ast, file);
10
10
  }
11
11
  function checkAssocWithoutCdrAst(ast, file) {
@@ -18,7 +18,7 @@ function condKey(node) {
18
18
  return '';
19
19
  }
20
20
  function checkCondDuplicate(content, file) {
21
- const ast = (0, parser_1.parseAst)(content);
21
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
22
22
  return checkCondDuplicateAst(ast, file);
23
23
  }
24
24
  function checkCondDuplicateAst(ast, file) {
@@ -7,7 +7,7 @@ const parser_1 = require("@atlisp/parser");
7
7
  const CONDITION_FORMS = new Set(['if', 'while']);
8
8
  const CONSTANTS = new Set(['T', 'nil']);
9
9
  function checkConstantCondition(content, file) {
10
- const ast = (0, parser_1.parseAst)(content);
10
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
11
11
  return checkConstantConditionAst(ast, file);
12
12
  }
13
13
  function checkConstantConditionAst(ast, file) {
@@ -41,8 +41,8 @@ function checkConstantConditionAst(ast, file) {
41
41
  continue;
42
42
  const condTest = clause.children[0];
43
43
  if (condTest.type === 'symbol' && condTest.name && CONSTANTS.has(condTest.name)) {
44
- // Skip last clause with T that's the standard else/default pattern
45
- if (condTest.name === 'T' && i === condNode.children.length - 1)
44
+ // Skip last clause with T (only when there are multiple clauses — else/default pattern)
45
+ if (condTest.name === 'T' && i === condNode.children.length - 1 && i > 1)
46
46
  continue;
47
47
  issues.push({
48
48
  file,
@@ -36,7 +36,7 @@ function isFunctionCall(list, name) {
36
36
  return list.children[0].type === 'symbol' && list.children[0].name === name;
37
37
  }
38
38
  function checkDangerousCalls(content, file, config) {
39
- const ast = (0, parser_1.parseAst)(content);
39
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
40
40
  return checkDangerousCallsAst(ast, file, config);
41
41
  }
42
42
  function checkDangerousCallsAst(ast, file, config) {
@@ -40,7 +40,7 @@ const fs = __importStar(require("fs"));
40
40
  function checkDanglingDefun(file, allDefuns, allReferences) {
41
41
  const issues = [];
42
42
  const content = fs.readFileSync(file, 'utf-8');
43
- const ast = (0, parser_1.parseAst)(content);
43
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
44
44
  const localDefuns = [];
45
45
  const defunNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'defun') || (0, parser_1.astIsList)(n, 'defun-q'));
46
46
  for (const node of defunNodes) {
@@ -5,7 +5,7 @@ exports.checkDoubleNotAst = checkDoubleNotAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkDoubleNot(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkDoubleNotAst(ast, file);
10
10
  }
11
11
  function checkDoubleNotAst(ast, file) {
@@ -7,7 +7,7 @@ const parser_1 = require("@atlisp/parser");
7
7
  // AutoLISP only has `if` for conditional branching; `while` with no body is valid (no-op loop)
8
8
  const BRANCH_FORMS = new Set(['if']);
9
9
  function checkEmptyBranch(content, file) {
10
- const ast = (0, parser_1.parseAst)(content);
10
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
11
11
  return checkEmptyBranchAst(ast, file);
12
12
  }
13
13
  function checkEmptyBranchAst(ast, file) {
@@ -5,7 +5,7 @@ const locale_1 = require("../locale");
5
5
  const parser_1 = require("@atlisp/parser");
6
6
  function checkEmptyCatch(content, file) {
7
7
  const issues = [];
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  const catchNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'vl-catch-all-apply'));
10
10
  for (const node of catchNodes) {
11
11
  if (isInQuote(node))
@@ -5,7 +5,7 @@ exports.checkFunctionComplexityAst = checkFunctionComplexityAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkFunctionComplexity(content, file, maxLines, maxNesting) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkFunctionComplexityAst(ast, file, maxLines, maxNesting);
10
10
  }
11
11
  function checkFunctionComplexityAst(ast, file, maxLines, maxNesting) {
@@ -25,7 +25,7 @@ function isInsideLocalScope(node) {
25
25
  }
26
26
  function checkGlobalNaming(content, file) {
27
27
  const issues = [];
28
- const ast = (0, parser_1.parseAst)(content);
28
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
29
29
  const setqForms = (0, parser_1.astFindListWithHead)(ast, 'setq');
30
30
  for (const form of setqForms) {
31
31
  if (!form.children || form.children.length < 2)
@@ -27,7 +27,7 @@ function astEqual(a, b) {
27
27
  return false;
28
28
  }
29
29
  function checkIdenticalBranches(content, file) {
30
- const ast = (0, parser_1.parseAst)(content);
30
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
31
31
  return checkIdenticalBranchesAst(ast, file);
32
32
  }
33
33
  function checkIdenticalBranchesAst(ast, file) {
@@ -5,7 +5,7 @@ exports.checkMisplacedElseAst = checkMisplacedElseAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkMisplacedElse(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkMisplacedElseAst(ast, file);
10
10
  }
11
11
  function checkMisplacedElseAst(ast, file) {
@@ -5,7 +5,7 @@ exports.checkMultipleSetqAst = checkMultipleSetqAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkMultipleSetq(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkMultipleSetqAst(ast, file);
10
10
  }
11
11
  function checkMultipleSetqAst(ast, file) {
@@ -5,7 +5,7 @@ const locale_1 = require("../locale");
5
5
  const parser_1 = require("@atlisp/parser");
6
6
  function checkNoReturn(content, file) {
7
7
  const issues = [];
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  const defuns = (0, parser_1.astFindListWithHead)(ast, 'defun');
10
10
  for (const defun of defuns) {
11
11
  if (!defun.children || defun.children.length < 3)
@@ -5,7 +5,7 @@ exports.checkOpenWithoutCloseAst = checkOpenWithoutCloseAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkOpenWithoutClose(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkOpenWithoutCloseAst(ast, file);
10
10
  }
11
11
  function checkOpenWithoutCloseAst(ast, file) {
@@ -5,7 +5,7 @@ exports.checkParameterNamingAst = checkParameterNamingAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkParameterNaming(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkParameterNamingAst(ast, file);
10
10
  }
11
11
  function checkParameterNamingAst(ast, file) {
@@ -17,7 +17,7 @@ const HIGHER_ORDER_FUNCS = new Set([
17
17
  'vl-every',
18
18
  ]);
19
19
  function checkQuoteVsFunction(content, file) {
20
- const ast = (0, parser_1.parseAst)(content);
20
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
21
21
  return checkQuoteVsFunctionAst(ast, file);
22
22
  }
23
23
  function checkQuoteVsFunctionAst(ast, file) {
@@ -5,7 +5,7 @@ exports.checkRecursiveCallAst = checkRecursiveCallAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkRecursiveCall(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkRecursiveCallAst(ast, file);
10
10
  }
11
11
  /** Forms that provide conditional branching (exit condition for recursion) */
@@ -5,7 +5,7 @@ exports.checkRedundantLetAst = checkRedundantLetAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkRedundantLet(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkRedundantLetAst(ast, file);
10
10
  }
11
11
  function checkRedundantLetAst(ast, file) {
@@ -5,7 +5,7 @@ exports.checkRedundantNilElseAst = checkRedundantNilElseAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkRedundantNilElse(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkRedundantNilElseAst(ast, file);
10
10
  }
11
11
  function checkRedundantNilElseAst(ast, file) {
@@ -5,7 +5,7 @@ exports.checkRedundantPrognAst = checkRedundantPrognAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkRedundantProgn(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkRedundantPrognAst(ast, file);
10
10
  }
11
11
  function checkRedundantPrognAst(ast, file) {
@@ -5,7 +5,7 @@ exports.checkRedundantQuotesAst = checkRedundantQuotesAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkRedundantQuotes(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkRedundantQuotesAst(ast, file);
10
10
  }
11
11
  function checkRedundantQuotesAst(ast, file) {
@@ -5,7 +5,7 @@ exports.checkRedundantSetqAst = checkRedundantSetqAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkRedundantSetq(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkRedundantSetqAst(ast, file);
10
10
  }
11
11
  function checkRedundantSetqAst(ast, file) {
@@ -6,7 +6,7 @@ const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  const COMPARE_FUNCTIONS = new Set(['=', '/=', '<', '>', '<=', '>=', 'eq', 'equal']);
8
8
  function checkSelfCompare(content, file) {
9
- const ast = (0, parser_1.parseAst)(content);
9
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
10
10
  return checkSelfCompareAst(ast, file);
11
11
  }
12
12
  function checkSelfCompareAst(ast, file) {
@@ -5,7 +5,7 @@ exports.checkSetqSingleArgAst = checkSetqSingleArgAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkSetqSingleArg(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkSetqSingleArgAst(ast, file);
10
10
  }
11
11
  function checkSetqSingleArgAst(ast, file) {
@@ -5,7 +5,7 @@ exports.checkSingleArgAndOrAst = checkSingleArgAndOrAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkSingleArgAndOr(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkSingleArgAndOrAst(ast, file);
10
10
  }
11
11
  function checkSingleArgAndOrAst(ast, file) {
@@ -42,7 +42,7 @@ function findEnclosingDefun(node) {
42
42
  }
43
43
  function checkUndeclaredSetq(content, file) {
44
44
  const issues = [];
45
- const ast = (0, parser_1.parseAst)(content);
45
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
46
46
  const setqForms = (0, parser_1.astFindListWithHead)(ast, 'setq');
47
47
  for (const setq of setqForms) {
48
48
  if (!setq.children || setq.children.length < 2)
@@ -5,7 +5,7 @@ const locale_1 = require("../locale");
5
5
  const parser_1 = require("@atlisp/parser");
6
6
  function checkUnusedCatchResult(content, file) {
7
7
  const issues = [];
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  const catchNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'vl-catch-all-apply'));
10
10
  for (const node of catchNodes) {
11
11
  if (isInQuote(node))
@@ -5,7 +5,7 @@ exports.checkUnusedLetBindingAst = checkUnusedLetBindingAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkUnusedLetBinding(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkUnusedLetBindingAst(ast, file);
10
10
  }
11
11
  function checkUnusedLetBindingAst(ast, file) {
@@ -5,7 +5,7 @@ exports.checkUnusedLocalFunAst = checkUnusedLocalFunAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkUnusedLocalFun(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkUnusedLocalFunAst(ast, file);
10
10
  }
11
11
  function checkUnusedLocalFunAst(ast, file) {
@@ -40,7 +40,7 @@ const fs = __importStar(require("fs"));
40
40
  function checkUnusedPackageDep(file) {
41
41
  const issues = [];
42
42
  const content = fs.readFileSync(file, 'utf-8');
43
- const ast = (0, parser_1.parseAst)(content);
43
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
44
44
  const inPackageNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'in-package'));
45
45
  if (inPackageNodes.length === 0)
46
46
  return issues;
@@ -5,7 +5,7 @@ exports.checkUnusedParameterAst = checkUnusedParameterAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkUnusedParameter(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkUnusedParameterAst(ast, file);
10
10
  }
11
11
  function checkUnusedParameterAst(ast, file) {
@@ -5,7 +5,7 @@ exports.checkUnusedVariableAst = checkUnusedVariableAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkUnusedVariable(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkUnusedVariableAst(ast, file);
10
10
  }
11
11
  function checkUnusedVariableAst(ast, file) {
@@ -5,7 +5,7 @@ exports.checkVariableShadowAst = checkVariableShadowAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkVariableShadow(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkVariableShadowAst(ast, file);
10
10
  }
11
11
  function checkVariableShadowAst(ast, file) {
@@ -5,7 +5,7 @@ exports.checkWhileConstantAst = checkWhileConstantAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  function checkWhileConstant(content, file) {
8
- const ast = (0, parser_1.parseAst)(content);
8
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
9
  return checkWhileConstantAst(ast, file);
10
10
  }
11
11
  function checkWhileConstantAst(ast, file) {
@@ -1,5 +1,4 @@
1
1
  import { Issue } from './types';
2
2
  export declare function formatDefault(issues: Issue[], fileContents?: Map<string, string>): string;
3
- export declare function formatHtml(issues: Issue[], errorCount: number, warningCount: number): string;
4
3
  export declare function formatJson(issues: Issue[], errorCount?: number, warningCount?: number): string;
5
4
  //# sourceMappingURL=formatters.d.ts.map
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.formatDefault = formatDefault;
4
- exports.formatHtml = formatHtml;
5
4
  exports.formatJson = formatJson;
6
5
  const locale_1 = require("./locale");
7
6
  const colors = {
@@ -69,21 +68,6 @@ function formatDefault(issues, fileContents) {
69
68
  }
70
69
  return lines.join('\n');
71
70
  }
72
- function formatHtml(issues, errorCount, warningCount) {
73
- let html = `<!DOCTYPE html>
74
- <html lang="en">
75
- <head><meta charset="UTF-8"><title>@atlisp/lint Report</title></head>
76
- <body>
77
- <h1>@atlisp/lint Report</h1>
78
- <p>${errorCount} error(s), ${warningCount} warning(s)</p>
79
- <ul>
80
- `;
81
- for (const iss of issues) {
82
- html += ` <li>${iss.severity}: ${iss.file}:${iss.line} — ${iss.message}</li>\n`;
83
- }
84
- html += '</ul>\n</body>\n</html>';
85
- return html;
86
- }
87
71
  function formatJson(issues, errorCount, warningCount) {
88
72
  return JSON.stringify({
89
73
  issues,
@@ -0,0 +1,161 @@
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>
3
+
4
+ (require :uiop)
5
+
6
+ (defparameter *errors* 0)
7
+ (defparameter *warnings* 0)
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
+
46
+ ;; ── Stub packages from external file ──────────────────────────────────
47
+ (defun load-stub-packages (file-path)
48
+ (flet ((ensure-pkg (name &optional use-list)
49
+ (unless (find-package name)
50
+ (make-package name :use (or use-list '())))))
51
+ (with-open-file (s file-path :direction :input)
52
+ (let ((packages (read s)))
53
+ (dolist (pkg packages)
54
+ (ensure-pkg (first pkg) (second pkg)))))))
55
+
56
+ ;; ── File walking ──────────────────────────────────────────────────────
57
+ (defun source-files (dir)
58
+ (sort
59
+ (remove-if-not
60
+ (lambda (p)
61
+ (let ((n (pathname-type p)))
62
+ (and n (string-equal n "lsp"))))
63
+ (uiop:directory-files dir))
64
+ 'string< :key #'namestring))
65
+
66
+ (defun walk-tree (dir exclude-dirs)
67
+ (let ((result (source-files dir)))
68
+ (dolist (sub (uiop:subdirectories dir))
69
+ (let ((name (car (last (pathname-directory sub)))))
70
+ (unless (find name exclude-dirs :test 'string=)
71
+ (setf result (append result (walk-tree sub exclude-dirs))))))
72
+ result))
73
+
74
+ ;; ── Per-file check ────────────────────────────────────────────────────
75
+ (defun check-file (path src-dir defmacro-allow-files)
76
+ (incf *checked*)
77
+ (let* ((rel (enough-namestring path src-dir))
78
+ (fullname (format nil "~a.~a" (pathname-name path) (pathname-type path))))
79
+ (format t " ~a ... " rel)
80
+ (force-output)
81
+ ;; Syntax check via READ
82
+ (handler-case
83
+ (with-open-file (s path :direction :input)
84
+ (loop for form = (read s nil :eof)
85
+ until (eq form :eof)))
86
+ (error (e)
87
+ (let ((msg (princ-to-string e)))
88
+ (cond
89
+ ((search "not found" msg)
90
+ (format t "~% [NOTE] ~a: ~a~%" rel (i18n :not-found))
91
+ (incf *warnings*))
92
+ (t
93
+ (format t "~a~% [ERROR] ~a: ~a~%" (i18n :fail) rel (i18n :syntax-error))
94
+ (incf *errors*)
95
+ (return-from check-file))))))
96
+ ;; Trailing whitespace
97
+ (with-open-file (s path :direction :input)
98
+ (loop for line = (read-line s nil nil)
99
+ for lineno from 1
100
+ while line
101
+ when (and (> (length line) 0)
102
+ (find (char line (1- (length line))) '(#\Space #\Tab)))
103
+ do (progn
104
+ (format t "~% [WARN] ~a line ~d: ~a~%" rel lineno (i18n :trailing-ws))
105
+ (incf *warnings*))))
106
+ ;; CL-ism checks: raw content scan
107
+ (with-open-file (s path :direction :input)
108
+ (let ((content (make-string (file-length s))))
109
+ (file-position s 0)
110
+ (read-sequence content s)
111
+ (when (search "#(" content)
112
+ (format t "~% [WARN] ~a: ~a~%" rel (i18n :vec-syntax))
113
+ (incf *warnings*))
114
+ (when (search "#\\" content)
115
+ (format t "~% [WARN] ~a: ~a~%" rel (i18n :char-syntax))
116
+ (incf *warnings*))
117
+ (when (search "#." content)
118
+ (format t "~% [WARN] ~a: ~a~%" rel (i18n :eval-syntax))
119
+ (incf *warnings*))
120
+ (when (and (search "defmacro" content)
121
+ (not (some (lambda (s) (search s fullname)) defmacro-allow-files)))
122
+ (format t "~% [WARN] ~a: ~a~%" rel (i18n :defmacro))
123
+ (incf *warnings*))))
124
+ (format t "~a~%" (i18n :ok))))
125
+
126
+ ;; ── Main ──────────────────────────────────────────────────────────────
127
+ (defun main ()
128
+ (let* ((args (uiop:command-line-arguments))
129
+ (src-dir (first args))
130
+ (stub-file (second args))
131
+ (walk-exclude (if (third args)
132
+ (read-from-string (third args))
133
+ '(".vscode" "test" "experiment" "tools" ".git")))
134
+ (defmacro-allow (if (fourth args)
135
+ (read-from-string (fourth args))
136
+ '("compat-cl")))
137
+ (locale (fifth args)))
138
+ (when locale (setf *locale* locale))
139
+ (load-stub-packages stub-file)
140
+
141
+ (format t "~%==================================================~%")
142
+ (format t "~a~%" (i18n :header))
143
+ (format t "~a: ~a~%" (i18n :source) (namestring (pathname src-dir)))
144
+ (format t "==================================================~%~%")
145
+ (setf *errors* 0 *warnings* 0 *checked* 0)
146
+
147
+ (let ((files (walk-tree src-dir walk-exclude)))
148
+ (format t "~a~%~%" (format nil (i18n :found) (length files)))
149
+ (dolist (f files)
150
+ (check-file f src-dir defmacro-allow)))
151
+
152
+ (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*)
156
+ (format t "==================================================~%~%")
157
+ (if (> *errors* 0)
158
+ (uiop:quit 1)
159
+ (uiop:quit 0))))
160
+
161
+ (main)
package/dist/project.js CHANGED
@@ -44,7 +44,7 @@ const duplicate_defun_1 = require("./checks/duplicate-defun");
44
44
  const locale_1 = require("./locale");
45
45
  function collectDefuns(file) {
46
46
  const content = fs.readFileSync(file, 'utf-8');
47
- const ast = (0, parser_1.parseAst)(content);
47
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
48
48
  const results = [];
49
49
  const defunNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'defun') || (0, parser_1.astIsList)(n, 'defun-q'));
50
50
  for (const node of defunNodes) {
@@ -56,7 +56,7 @@ function collectDefuns(file) {
56
56
  }
57
57
  function collectReferences(file) {
58
58
  const content = fs.readFileSync(file, 'utf-8');
59
- const ast = (0, parser_1.parseAst)(content);
59
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
60
60
  const results = [];
61
61
  const allCalls = (0, parser_1.astFindAll)(ast, n => {
62
62
  if (n.type !== 'list' || !n.children || n.children.length === 0)
@@ -73,7 +73,7 @@ function collectReferences(file) {
73
73
  }
74
74
  function countFunctionArgs(file) {
75
75
  const content = fs.readFileSync(file, 'utf-8');
76
- const ast = (0, parser_1.parseAst)(content);
76
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
77
77
  const result = new Map();
78
78
  const defunNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'defun') || (0, parser_1.astIsList)(n, 'defun-q'));
79
79
  for (const node of defunNodes) {
@@ -102,7 +102,7 @@ function countFunctionArgs(file) {
102
102
  }
103
103
  function countCallArgs(file) {
104
104
  const content = fs.readFileSync(file, 'utf-8');
105
- const ast = (0, parser_1.parseAst)(content);
105
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
106
106
  const result = new Map();
107
107
  const allCalls = (0, parser_1.astFindAll)(ast, n => {
108
108
  if (n.type !== 'list' || !n.children || n.children.length === 0)
@@ -122,7 +122,7 @@ function countCallArgs(file) {
122
122
  }
123
123
  function findModuleDeps(filepath) {
124
124
  const content = fs.readFileSync(filepath, 'utf-8');
125
- const ast = (0, parser_1.parseAst)(content);
125
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
126
126
  const imports = [];
127
127
  const exports = [];
128
128
  const inPackageNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'in-package'));
package/dist/runner.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Issue, LintConfig } from './types';
2
2
  export declare function runChecks(content: string, file: string, config: LintConfig): Issue[];
3
- export declare function runChecksWithVisitorWrapper(content: string, file: string, config: LintConfig): Issue[];
4
3
  export declare function lintFiles(files: string[], config: LintConfig, rootDir: string): Issue[];
5
4
  export declare function lintFilesParallel(files: string[], config: LintConfig, rootDir: string): Promise<Issue[]>;
6
5
  export declare const FIXABLE_RULES: Set<string>;
package/dist/runner.js CHANGED
@@ -35,7 +35,6 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.FIXABLE_RULES = void 0;
37
37
  exports.runChecks = runChecks;
38
- exports.runChecksWithVisitorWrapper = runChecksWithVisitorWrapper;
39
38
  exports.lintFiles = lintFiles;
40
39
  exports.lintFilesParallel = lintFilesParallel;
41
40
  exports.applyFixes = applyFixes;
@@ -100,12 +99,17 @@ function runChecks(content, file, config) {
100
99
  const severity = checks[rule];
101
100
  if (!severity || severity === 'off')
102
101
  return;
103
- const results = fn();
104
- for (const r of results) {
105
- if (!(0, disable_1.isDisabled)(r.rule, r.line, disableMap)) {
106
- issues.push(r);
102
+ try {
103
+ const results = fn();
104
+ for (const r of results) {
105
+ if (!(0, disable_1.isDisabled)(r.rule, r.line, disableMap)) {
106
+ issues.push(r);
107
+ }
107
108
  }
108
109
  }
110
+ catch {
111
+ // Check failed, continue with remaining checks
112
+ }
109
113
  }
110
114
  function addIssues(ruleIssues) {
111
115
  for (const r of ruleIssues) {
@@ -173,17 +177,17 @@ function runChecks(content, file, config) {
173
177
  ]);
174
178
  const needsAst = Array.from(astRules).some(r => checks[r] !== 'off');
175
179
  if (needsAst) {
176
- const ast = (0, parser_1.parseAst)(content);
177
- const visitorIssues = (0, visitor_runner_1.runChecksWithVisitor)(ast, file, config, disableMap);
178
- addIssues(visitorIssues);
180
+ try {
181
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
182
+ const visitorIssues = (0, visitor_runner_1.runChecksWithVisitor)(ast, file, config, disableMap);
183
+ addIssues(visitorIssues);
184
+ }
185
+ catch {
186
+ // AST-based checks are skipped, text-based checks above still report issues
187
+ }
179
188
  }
180
189
  return issues;
181
190
  }
182
- function runChecksWithVisitorWrapper(content, file, config) {
183
- const disableMap = (0, disable_1.parseDisableComments)(content);
184
- const ast = (0, parser_1.parseAst)(content);
185
- return (0, visitor_runner_1.runChecksWithVisitor)(ast, file, config, disableMap);
186
- }
187
191
  function lintFiles(files, config, rootDir) {
188
192
  (0, locale_1.setLocale)(config.locale || 'zh');
189
193
  const allIssues = [];
@@ -321,6 +325,7 @@ exports.FIXABLE_RULES = new Set([
321
325
  ]);
322
326
  function applyFixes(issues, content, filepath) {
323
327
  const lines = content.split('\n');
328
+ const fixedLines = new Set();
324
329
  const fixesByRule = new Map();
325
330
  for (const iss of issues) {
326
331
  if (iss.file !== filepath)
@@ -330,7 +335,6 @@ function applyFixes(issues, content, filepath) {
330
335
  fixesByRule.set(iss.rule, set);
331
336
  }
332
337
  const fixableRules = exports.FIXABLE_RULES;
333
- let result = content;
334
338
  for (const [rule, lineSet] of fixesByRule) {
335
339
  if (!fixableRules.has(rule))
336
340
  continue;
@@ -339,30 +343,38 @@ function applyFixes(issues, content, filepath) {
339
343
  const idx = line - 1;
340
344
  if (idx < 0 || idx >= lines.length)
341
345
  continue;
342
- const lineContent = result.split('\n')[idx];
346
+ const lineContent = lines[idx];
343
347
  switch (rule) {
344
348
  case 'redundant_quotes': {
345
349
  const fixed = lineContent.replace(/''(\S)/g, "'$1");
346
- if (fixed !== lineContent)
347
- result = replaceLine(result, idx, fixed);
350
+ if (fixed !== lineContent) {
351
+ lines[idx] = fixed;
352
+ fixedLines.add(idx);
353
+ }
348
354
  break;
349
355
  }
350
356
  case 'double_not': {
351
357
  const fixed = lineContent.replace(/\(not\s+\(not\s+/g, '(not ');
352
- if (fixed !== lineContent)
353
- result = replaceLine(result, idx, fixed);
358
+ if (fixed !== lineContent) {
359
+ lines[idx] = fixed;
360
+ fixedLines.add(idx);
361
+ }
354
362
  break;
355
363
  }
356
364
  case 'redundant_nil_else': {
357
365
  const fixed = lineContent.replace(/\s+nil\s*\)$/, ')');
358
- if (fixed !== lineContent)
359
- result = replaceLine(result, idx, fixed);
366
+ if (fixed !== lineContent) {
367
+ lines[idx] = fixed;
368
+ fixedLines.add(idx);
369
+ }
360
370
  break;
361
371
  }
362
372
  case 'single_arg_and_or': {
363
373
  const m = lineContent.match(/\((and|or)\s+([^)\s]+)\s*\)/);
364
- if (m)
365
- result = replaceLine(result, idx, m[2]);
374
+ if (m) {
375
+ lines[idx] = m[2];
376
+ fixedLines.add(idx);
377
+ }
366
378
  break;
367
379
  }
368
380
  case 'redundant_progn': {
@@ -374,16 +386,20 @@ function applyFixes(issues, content, filepath) {
374
386
  const inner = lineContent.slice(proIdx + 7, close);
375
387
  const rest = lineContent.slice(close + 1);
376
388
  const fixed = beforePro + inner + rest;
377
- if (fixed !== lineContent)
378
- result = replaceLine(result, idx, fixed);
389
+ if (fixed !== lineContent) {
390
+ lines[idx] = fixed;
391
+ fixedLines.add(idx);
392
+ }
379
393
  }
380
394
  }
381
395
  break;
382
396
  }
383
397
  case 'redundant_let': {
384
398
  const fixed = lineContent.replace(/\(let\s+\(\)\s*/, '(progn ');
385
- if (fixed !== lineContent)
386
- result = replaceLine(result, idx, fixed);
399
+ if (fixed !== lineContent) {
400
+ lines[idx] = fixed;
401
+ fixedLines.add(idx);
402
+ }
387
403
  break;
388
404
  }
389
405
  case 'quote_style': {
@@ -395,8 +411,10 @@ function applyFixes(issues, content, filepath) {
395
411
  const quoted = lineContent.slice(qIdx + 7, close);
396
412
  const rest = lineContent.slice(close + 1);
397
413
  const fixed = beforeQ + "'" + quoted + rest;
398
- if (fixed !== lineContent)
399
- result = replaceLine(result, idx, fixed);
414
+ if (fixed !== lineContent) {
415
+ lines[idx] = fixed;
416
+ fixedLines.add(idx);
417
+ }
400
418
  }
401
419
  }
402
420
  break;
@@ -410,8 +428,10 @@ function applyFixes(issues, content, filepath) {
410
428
  const inner = lineContent.slice(pIdx + 7, close);
411
429
  const rest = lineContent.slice(close + 1);
412
430
  const fixed = beforeP + inner + rest;
413
- if (fixed !== lineContent)
414
- result = replaceLine(result, idx, fixed);
431
+ if (fixed !== lineContent) {
432
+ lines[idx] = fixed;
433
+ fixedLines.add(idx);
434
+ }
415
435
  }
416
436
  }
417
437
  break;
@@ -436,18 +456,18 @@ function applyFixes(issues, content, filepath) {
436
456
  if (thenForm && elseForm) {
437
457
  const rest = lineContent.slice(ifClose + 1);
438
458
  const fixed = indent + '(if ' + condVar + ' ' + elseForm + ' ' + thenForm + ')' + rest;
439
- if (fixed !== lineContent)
440
- result = replaceLine(result, idx, fixed);
459
+ if (fixed !== lineContent) {
460
+ lines[idx] = fixed;
461
+ fixedLines.add(idx);
462
+ }
441
463
  }
442
464
  }
443
465
  break;
444
466
  }
445
467
  case 'redundant_setq': {
446
- const allLines = result.split('\n');
447
- const trimmed = allLines[idx].trim();
468
+ const trimmed = lines[idx].trim();
448
469
  if (/^\(setq\s+(\S+)\s+\1\s*\)$/.test(trimmed)) {
449
- allLines.splice(idx, 1);
450
- result = allLines.join('\n');
470
+ lines.splice(idx, 1);
451
471
  }
452
472
  break;
453
473
  }
@@ -456,12 +476,15 @@ function applyFixes(issues, content, filepath) {
456
476
  if (fixed !== lineContent) {
457
477
  const merged = lineContent.replace(/^\(\s*setq\s+/, '(setq ');
458
478
  if (merged.includes('setq') && !merged.match(/\)\(/)) {
459
- result = replaceLine(result, idx, merged);
479
+ lines[idx] = merged;
480
+ fixedLines.add(idx);
460
481
  }
461
482
  else {
462
483
  const simplified = lineContent.replace(/\)\(setq\s+/g, '\n');
463
- if (simplified !== lineContent)
464
- result = replaceLine(result, idx, simplified);
484
+ if (simplified !== lineContent) {
485
+ lines[idx] = simplified;
486
+ fixedLines.add(idx);
487
+ }
465
488
  }
466
489
  }
467
490
  break;
@@ -475,8 +498,10 @@ function applyFixes(issues, content, filepath) {
475
498
  const listMatch = inner.match(/^\(list\s+(.*)\)\s*$/);
476
499
  if (listMatch) {
477
500
  const fixed = lineContent.slice(0, aIdx) + '(cons ' + listMatch[1] + ')' + lineContent.slice(close + 1);
478
- if (fixed !== lineContent)
479
- result = replaceLine(result, idx, fixed);
501
+ if (fixed !== lineContent) {
502
+ lines[idx] = fixed;
503
+ fixedLines.add(idx);
504
+ }
480
505
  }
481
506
  }
482
507
  }
@@ -484,40 +509,48 @@ function applyFixes(issues, content, filepath) {
484
509
  }
485
510
  case 'nth_usage': {
486
511
  const fixed = lineContent.replace(/\(nth\s+0\s+/g, '(car ');
487
- if (fixed !== lineContent)
488
- result = replaceLine(result, idx, fixed);
512
+ if (fixed !== lineContent) {
513
+ lines[idx] = fixed;
514
+ fixedLines.add(idx);
515
+ }
489
516
  break;
490
517
  }
491
518
  case 'setq_single_arg': {
492
- const allLines = result.split('\n');
493
- if (/^\s*\(setq\s+\S+\s*\)\s*$/.test(allLines[idx])) {
494
- allLines.splice(idx, 1);
495
- result = allLines.join('\n');
519
+ if (/^\s*\(setq\s+\S+\s*\)\s*$/.test(lines[idx])) {
520
+ lines.splice(idx, 1);
496
521
  }
497
522
  break;
498
523
  }
499
524
  case 'extra_parens': {
500
525
  const fixed = lineContent.replace(/\){2,}/g, ')');
501
- if (fixed !== lineContent)
502
- result = replaceLine(result, idx, fixed);
526
+ if (fixed !== lineContent) {
527
+ lines[idx] = fixed;
528
+ fixedLines.add(idx);
529
+ }
503
530
  break;
504
531
  }
505
532
  case 'empty_branch': {
506
533
  const fixed = lineContent.replace(/\(if\s+\S+\s*\)/g, m => m.slice(0, -1) + ' nil)');
507
- if (fixed !== lineContent)
508
- result = replaceLine(result, idx, fixed);
534
+ if (fixed !== lineContent) {
535
+ lines[idx] = fixed;
536
+ fixedLines.add(idx);
537
+ }
509
538
  break;
510
539
  }
511
540
  case 'comment_style': {
512
541
  const fixed = lineContent.replace(/^(\s*;)([^ ;|].*)$/gm, '$1 $2');
513
- if (fixed !== lineContent)
514
- result = replaceLine(result, idx, fixed);
542
+ if (fixed !== lineContent) {
543
+ lines[idx] = fixed;
544
+ fixedLines.add(idx);
545
+ }
515
546
  break;
516
547
  }
517
548
  case 'eq_usage': {
518
549
  const fixed = lineContent.replace(/\(eq\s+(\S+)\s+(\d+)\s*\)/g, '(= $1 $2)');
519
- if (fixed !== lineContent)
520
- result = replaceLine(result, idx, fixed);
550
+ if (fixed !== lineContent) {
551
+ lines[idx] = fixed;
552
+ fixedLines.add(idx);
553
+ }
521
554
  break;
522
555
  }
523
556
  case 'cond_simplify': {
@@ -538,8 +571,10 @@ function applyFixes(issues, content, filepath) {
538
571
  const bodyExpr = branchContent.slice(space).trim();
539
572
  const rest = lineContent.slice(condEnd + 1);
540
573
  const fixed = indent + '(if ' + testExpr + ' ' + bodyExpr + ')' + rest;
541
- if (fixed !== lineContent)
542
- result = replaceLine(result, idx, fixed);
574
+ if (fixed !== lineContent) {
575
+ lines[idx] = fixed;
576
+ fixedLines.add(idx);
577
+ }
543
578
  }
544
579
  }
545
580
  }
@@ -549,34 +584,31 @@ function applyFixes(issues, content, filepath) {
549
584
  }
550
585
  case 'self_compare': {
551
586
  const fixed = lineContent.replace(/\((?:[=/<>]+|eq|equal)\s+(\S+)\s+\1\s*\)/g, 'T');
552
- if (fixed !== lineContent)
553
- result = replaceLine(result, idx, fixed);
587
+ if (fixed !== lineContent) {
588
+ lines[idx] = fixed;
589
+ fixedLines.add(idx);
590
+ }
554
591
  break;
555
592
  }
556
593
  case 'while_constant': {
557
- const allLines = result.split('\n');
558
- const trimmed = allLines[idx].trim();
594
+ const trimmed = lines[idx].trim();
559
595
  const nilMatch = trimmed.match(/^\(\s*while\s+nil\s+(.*)\)\s*$/);
560
596
  if (nilMatch) {
561
- allLines[idx] = allLines[idx].replace(trimmed, 'nil');
562
- result = allLines.join('\n');
597
+ lines[idx] = lines[idx].replace(trimmed, 'nil');
563
598
  }
564
599
  break;
565
600
  }
566
601
  case 'redundant_list': {
567
602
  const fixed = lineContent.replace(/\(\s*list\s*\)/g, 'nil');
568
- if (fixed !== lineContent)
569
- result = replaceLine(result, idx, fixed);
603
+ if (fixed !== lineContent) {
604
+ lines[idx] = fixed;
605
+ fixedLines.add(idx);
606
+ }
570
607
  break;
571
608
  }
572
609
  }
573
610
  }
574
611
  }
575
- return result;
576
- }
577
- function replaceLine(content, lineIdx, newLine) {
578
- const lines = content.split('\n');
579
- lines[lineIdx] = newLine;
580
612
  return lines.join('\n');
581
613
  }
582
614
  function parseIgnoreFile(rootDir) {
@@ -593,23 +625,51 @@ const UTF8_BOM = 0xfeff;
593
625
  function fixFile(filepath) {
594
626
  const fixes = [];
595
627
  let content = fs.readFileSync(filepath, 'utf-8');
596
- const wsFixed = content
597
- .split('\n')
598
- .map(l => l.replace(/[ \t]+$/, ''))
599
- .join('\n');
600
- if (wsFixed !== content) {
601
- content = wsFixed;
602
- fixes.push('trailing_whitespace');
603
- }
628
+ // 1. Remove BOM
604
629
  if (content.charCodeAt(0) === UTF8_BOM) {
605
630
  content = content.slice(1);
606
631
  fixes.push('encoding (BOM)');
607
632
  }
633
+ // 2. Single line pass: trailing whitespace + empty comment lines
634
+ const origLines = content.split('\n');
635
+ let wsChanged = false;
636
+ let ecChanged = false;
637
+ const processed = origLines.map(l => {
638
+ const stripped = l.replace(/[ \t]+$/, '');
639
+ if (stripped !== l)
640
+ wsChanged = true;
641
+ if (/^\s*;\s*$/.test(stripped)) {
642
+ ecChanged = true;
643
+ return '';
644
+ }
645
+ return stripped;
646
+ });
647
+ if (wsChanged)
648
+ fixes.push('trailing_whitespace');
649
+ if (ecChanged)
650
+ fixes.push('empty_comment');
651
+ if (wsChanged || ecChanged)
652
+ content = processed.join('\n');
653
+ // 3. Count parens (string/comment aware), fix trailing excess
608
654
  let openCount = 0;
609
655
  let closeCount = 0;
610
- let fixContent = content;
611
- for (let i = 0; i < fixContent.length; i++) {
612
- const ch = fixContent[i];
656
+ let inStr = false;
657
+ for (let i = 0; i < content.length; i++) {
658
+ const ch = content[i];
659
+ if (ch === '"') {
660
+ inStr = !inStr;
661
+ continue;
662
+ }
663
+ if (inStr) {
664
+ if (ch === '\\')
665
+ i++;
666
+ continue;
667
+ }
668
+ if (ch === ';') {
669
+ const nl = content.indexOf('\n', i);
670
+ i = nl !== -1 ? nl : content.length;
671
+ continue;
672
+ }
613
673
  if (ch === '(')
614
674
  openCount++;
615
675
  else if (ch === ')')
@@ -617,29 +677,17 @@ function fixFile(filepath) {
617
677
  }
618
678
  if (closeCount > openCount) {
619
679
  let excess = closeCount - openCount;
620
- while (excess > 0 && fixContent.endsWith(')')) {
621
- fixContent = fixContent.slice(0, -1);
680
+ let fixed = content;
681
+ while (excess > 0 && fixed.endsWith(')')) {
682
+ fixed = fixed.slice(0, -1);
622
683
  excess--;
623
684
  }
624
- fixContent = fixContent.replace(/\n[ \t]*$/, '');
625
- if (fixContent !== content) {
626
- content = fixContent;
685
+ fixed = fixed.replace(/\n[ \t]*$/, '');
686
+ if (fixed !== content) {
687
+ content = fixed;
627
688
  fixes.push('trailing_paren');
628
689
  }
629
690
  }
630
- const lines = content.split('\n');
631
- let changed = false;
632
- const fixedLines = lines.map(l => {
633
- if (/^\s*;\s*$/.test(l)) {
634
- changed = true;
635
- return '';
636
- }
637
- return l;
638
- });
639
- if (changed) {
640
- content = fixedLines.join('\n');
641
- fixes.push('empty_comment');
642
- }
643
691
  if (fixes.length > 0) {
644
692
  fs.writeFileSync(filepath, content, 'utf-8');
645
693
  }
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlisp/lint",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "AutoLISP static analysis tool — parens, security, conventions, SBCL syntax validation",
5
5
  "keywords": [
6
6
  "CAD",
@@ -37,7 +37,7 @@
37
37
  "prepack": "npm run build"
38
38
  },
39
39
  "dependencies": {
40
- "@atlisp/parser": "^0.1.2"
40
+ "@atlisp/parser": "^0.1.3"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/jest": "^29",