@atlisp/lint 0.2.12 → 0.2.14

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/atlisp-lint.schema.json +27 -4
  2. package/dist/cache.js +19 -9
  3. package/dist/checks/builtin-arg-count.d.ts +1 -0
  4. package/dist/checks/builtin-arg-count.js +5 -0
  5. package/dist/checks/dangerous-calls.js +1 -0
  6. package/dist/checks/error-handling.js +5 -6
  7. package/dist/checks/global-function-naming.js +159 -35
  8. package/dist/checks/missing-princ.js +2 -2
  9. package/dist/checks/nth-usage.d.ts +1 -0
  10. package/dist/checks/nth-usage.js +16 -1
  11. package/dist/cli/collector.d.ts +7 -0
  12. package/dist/cli/collector.js +154 -0
  13. package/dist/cli/parser.d.ts +30 -0
  14. package/dist/cli/parser.js +119 -0
  15. package/dist/config.js +28 -6
  16. package/dist/disable.js +12 -0
  17. package/dist/fixes/index.d.ts +8 -0
  18. package/dist/fixes/index.js +200 -0
  19. package/dist/index.js +56 -236
  20. package/dist/locale.js +30 -2
  21. package/dist/presets.js +24 -0
  22. package/dist/project.js +64 -34
  23. package/dist/rules.d.ts +1 -0
  24. package/dist/rules.js +85 -0
  25. package/dist/runner.d.ts +2 -3
  26. package/dist/runner.js +46 -371
  27. package/dist/types.d.ts +1 -0
  28. package/dist/utils.d.ts +4 -0
  29. package/dist/utils.js +43 -0
  30. package/dist/validate.js +12 -0
  31. package/dist/visitor-runner.d.ts +28 -0
  32. package/dist/visitor-runner.js +115 -1
  33. package/dist/visitors/equal-nil.d.ts +8 -0
  34. package/dist/visitors/equal-nil.js +26 -0
  35. package/dist/visitors/index.d.ts +5 -0
  36. package/dist/visitors/index.js +14 -0
  37. package/dist/visitors/progn-in-cond.d.ts +8 -0
  38. package/dist/visitors/progn-in-cond.js +33 -0
  39. package/dist/visitors/redundant-and-or.d.ts +8 -0
  40. package/dist/visitors/redundant-and-or.js +27 -0
  41. package/dist/visitors/redundant-car-cdr.d.ts +8 -0
  42. package/dist/visitors/redundant-car-cdr.js +35 -0
  43. package/dist/visitors/types.d.ts +13 -0
  44. package/dist/visitors/types.js +72 -0
  45. package/package.json +1 -1
@@ -0,0 +1,30 @@
1
+ export interface CliOptions {
2
+ src: string[];
3
+ test: string[];
4
+ config?: string;
5
+ staged: boolean;
6
+ diff: boolean;
7
+ changed: string;
8
+ format: 'default' | 'json' | 'sarif' | 'html';
9
+ init: boolean;
10
+ installHook: boolean;
11
+ fix: boolean;
12
+ hookArgs: string;
13
+ cache: boolean;
14
+ clearCache: boolean;
15
+ parallel: boolean;
16
+ project: boolean;
17
+ watch: boolean;
18
+ help: boolean;
19
+ version: boolean;
20
+ formatCode: boolean;
21
+ formatCheck: boolean;
22
+ sbcl: boolean;
23
+ docs: boolean;
24
+ listRules: boolean;
25
+ explain: string;
26
+ dryRun: boolean;
27
+ maxWarnings: number;
28
+ }
29
+ export declare function parseArgs(): CliOptions;
30
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseArgs = parseArgs;
4
+ function parseArgs() {
5
+ const argv = process.argv.slice(2);
6
+ const opts = {
7
+ src: [],
8
+ test: [],
9
+ staged: false,
10
+ diff: false,
11
+ changed: '',
12
+ format: 'default',
13
+ init: false,
14
+ installHook: false,
15
+ fix: false,
16
+ hookArgs: '',
17
+ cache: false,
18
+ clearCache: false,
19
+ parallel: false,
20
+ project: false,
21
+ watch: false,
22
+ help: false,
23
+ version: false,
24
+ formatCode: false,
25
+ formatCheck: false,
26
+ sbcl: false,
27
+ docs: false,
28
+ listRules: false,
29
+ explain: '',
30
+ dryRun: false,
31
+ maxWarnings: -1,
32
+ };
33
+ for (let i = 0; i < argv.length; i++) {
34
+ switch (argv[i]) {
35
+ case '--src':
36
+ opts.src.push(argv[++i]);
37
+ break;
38
+ case '--test':
39
+ opts.test.push(argv[++i]);
40
+ break;
41
+ case '--config':
42
+ opts.config = argv[++i];
43
+ break;
44
+ case '--staged':
45
+ opts.staged = true;
46
+ break;
47
+ case '--diff':
48
+ opts.diff = true;
49
+ break;
50
+ case '--changed':
51
+ opts.changed = argv[++i] || 'main';
52
+ break;
53
+ case '--format':
54
+ opts.format = argv[++i];
55
+ break;
56
+ case '--init':
57
+ opts.init = true;
58
+ break;
59
+ case '--install-hook':
60
+ opts.installHook = true;
61
+ break;
62
+ case '--fix':
63
+ opts.fix = true;
64
+ break;
65
+ case '--hook-args':
66
+ opts.hookArgs = argv[++i];
67
+ break;
68
+ case '--cache':
69
+ opts.cache = true;
70
+ break;
71
+ case '--clear-cache':
72
+ opts.clearCache = true;
73
+ break;
74
+ case '--parallel':
75
+ opts.parallel = true;
76
+ break;
77
+ case '--project':
78
+ opts.project = true;
79
+ break;
80
+ case '--watch':
81
+ opts.watch = true;
82
+ break;
83
+ case '--help':
84
+ opts.help = true;
85
+ break;
86
+ case '--version':
87
+ opts.version = true;
88
+ break;
89
+ case '--format-code':
90
+ opts.formatCode = true;
91
+ break;
92
+ case '--format-check':
93
+ opts.formatCheck = true;
94
+ break;
95
+ case '--sbcl':
96
+ opts.sbcl = true;
97
+ break;
98
+ case '--docs':
99
+ opts.docs = true;
100
+ break;
101
+ case '--list-rules':
102
+ opts.listRules = true;
103
+ break;
104
+ case '--explain':
105
+ opts.explain = argv[++i];
106
+ break;
107
+ case '--dry-run':
108
+ opts.dryRun = true;
109
+ break;
110
+ case '--max-warnings':
111
+ opts.maxWarnings = parseInt(argv[++i], 10);
112
+ break;
113
+ default:
114
+ break;
115
+ }
116
+ }
117
+ return opts;
118
+ }
119
+ //# sourceMappingURL=parser.js.map
package/dist/config.js CHANGED
@@ -65,6 +65,18 @@ const DEFAULT_CONFIG = {
65
65
  missing_doc: 'info',
66
66
  trailing_whitespace: 'info',
67
67
  undeclared_setq: 'warn',
68
+ command_s: 'warn',
69
+ zerop_usage: 'info',
70
+ progn_in_body: 'info',
71
+ if_progn_both: 'info',
72
+ redundant_cond: 'info',
73
+ selection_set_leak: 'warn',
74
+ empty_branch_cond: 'warn',
75
+ function_reference: 'warn',
76
+ progn_in_cond: 'info',
77
+ redundant_car_cdr: 'info',
78
+ equal_nil: 'info',
79
+ redundant_and_or: 'info',
68
80
  module_registration: 'off',
69
81
  namespace_header: 'off',
70
82
  unused_parameter: 'warn',
@@ -152,6 +164,7 @@ const DEFAULT_CONFIG = {
152
164
  command_shell: 'warn',
153
165
  startapp: 'warn',
154
166
  vl_registry_write: 'warn',
167
+ eval: 'warn',
155
168
  },
156
169
  module_registration: {
157
170
  severity: 'off',
@@ -219,13 +232,22 @@ function getCheckSeverity(config, rule) {
219
232
  return config.checks[rule] || 'off';
220
233
  }
221
234
  function findOverrides(filepath) {
222
- const dir = path.dirname(filepath);
223
- const configPath = path.join(dir, '.atlisp-lint.json');
224
- if (fs.existsSync(configPath)) {
225
- const raw = fs.readFileSync(configPath, 'utf-8');
226
- return JSON.parse(raw);
235
+ let dir = path.dirname(filepath);
236
+ for (;;) {
237
+ const configPath = path.join(dir, '.atlisp-lint.json');
238
+ if (fs.existsSync(configPath)) {
239
+ try {
240
+ return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
241
+ }
242
+ catch {
243
+ return null;
244
+ }
245
+ }
246
+ const parent = path.dirname(dir);
247
+ if (parent === dir)
248
+ return null;
249
+ dir = parent;
227
250
  }
228
- return null;
229
251
  }
230
252
  function mergeOverrides(base, overrides) {
231
253
  const merged = JSON.parse(JSON.stringify(base));
package/dist/disable.js CHANGED
@@ -7,6 +7,18 @@ function parseDisableComments(content, fileLine) {
7
7
  const lines = content.split('\n');
8
8
  for (let i = 0; i < lines.length; i++) {
9
9
  const line = lines[i];
10
+ const disableFileM = line.match(/;\s*atlisp-lint:\s*disable-file=(.+)/i);
11
+ if (disableFileM) {
12
+ const rules = disableFileM[1]
13
+ .split(',')
14
+ .map(r => r.trim())
15
+ .filter(Boolean);
16
+ for (const rule of rules) {
17
+ if (!map.has(rule))
18
+ map.set(rule, new Set());
19
+ map.get(rule).add(0);
20
+ }
21
+ }
10
22
  const m = line.match(/;\s*atlisp-lint:\s*disable=(.+)/i);
11
23
  if (!m)
12
24
  continue;
@@ -0,0 +1,8 @@
1
+ import { Issue } from '../types';
2
+ export type FixResult = string | null;
3
+ export interface FixProvider {
4
+ (line: string): string;
5
+ }
6
+ export declare const FIXABLE_RULES: Set<string>;
7
+ export declare function applyFixesFromProviders(issues: Issue[], content: string, filepath: string): string;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FIXABLE_RULES = void 0;
4
+ exports.applyFixesFromProviders = applyFixesFromProviders;
5
+ const utils_1 = require("../utils");
6
+ const nth_usage_1 = require("../checks/nth-usage");
7
+ const FIX_PROVIDERS = {
8
+ redundant_quotes(line) {
9
+ return line.replace(/''(\S)/g, "'$1");
10
+ },
11
+ double_not(line) {
12
+ return line.replace(/\(not\s+\(not\s+/g, '(not ');
13
+ },
14
+ redundant_nil_else(line) {
15
+ return line.replace(/\s+nil\s*\)$/, ')');
16
+ },
17
+ single_arg_and_or(line) {
18
+ const m = line.match(/\((and|or)\s+([^)\s]+)\s*\)/);
19
+ return m ? m[2] : line;
20
+ },
21
+ redundant_progn(line) {
22
+ const proIdx = line.indexOf('(progn ');
23
+ if (proIdx === -1)
24
+ return line;
25
+ const beforePro = line.slice(0, proIdx);
26
+ const close = (0, utils_1.findMatchingParen)(line, proIdx);
27
+ if (close <= proIdx)
28
+ return line;
29
+ const inner = line.slice(proIdx + 7, close);
30
+ const rest = line.slice(close + 1);
31
+ return beforePro + inner + rest;
32
+ },
33
+ redundant_let(line) {
34
+ return line.replace(/\(let\s+\(\)\s*/, '(progn ');
35
+ },
36
+ quote_style(line) {
37
+ const qIdx = line.indexOf('(quote ');
38
+ if (qIdx === -1)
39
+ return line;
40
+ const beforeQ = line.slice(0, qIdx);
41
+ const close = (0, utils_1.findMatchingParen)(line, qIdx);
42
+ if (close <= qIdx)
43
+ return line;
44
+ const quoted = line.slice(qIdx + 7, close);
45
+ const rest = line.slice(close + 1);
46
+ return beforeQ + "'" + quoted + rest;
47
+ },
48
+ redundant_if(line) {
49
+ const pIdx = line.indexOf('(progn ');
50
+ if (pIdx === -1)
51
+ return line;
52
+ const beforeP = line.slice(0, pIdx);
53
+ const close = (0, utils_1.findMatchingParen)(line, pIdx);
54
+ if (close <= pIdx)
55
+ return line;
56
+ const inner = line.slice(pIdx + 7, close);
57
+ const rest = line.slice(close + 1);
58
+ return beforeP + inner + rest;
59
+ },
60
+ misplaced_else(line) {
61
+ const ifStart = line.indexOf('(if (not ');
62
+ if (ifStart === -1)
63
+ return line;
64
+ const notIdx = line.indexOf('(not ', ifStart);
65
+ if (notIdx === -1)
66
+ return line;
67
+ const notClose = (0, utils_1.findMatchingParen)(line, notIdx);
68
+ if (notClose <= notIdx)
69
+ return line;
70
+ const ifClose = (0, utils_1.findMatchingParen)(line, ifStart);
71
+ if (ifClose <= ifStart)
72
+ return line;
73
+ const indent = line.slice(0, ifStart);
74
+ const condVar = line.slice(notIdx + 5, notClose);
75
+ const branchContent = line.slice(notClose + 1, ifClose).trim();
76
+ const [thenForm, thenEnd] = (0, utils_1.extractForm)(branchContent, 0);
77
+ const elseForm = branchContent.slice(thenEnd).trim();
78
+ if (!thenForm || !elseForm)
79
+ return line;
80
+ const rest = line.slice(ifClose + 1);
81
+ return indent + '(if ' + condVar + ' ' + elseForm + ' ' + thenForm + ')' + rest;
82
+ },
83
+ setq_multiple(line) {
84
+ const fixed = line.replace(/\)\(setq\s+/g, ' ');
85
+ if (fixed === line)
86
+ return line;
87
+ const merged = line.replace(/^\(\s*setq\s+/, '(setq ');
88
+ if (merged.includes('setq') && !merged.match(/\)\(/))
89
+ return merged;
90
+ return line.replace(/\)\(setq\s+/g, '\n');
91
+ },
92
+ append_single(line) {
93
+ const aIdx = line.indexOf('(append ');
94
+ if (aIdx === -1)
95
+ return line;
96
+ const close = (0, utils_1.findMatchingParen)(line, aIdx);
97
+ if (close <= aIdx)
98
+ return line;
99
+ const inner = line.slice(aIdx + 8, close);
100
+ const listMatch = inner.match(/^\(list\s+(.*)\)\s*$/);
101
+ if (!listMatch)
102
+ return line;
103
+ return line.slice(0, aIdx) + '(cons ' + listMatch[1] + ')' + line.slice(close + 1);
104
+ },
105
+ nth_usage(line) {
106
+ return (0, nth_usage_1.replaceNthUsage)(line) || line;
107
+ },
108
+ setq_single_arg(line) {
109
+ return line;
110
+ },
111
+ extra_parens(line) {
112
+ return line.replace(/\){2,}/g, ')');
113
+ },
114
+ empty_branch(line) {
115
+ return line.replace(/\(if\s+\S+\s*\)/g, m => m.slice(0, -1) + ' nil)');
116
+ },
117
+ comment_style(line) {
118
+ return line.replace(/^(\s*;)([^ ;|].*)$/gm, '$1 $2');
119
+ },
120
+ eq_usage(line) {
121
+ return line.replace(/\(eq\s+(\S+)\s+(\d+)\s*\)/g, '(= $1 $2)');
122
+ },
123
+ cond_simplify(line) {
124
+ const condIdx = line.indexOf('(cond ');
125
+ if (condIdx === -1)
126
+ return line;
127
+ const condEnd = (0, utils_1.findMatchingParen)(line, condIdx);
128
+ if (condEnd <= condIdx)
129
+ return line;
130
+ const inner = line.slice(condIdx + 6, condEnd).trim();
131
+ const branchOpen = inner.indexOf('(');
132
+ if (branchOpen === -1)
133
+ return line;
134
+ const branchEnd = (0, utils_1.findMatchingParen)(inner, branchOpen);
135
+ if (branchEnd <= branchOpen)
136
+ return line;
137
+ const indent = line.slice(0, condIdx);
138
+ const branchContent = inner.slice(branchOpen + 1, branchEnd).trim();
139
+ const space = branchContent.search(/\s+/);
140
+ if (space === -1)
141
+ return line;
142
+ const testExpr = branchContent.slice(0, space);
143
+ const bodyExpr = branchContent.slice(space).trim();
144
+ const rest = line.slice(condEnd + 1);
145
+ return indent + '(if ' + testExpr + ' ' + bodyExpr + ')' + rest;
146
+ },
147
+ self_compare(line) {
148
+ return line.replace(/\((?:[=/<>]+|eq|equal)\s+(\S+)\s+\1\s*\)/g, 'T');
149
+ },
150
+ while_constant(line) {
151
+ const trimmed = line.trim();
152
+ const nilMatch = trimmed.match(/^\(\s*while\s+nil\s+(.*)\)\s*$/);
153
+ if (!nilMatch)
154
+ return line;
155
+ return line.replace(trimmed, 'nil');
156
+ },
157
+ redundant_list(line) {
158
+ return line.replace(/\(\s*list\s*\)/g, 'nil');
159
+ },
160
+ zerop_usage(line) {
161
+ return line.replace(/\(=\s+(\S+)\s+0\s*\)/g, '(zerop $1)').replace(/\(=\s+0\s+(\S+)\s*\)/g, '(zerop $1)');
162
+ },
163
+ redundant_cond(line) {
164
+ const m = line.match(/^(\s*)\(cond\s+\(\(([^)]+)\)\s+\(([^)]*)\)\s*\)\s*\)$/);
165
+ if (!m)
166
+ return line;
167
+ return `${m[1]}(if (${m[2]}) (${m[3]}))`;
168
+ },
169
+ };
170
+ exports.FIXABLE_RULES = new Set(Object.keys(FIX_PROVIDERS));
171
+ function applyFixesFromProviders(issues, content, filepath) {
172
+ const lines = content.split('\n');
173
+ const fixedLines = new Set();
174
+ const fixesByRule = new Map();
175
+ for (const iss of issues) {
176
+ if (iss.file !== filepath)
177
+ continue;
178
+ const set = fixesByRule.get(iss.rule) || new Set();
179
+ set.add(iss.line);
180
+ fixesByRule.set(iss.rule, set);
181
+ }
182
+ for (const [rule, lineSet] of fixesByRule) {
183
+ const provider = FIX_PROVIDERS[rule];
184
+ if (!provider)
185
+ continue;
186
+ const sortedLines = Array.from(lineSet).sort((a, b) => b - a);
187
+ for (const line of sortedLines) {
188
+ const idx = line - 1;
189
+ if (idx < 0 || idx >= lines.length)
190
+ continue;
191
+ const fixed = provider(lines[idx]);
192
+ if (fixed !== lines[idx]) {
193
+ lines[idx] = fixed;
194
+ fixedLines.add(idx);
195
+ }
196
+ }
197
+ }
198
+ return lines.join('\n');
199
+ }
200
+ //# sourceMappingURL=index.js.map