@atlisp/lint 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +182 -58
  3. package/atlisp-lint.default.json +31 -1
  4. package/dist/atlisp-lint.default.json +90 -0
  5. package/dist/cache.d.ts +2 -2
  6. package/dist/cache.js +6 -6
  7. package/dist/checks/append-single.d.ts +3 -0
  8. package/dist/checks/append-single.js +17 -0
  9. package/dist/checks/arg-count.d.ts +3 -0
  10. package/dist/checks/arg-count.js +123 -0
  11. package/dist/checks/assoc-without-cdr.d.ts +5 -0
  12. package/dist/checks/assoc-without-cdr.js +32 -0
  13. package/dist/checks/comment-style.d.ts +3 -0
  14. package/dist/checks/comment-style.js +24 -0
  15. package/dist/checks/cond-duplicate.d.ts +5 -0
  16. package/dist/checks/cond-duplicate.js +52 -0
  17. package/dist/checks/cond-simplify.d.ts +3 -0
  18. package/dist/checks/cond-simplify.js +45 -0
  19. package/dist/checks/constant-condition.js +4 -4
  20. package/dist/checks/dangerous-calls.js +2 -2
  21. package/dist/checks/dangling-defun.d.ts +3 -0
  22. package/dist/checks/dangling-defun.js +10 -28
  23. package/dist/checks/double-not.js +1 -1
  24. package/dist/checks/duplicate-defun.d.ts +6 -0
  25. package/dist/checks/duplicate-defun.js +50 -0
  26. package/dist/checks/dynamic-doc.d.ts +3 -0
  27. package/dist/checks/dynamic-doc.js +21 -0
  28. package/dist/checks/empty-branch.js +1 -1
  29. package/dist/checks/empty-catch.d.ts +3 -0
  30. package/dist/checks/empty-catch.js +34 -0
  31. package/dist/checks/eq-usage.d.ts +3 -0
  32. package/dist/checks/eq-usage.js +25 -0
  33. package/dist/checks/error-handling.d.ts +3 -0
  34. package/dist/checks/error-handling.js +56 -0
  35. package/dist/checks/extra-parens.d.ts +3 -0
  36. package/dist/checks/extra-parens.js +45 -0
  37. package/dist/checks/function-complexity.js +1 -1
  38. package/dist/checks/function-order.d.ts +3 -0
  39. package/dist/checks/function-order.js +33 -0
  40. package/dist/checks/global-naming.d.ts +3 -0
  41. package/dist/checks/global-naming.js +62 -0
  42. package/dist/checks/identical-branches.d.ts +5 -0
  43. package/dist/checks/identical-branches.js +54 -0
  44. package/dist/checks/index.d.ts +3 -0
  45. package/dist/checks/index.js +117 -0
  46. package/dist/checks/lambda-syntax.d.ts +3 -0
  47. package/dist/checks/lambda-syntax.js +25 -0
  48. package/dist/checks/long-function-call.d.ts +3 -0
  49. package/dist/checks/long-function-call.js +54 -0
  50. package/dist/checks/loop-optimization.d.ts +3 -0
  51. package/dist/checks/loop-optimization.js +17 -0
  52. package/dist/checks/magic-number.d.ts +3 -0
  53. package/dist/checks/magic-number.js +21 -0
  54. package/dist/checks/misplaced-else.js +1 -1
  55. package/dist/checks/missing-export.js +2 -2
  56. package/dist/checks/mixed-indent.d.ts +3 -0
  57. package/dist/checks/mixed-indent.js +19 -0
  58. package/dist/checks/module-reg.js +1 -1
  59. package/dist/checks/multiple-setq.js +1 -1
  60. package/dist/checks/no-return.d.ts +3 -0
  61. package/dist/checks/no-return.js +51 -0
  62. package/dist/checks/nth-usage.d.ts +3 -0
  63. package/dist/checks/nth-usage.js +17 -0
  64. package/dist/checks/quote-style.d.ts +3 -0
  65. package/dist/checks/quote-style.js +25 -0
  66. package/dist/checks/quote-vs-function.js +1 -1
  67. package/dist/checks/recursive-call.js +1 -1
  68. package/dist/checks/redundant-if.d.ts +3 -0
  69. package/dist/checks/redundant-if.js +17 -0
  70. package/dist/checks/redundant-let.js +1 -1
  71. package/dist/checks/redundant-nil-else.js +1 -1
  72. package/dist/checks/redundant-progn.js +1 -1
  73. package/dist/checks/redundant-quotes.js +1 -1
  74. package/dist/checks/redundant-setq.js +1 -1
  75. package/dist/checks/self-compare.js +1 -1
  76. package/dist/checks/setq-multiple.d.ts +3 -0
  77. package/dist/checks/setq-multiple.js +17 -0
  78. package/dist/checks/setq-single-arg.d.ts +5 -0
  79. package/dist/checks/setq-single-arg.js +30 -0
  80. package/dist/checks/shadow-builtin.d.ts +3 -0
  81. package/dist/checks/shadow-builtin.js +77 -0
  82. package/dist/checks/single-arg-and-or.js +1 -1
  83. package/dist/checks/strcat-usage.d.ts +3 -0
  84. package/dist/checks/strcat-usage.js +25 -0
  85. package/dist/checks/type-check.d.ts +3 -0
  86. package/dist/checks/type-check.js +26 -0
  87. package/dist/checks/unused-let.js +1 -1
  88. package/dist/checks/unused-package-dep.js +3 -3
  89. package/dist/checks/variable-shadow.js +1 -1
  90. package/dist/checks/while-constant.d.ts +5 -0
  91. package/dist/checks/while-constant.js +40 -0
  92. package/dist/config.d.ts +1 -0
  93. package/dist/config.js +70 -2
  94. package/dist/disable.js +1 -1
  95. package/dist/formatters.d.ts +1 -0
  96. package/dist/formatters.js +18 -2
  97. package/dist/index.js +53 -13
  98. package/dist/lib/lint-sbcl.lisp +161 -0
  99. package/dist/locale.js +24 -0
  100. package/dist/presets.d.ts +4 -0
  101. package/dist/presets.js +158 -0
  102. package/dist/project.js +37 -6
  103. package/dist/rules.d.ts +9 -0
  104. package/dist/rules.js +238 -0
  105. package/dist/runner.d.ts +2 -0
  106. package/dist/runner.js +198 -11
  107. package/dist/sbcl.js +1 -1
  108. package/dist/stub-packages.json +41 -0
  109. package/dist/types.d.ts +6 -0
  110. package/dist/validate.d.ts +8 -0
  111. package/dist/validate.js +125 -0
  112. package/dist/watch.d.ts +9 -0
  113. package/dist/watch.js +113 -0
  114. package/package.json +1 -1
@@ -11,7 +11,7 @@ function checkRedundantQuotes(content, file) {
11
11
  function checkRedundantQuotesAst(ast, file) {
12
12
  const issues = [];
13
13
  // ''x → (quote (quote x)) — nested quote
14
- const doubleQuotes = (0, parser_1.astFindAll)(ast, (n) => {
14
+ const doubleQuotes = (0, parser_1.astFindAll)(ast, n => {
15
15
  if (!(0, parser_1.astIsList)(n, 'quote'))
16
16
  return false;
17
17
  if (!n.children || n.children.length < 2)
@@ -10,7 +10,7 @@ function checkRedundantSetq(content, file) {
10
10
  }
11
11
  function checkRedundantSetqAst(ast, file) {
12
12
  const issues = [];
13
- const setqNodes = (0, parser_1.astFindAll)(ast, (n) => (0, parser_1.astIsList)(n, 'setq'));
13
+ const setqNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'setq'));
14
14
  for (const node of setqNodes) {
15
15
  if (!node.children)
16
16
  continue;
@@ -12,7 +12,7 @@ function checkSelfCompare(content, file) {
12
12
  function checkSelfCompareAst(ast, file) {
13
13
  const issues = [];
14
14
  for (const fn of COMPARE_FUNCTIONS) {
15
- const nodes = (0, parser_1.astFindAll)(ast, (n) => (0, parser_1.astIsList)(n, fn));
15
+ const nodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, fn));
16
16
  for (const node of nodes) {
17
17
  if (!node.children)
18
18
  continue;
@@ -0,0 +1,3 @@
1
+ import { Issue } from '../types';
2
+ export declare function checkSetqMultiple(content: string, file: string): Issue[];
3
+ //# sourceMappingURL=setq-multiple.d.ts.map
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkSetqMultiple = checkSetqMultiple;
4
+ const locale_1 = require("../locale");
5
+ const utils_1 = require("../utils");
6
+ function checkSetqMultiple(content, file) {
7
+ const issues = [];
8
+ const lines = content.split('\n');
9
+ for (let i = 0; i < lines.length; i++) {
10
+ const stripped = (0, utils_1.stripLine)(lines[i]);
11
+ if (/\(setq\s+\S+\s+[^\s)]+\)\s*\(setq\b/.test(stripped)) {
12
+ issues.push({ file, line: i + 1, severity: 'warn', rule: 'setq_multiple', message: (0, locale_1.t)('setq_multiple') });
13
+ }
14
+ }
15
+ return issues;
16
+ }
17
+ //# sourceMappingURL=setq-multiple.js.map
@@ -0,0 +1,5 @@
1
+ import { Issue } from '../types';
2
+ import { AstNode } from '@atlisp/parser';
3
+ export declare function checkSetqSingleArg(content: string, file: string): Issue[];
4
+ export declare function checkSetqSingleArgAst(ast: AstNode, file: string): Issue[];
5
+ //# sourceMappingURL=setq-single-arg.d.ts.map
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkSetqSingleArg = checkSetqSingleArg;
4
+ exports.checkSetqSingleArgAst = checkSetqSingleArgAst;
5
+ const locale_1 = require("../locale");
6
+ const parser_1 = require("@atlisp/parser");
7
+ function checkSetqSingleArg(content, file) {
8
+ const ast = (0, parser_1.parseAst)(content);
9
+ return checkSetqSingleArgAst(ast, file);
10
+ }
11
+ function checkSetqSingleArgAst(ast, file) {
12
+ const issues = [];
13
+ const nodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'setq'));
14
+ for (const node of nodes) {
15
+ if (!node.children)
16
+ continue;
17
+ // (setq x) — 2 children: 'setq', x — missing value
18
+ if (node.children.length === 2) {
19
+ issues.push({
20
+ file,
21
+ line: node.pos.line,
22
+ severity: 'warn',
23
+ rule: 'setq_single_arg',
24
+ message: (0, locale_1.t)('setq_single_arg', node.children[1].name || ''),
25
+ });
26
+ }
27
+ }
28
+ return issues;
29
+ }
30
+ //# sourceMappingURL=setq-single-arg.js.map
@@ -0,0 +1,3 @@
1
+ import { Issue } from '../types';
2
+ export declare function checkShadowBuiltin(content: string, file: string): Issue[];
3
+ //# sourceMappingURL=shadow-builtin.d.ts.map
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkShadowBuiltin = checkShadowBuiltin;
4
+ const locale_1 = require("../locale");
5
+ const utils_1 = require("../utils");
6
+ const BUILTINS = new Set([
7
+ 'car',
8
+ 'cdr',
9
+ 'cons',
10
+ 'list',
11
+ 'append',
12
+ 'length',
13
+ 'member',
14
+ 'assoc',
15
+ 'subst',
16
+ 'apply',
17
+ 'mapcar',
18
+ 'foreach',
19
+ 'while',
20
+ 'repeat',
21
+ 'if',
22
+ 'cond',
23
+ 'progn',
24
+ 'setq',
25
+ 'set',
26
+ 'quote',
27
+ 'function',
28
+ 'lambda',
29
+ 'defun',
30
+ 'princ',
31
+ 'print',
32
+ 'prompt',
33
+ 'getint',
34
+ 'getreal',
35
+ 'getstring',
36
+ 'getpoint',
37
+ 'getvar',
38
+ 'setvar',
39
+ 'command',
40
+ 'vlax-',
41
+ 'vla-',
42
+ 'vl-',
43
+ 'ssget',
44
+ 'ssname',
45
+ 'sslength',
46
+ 'entget',
47
+ 'entmod',
48
+ 'entmake',
49
+ 'entdel',
50
+ 'entlast',
51
+ 'entsel',
52
+ 'tblobjname',
53
+ 'tblsearch',
54
+ 'tblnext',
55
+ 'regapp',
56
+ 'startapp',
57
+ 'vl-registry-write',
58
+ 'vl-registry-read',
59
+ 'vl-registry-delete',
60
+ 'vl-catch-all-apply',
61
+ 'vl-catch-all-error-p',
62
+ 'vl-load-com',
63
+ 'vlax-get-or-create-object',
64
+ ]);
65
+ function checkShadowBuiltin(content, file) {
66
+ const issues = [];
67
+ const lines = content.split('\n');
68
+ for (let i = 0; i < lines.length; i++) {
69
+ const stripped = (0, utils_1.stripLine)(lines[i]);
70
+ const m = stripped.match(/\(defun\s+(\S+)\b/);
71
+ if (m && BUILTINS.has(m[1].toLowerCase())) {
72
+ issues.push({ file, line: i + 1, severity: 'warn', rule: 'shadow_builtin', message: (0, locale_1.t)('shadow_builtin', m[1]) });
73
+ }
74
+ }
75
+ return issues;
76
+ }
77
+ //# sourceMappingURL=shadow-builtin.js.map
@@ -11,7 +11,7 @@ function checkSingleArgAndOr(content, file) {
11
11
  function checkSingleArgAndOrAst(ast, file) {
12
12
  const issues = [];
13
13
  for (const form of ['and', 'or']) {
14
- const nodes = (0, parser_1.astFindAll)(ast, (n) => (0, parser_1.astIsList)(n, form));
14
+ const nodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, form));
15
15
  for (const node of nodes) {
16
16
  if (!node.children)
17
17
  continue;
@@ -0,0 +1,3 @@
1
+ import { Issue } from '../types';
2
+ export declare function checkStrcatUsage(content: string, file: string): Issue[];
3
+ //# sourceMappingURL=strcat-usage.d.ts.map
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkStrcatUsage = checkStrcatUsage;
4
+ const locale_1 = require("../locale");
5
+ const utils_1 = require("../utils");
6
+ function checkStrcatUsage(content, file) {
7
+ const issues = [];
8
+ const lines = content.split('\n');
9
+ for (let i = 0; i < lines.length; i++) {
10
+ const stripped = (0, utils_1.stripLineRaw)(lines[i]);
11
+ // Check for (+ "str" ...) pattern
12
+ const m = stripped.match(/\(\+\s+"[^"]*"/);
13
+ if (m) {
14
+ issues.push({
15
+ file,
16
+ line: i + 1,
17
+ severity: 'warn',
18
+ rule: 'strcat_usage',
19
+ message: (0, locale_1.t)('strcat_usage'),
20
+ });
21
+ }
22
+ }
23
+ return issues;
24
+ }
25
+ //# sourceMappingURL=strcat-usage.js.map
@@ -0,0 +1,3 @@
1
+ import { Issue } from '../types';
2
+ export declare function checkTypeCheck(content: string, file: string): Issue[];
3
+ //# sourceMappingURL=type-check.d.ts.map
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkTypeCheck = checkTypeCheck;
4
+ const locale_1 = require("../locale");
5
+ const utils_1 = require("../utils");
6
+ function checkTypeCheck(content, file) {
7
+ const issues = [];
8
+ const lines = content.split('\n');
9
+ for (let i = 0; i < lines.length; i++) {
10
+ const stripped = (0, utils_1.stripLine)(lines[i]);
11
+ const m = stripped.match(/\(setq\s+\S+\s+\(getvar\b/);
12
+ if (m) {
13
+ const varName = m[0].match(/\(setq\s+(\S+)/)?.[1] || '';
14
+ if (!varName)
15
+ continue;
16
+ const body = lines.slice(i, i + 5).join('\n');
17
+ const typeChk = '(type ' + varName;
18
+ const eqTypeChk = '(= (type ' + varName;
19
+ if (!body.includes(typeChk) && !body.includes(eqTypeChk)) {
20
+ issues.push({ file, line: i + 1, severity: 'warn', rule: 'type_check', message: (0, locale_1.t)('type_check', varName) });
21
+ }
22
+ }
23
+ }
24
+ return issues;
25
+ }
26
+ //# sourceMappingURL=type-check.js.map
@@ -10,7 +10,7 @@ function checkUnusedLetBinding(content, file) {
10
10
  }
11
11
  function checkUnusedLetBindingAst(ast, file) {
12
12
  const issues = [];
13
- const letNodes = (0, parser_1.astFindAll)(ast, (n) => (0, parser_1.astIsList)(n, 'let'));
13
+ const letNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'let'));
14
14
  for (const node of letNodes) {
15
15
  if (!node.children || node.children.length < 2)
16
16
  continue;
@@ -41,7 +41,7 @@ function checkUnusedPackageDep(file) {
41
41
  const issues = [];
42
42
  const content = fs.readFileSync(file, 'utf-8');
43
43
  const ast = (0, parser_1.parseAst)(content);
44
- const inPackageNodes = (0, parser_1.astFindAll)(ast, (n) => (0, parser_1.astIsList)(n, 'in-package'));
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;
47
47
  const allPackages = new Set();
@@ -51,7 +51,7 @@ function checkUnusedPackageDep(file) {
51
51
  }
52
52
  }
53
53
  const usedSymbols = new Set();
54
- const allLists = (0, parser_1.astFindAll)(ast, (n) => n.type === 'list');
54
+ const allLists = (0, parser_1.astFindAll)(ast, n => n.type === 'list');
55
55
  for (const node of allLists) {
56
56
  if (!node.children || node.children.length === 0)
57
57
  continue;
@@ -78,7 +78,7 @@ function checkUnusedPackageDep(file) {
78
78
  allPackages.delete(mainPackage);
79
79
  for (const pkg of allPackages) {
80
80
  if (!usedSymbols.has(pkg)) {
81
- const node = inPackageNodes.find((n) => n.children && n.children.length >= 2 && (0, parser_1.astIsSymbol)(n.children[1]) && n.children[1].name === pkg);
81
+ const node = inPackageNodes.find(n => n.children && n.children.length >= 2 && (0, parser_1.astIsSymbol)(n.children[1]) && n.children[1].name === pkg);
82
82
  issues.push({
83
83
  file,
84
84
  line: node ? node.pos.line : 1,
@@ -10,7 +10,7 @@ function checkVariableShadow(content, file) {
10
10
  }
11
11
  function checkVariableShadowAst(ast, file) {
12
12
  const issues = [];
13
- const letNodes = (0, parser_1.astFindAll)(ast, (n) => (0, parser_1.astIsList)(n, 'let'));
13
+ const letNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'let'));
14
14
  for (const node of letNodes) {
15
15
  if (!node.children || node.children.length < 2)
16
16
  continue;
@@ -0,0 +1,5 @@
1
+ import { Issue } from '../types';
2
+ import { AstNode } from '@atlisp/parser';
3
+ export declare function checkWhileConstant(content: string, file: string): Issue[];
4
+ export declare function checkWhileConstantAst(ast: AstNode, file: string): Issue[];
5
+ //# sourceMappingURL=while-constant.d.ts.map
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkWhileConstant = checkWhileConstant;
4
+ exports.checkWhileConstantAst = checkWhileConstantAst;
5
+ const locale_1 = require("../locale");
6
+ const parser_1 = require("@atlisp/parser");
7
+ function checkWhileConstant(content, file) {
8
+ const ast = (0, parser_1.parseAst)(content);
9
+ return checkWhileConstantAst(ast, file);
10
+ }
11
+ function checkWhileConstantAst(ast, file) {
12
+ const issues = [];
13
+ const whileNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'while'));
14
+ for (const node of whileNodes) {
15
+ if (!node.children || node.children.length < 2)
16
+ continue;
17
+ const cond = node.children[1];
18
+ // Check for constant conditions: nil, T, 1, 0, etc.
19
+ if (cond.type === 'symbol' && (cond.name === 'nil' || cond.name === 'T')) {
20
+ issues.push({
21
+ file,
22
+ line: node.pos.line,
23
+ severity: 'warn',
24
+ rule: 'while_constant',
25
+ message: (0, locale_1.t)('while_constant', cond.name),
26
+ });
27
+ }
28
+ else if (cond.type === 'number') {
29
+ issues.push({
30
+ file,
31
+ line: node.pos.line,
32
+ severity: 'warn',
33
+ rule: 'while_constant',
34
+ message: (0, locale_1.t)('while_constant', String(cond.value)),
35
+ });
36
+ }
37
+ }
38
+ return issues;
39
+ }
40
+ //# sourceMappingURL=while-constant.js.map
package/dist/config.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { LintConfig } from './types';
2
2
  export declare function loadConfig(configPath?: string): LintConfig;
3
+ export declare function getCheckSeverity(config: LintConfig, rule: string): string;
3
4
  export declare function findOverrides(filepath: string): Partial<LintConfig> | null;
4
5
  export declare function mergeOverrides(base: LintConfig, overrides: Partial<LintConfig>): LintConfig;
5
6
  //# sourceMappingURL=config.d.ts.map
package/dist/config.js CHANGED
@@ -34,10 +34,12 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.loadConfig = loadConfig;
37
+ exports.getCheckSeverity = getCheckSeverity;
37
38
  exports.findOverrides = findOverrides;
38
39
  exports.mergeOverrides = mergeOverrides;
39
40
  const fs = __importStar(require("fs"));
40
41
  const path = __importStar(require("path"));
42
+ const presets_1 = require("./presets");
41
43
  const DEFAULT_CONFIG = {
42
44
  locale: 'zh',
43
45
  source: {
@@ -56,14 +58,69 @@ const DEFAULT_CONFIG = {
56
58
  token_in_url: 'warn',
57
59
  open_without_close: 'warn',
58
60
  bare_function_names: 'warn',
59
- trailing_whitespace: 'warn',
60
61
  line_length: 'warn',
61
62
  function_complexity: 'warn',
62
63
  parameter_naming: 'warn',
63
64
  unused_variable: 'warn',
64
65
  missing_doc: 'warn',
66
+ trailing_whitespace: 'warn',
65
67
  module_registration: 'off',
66
68
  namespace_header: 'off',
69
+ unused_parameter: 'warn',
70
+ constant_condition: 'warn',
71
+ redundant_progn: 'warn',
72
+ empty_branch: 'warn',
73
+ unused_let_binding: 'warn',
74
+ recursive_call: 'warn',
75
+ variable_shadow: 'warn',
76
+ redundant_cond: 'warn',
77
+ unused_local_fun: 'warn',
78
+ multiple_setq: 'warn',
79
+ redundant_quotes: 'warn',
80
+ trailing_paren: 'warn',
81
+ empty_comment: 'warn',
82
+ redundant_setq: 'warn',
83
+ redundant_nil_else: 'warn',
84
+ single_arg_and_or: 'warn',
85
+ redundant_let: 'warn',
86
+ self_compare: 'warn',
87
+ misplaced_else: 'warn',
88
+ quote_vs_function: 'warn',
89
+ commented_code: 'warn',
90
+ double_not: 'warn',
91
+ setq_single_arg: 'warn',
92
+ assoc_without_cdr: 'warn',
93
+ identical_branches: 'warn',
94
+ while_constant: 'warn',
95
+ cond_duplicate: 'warn',
96
+ duplicate_defun: 'warn',
97
+ dangling_defun: 'warn',
98
+ missing_export: 'warn',
99
+ unused_package_dep: 'warn',
100
+ error_handling: 'warn',
101
+ global_naming: 'warn',
102
+ extra_parens: 'warn',
103
+ arg_count: 'warn',
104
+ strcat_usage: 'warn',
105
+ cond_simplify: 'warn',
106
+ quote_style: 'warn',
107
+ eq_usage: 'warn',
108
+ lambda_syntax: 'warn',
109
+ comment_style: 'warn',
110
+ empty_catch: 'warn',
111
+ nth_usage: 'warn',
112
+ append_single: 'warn',
113
+ setq_multiple: 'warn',
114
+ function_order: 'off',
115
+ magic_number: 'off',
116
+ mixed_indent: 'warn',
117
+ long_function_call: 'warn',
118
+ no_return: 'warn',
119
+ shadow_builtin: 'warn',
120
+ dynamic_doc: 'warn',
121
+ loop_optimization: 'off',
122
+ type_check: 'off',
123
+ redundant_if: 'warn',
67
124
  },
68
125
  line_length: {
69
126
  max: 100,
@@ -111,9 +168,17 @@ function loadConfig(configPath) {
111
168
  }
112
169
  const raw = fs.readFileSync(absPath, 'utf-8');
113
170
  const user = JSON.parse(raw);
114
- // Deep merge: start from default, overlay user values
171
+ // Start from default config
115
172
  const merged = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
173
+ // Apply preset if specified (preset applies before user overrides)
174
+ const preset = user.preset;
175
+ if (preset && presets_1.PRESETS[preset]) {
176
+ deepMerge(merged, presets_1.PRESETS[preset]);
177
+ }
178
+ // Apply user overrides (user settings take precedence)
116
179
  deepMerge(merged, user);
180
+ // Remove non-config keys
181
+ delete merged.preset;
117
182
  return merged;
118
183
  }
119
184
  return JSON.parse(JSON.stringify(DEFAULT_CONFIG));
@@ -133,6 +198,9 @@ function deepMerge(target, source) {
133
198
  }
134
199
  }
135
200
  }
201
+ function getCheckSeverity(config, rule) {
202
+ return config.checks[rule] || 'off';
203
+ }
136
204
  function findOverrides(filepath) {
137
205
  const dir = path.dirname(filepath);
138
206
  const configPath = path.join(dir, '.atlisp-lint.json');
package/dist/disable.js CHANGED
@@ -12,7 +12,7 @@ function parseDisableComments(content, fileLine) {
12
12
  continue;
13
13
  const rules = m[1]
14
14
  .split(',')
15
- .map((r) => r.trim())
15
+ .map(r => r.trim())
16
16
  .filter(Boolean);
17
17
  let targetLine = i + 1;
18
18
  if (fileLine !== undefined)
@@ -1,4 +1,5 @@
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;
3
4
  export declare function formatJson(issues: Issue[], errorCount?: number, warningCount?: number): string;
4
5
  //# sourceMappingURL=formatters.d.ts.map
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.formatDefault = formatDefault;
4
+ exports.formatHtml = formatHtml;
4
5
  exports.formatJson = formatJson;
5
6
  const locale_1 = require("./locale");
6
7
  const colors = {
@@ -68,11 +69,26 @@ function formatDefault(issues, fileContents) {
68
69
  }
69
70
  return lines.join('\n');
70
71
  }
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
+ }
71
87
  function formatJson(issues, errorCount, warningCount) {
72
88
  return JSON.stringify({
73
89
  issues,
74
- error_count: errorCount ?? issues.filter((i) => i.severity === 'error').length,
75
- warning_count: warningCount ?? issues.filter((i) => i.severity === 'warn').length,
90
+ error_count: errorCount ?? issues.filter(i => i.severity === 'error').length,
91
+ warning_count: warningCount ?? issues.filter(i => i.severity === 'warn').length,
76
92
  }, null, 2);
77
93
  }
78
94
  //# sourceMappingURL=formatters.js.map
package/dist/index.js CHANGED
@@ -44,6 +44,7 @@ const sbcl_1 = require("./sbcl");
44
44
  const formatters_1 = require("./formatters");
45
45
  const locale_1 = require("./locale");
46
46
  const cache_1 = require("./cache");
47
+ const watch_1 = require("./watch");
47
48
  function parseArgs() {
48
49
  const argv = process.argv.slice(2);
49
50
  const opts = {
@@ -59,6 +60,7 @@ function parseArgs() {
59
60
  clearCache: false,
60
61
  parallel: false,
61
62
  project: false,
63
+ watch: false,
62
64
  };
63
65
  for (let i = 0; i < argv.length; i++) {
64
66
  switch (argv[i]) {
@@ -101,6 +103,9 @@ function parseArgs() {
101
103
  case '--project':
102
104
  opts.project = true;
103
105
  break;
106
+ case '--watch':
107
+ opts.watch = true;
108
+ break;
104
109
  default:
105
110
  break;
106
111
  }
@@ -168,12 +173,12 @@ function collectFiles(rootDir, opts, config) {
168
173
  }
169
174
  }
170
175
  }
171
- const excludePatterns = config.source.exclude.map((e) => {
176
+ const excludePatterns = config.source.exclude.map(e => {
172
177
  const str = e.replace(/\./g, '\\.').replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*');
173
178
  return new RegExp(`^${str}$`);
174
179
  });
175
180
  return Array.from(files)
176
- .filter((f) => {
181
+ .filter(f => {
177
182
  const rel = path.relative(rootDir, f).replace(/\\/g, '/');
178
183
  for (const re of excludePatterns) {
179
184
  if (re.test(rel))
@@ -190,10 +195,10 @@ function collectStagedFiles(rootDir) {
190
195
  });
191
196
  return output
192
197
  .split('\n')
193
- .map((l) => l.trim())
194
- .filter((l) => l.endsWith('.lsp'))
195
- .map((l) => path.resolve(rootDir, l))
196
- .filter((f) => fs.existsSync(f));
198
+ .map(l => l.trim())
199
+ .filter(l => l.endsWith('.lsp'))
200
+ .map(l => path.resolve(rootDir, l))
201
+ .filter(f => fs.existsSync(f));
197
202
  }
198
203
  function initConfig(rootDir) {
199
204
  const defaultPath = path.join(__dirname, '..', 'atlisp-lint.default.json');
@@ -249,6 +254,11 @@ async function main() {
249
254
  console.log((0, locale_1.t)('index.no_files'));
250
255
  return;
251
256
  }
257
+ // --watch mode
258
+ if (opts.watch) {
259
+ (0, watch_1.watchFiles)(files, { rootDir, format: opts.format, config });
260
+ return;
261
+ }
252
262
  // --fix mode: auto-fix before linting
253
263
  if (opts.fix) {
254
264
  for (const f of files) {
@@ -260,10 +270,20 @@ async function main() {
260
270
  if (!opts.staged)
261
271
  return;
262
272
  }
263
- // Filter cached files
273
+ // Filter cached files (pre-read content to avoid double I/O)
274
+ const contentCache = new Map();
264
275
  const filesToLint = opts.cache
265
- ? files.filter((f) => {
266
- const cached = (0, cache_1.isCached)(f, rootDir);
276
+ ? files.filter(f => {
277
+ let content;
278
+ try {
279
+ content = fs.readFileSync(f, 'utf-8');
280
+ }
281
+ catch {
282
+ /* ignore */
283
+ }
284
+ if (content !== undefined)
285
+ contentCache.set(f, content);
286
+ const cached = (0, cache_1.isCached)(f, rootDir, content);
267
287
  if (cached) {
268
288
  console.log(`${(0, locale_1.t)('summary.tag_warn')}: ${path.relative(rootDir, f)} — ${(0, locale_1.t)('index.cached')}`);
269
289
  }
@@ -283,10 +303,30 @@ async function main() {
283
303
  const projectIssues = (0, project_1.lintProject)(filesToLint, config, rootDir);
284
304
  issues.push(...projectIssues);
285
305
  }
286
- // Mark as cached
306
+ // Apply rule-based fixes (after lint, when --fix is set)
307
+ if (opts.fix) {
308
+ for (const f of filesToLint) {
309
+ const relPath = path.relative(rootDir, f);
310
+ const fileIssues = issues.filter(i => i.file === relPath);
311
+ if (fileIssues.length > 0) {
312
+ try {
313
+ const content = fs.readFileSync(f, 'utf-8');
314
+ const fixed = (0, runner_1.applyFixes)(fileIssues, content, relPath);
315
+ if (fixed !== content) {
316
+ fs.writeFileSync(f, fixed, 'utf-8');
317
+ console.log(`${(0, locale_1.t)('summary.tag_fail')}: ${relPath} — ${(0, locale_1.t)('index.fixed')}`);
318
+ }
319
+ }
320
+ catch {
321
+ /* ignore */
322
+ }
323
+ }
324
+ }
325
+ }
326
+ // Mark as cached (reuse pre-read content if available)
287
327
  if (opts.cache) {
288
328
  for (const f of filesToLint) {
289
- (0, cache_1.markCached)(f, rootDir);
329
+ (0, cache_1.markCached)(f, rootDir, contentCache.get(f));
290
330
  }
291
331
  }
292
332
  // Phase 2: SBCL syntax validation
@@ -319,8 +359,8 @@ async function main() {
319
359
  }
320
360
  }
321
361
  // Format & output
322
- const errorCount = issues.filter((i) => i.severity === 'error').length;
323
- const warningCount = issues.filter((i) => i.severity === 'warn').length;
362
+ const errorCount = issues.filter(i => i.severity === 'error').length;
363
+ const warningCount = issues.filter(i => i.severity === 'warn').length;
324
364
  if (opts.format === 'json') {
325
365
  console.log((0, formatters_1.formatJson)(issues, errorCount, warningCount));
326
366
  }