@atlisp/lint 0.2.16 → 0.2.18

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 (80) hide show
  1. package/README.md +1 -1
  2. package/atlisp-lint.default.json +1 -1
  3. package/dist/checks/arg-count.d.ts +2 -1
  4. package/dist/checks/arg-count.js +37 -19
  5. package/dist/checks/builtin-arg-count.d.ts +2 -1
  6. package/dist/checks/builtin-arg-count.js +3 -2
  7. package/dist/checks/cond-duplicate.js +5 -6
  8. package/dist/checks/constant-condition.js +9 -9
  9. package/dist/checks/dangerous-calls.js +1 -1
  10. package/dist/checks/dangling-defun.d.ts +0 -7
  11. package/dist/checks/dangling-defun.js +0 -63
  12. package/dist/checks/double-not.js +1 -1
  13. package/dist/checks/empty-branch.js +1 -1
  14. package/dist/checks/empty-catch.d.ts +2 -1
  15. package/dist/checks/empty-catch.js +8 -7
  16. package/dist/checks/function-complexity.js +5 -18
  17. package/dist/checks/global-naming.js +1 -1
  18. package/dist/checks/identical-branches.js +3 -3
  19. package/dist/checks/if-without-else.js +1 -1
  20. package/dist/checks/misplaced-else.js +2 -2
  21. package/dist/checks/missing-export.js +3 -3
  22. package/dist/checks/missing-princ.js +1 -1
  23. package/dist/checks/multiple-setq.js +1 -1
  24. package/dist/checks/no-return.d.ts +2 -1
  25. package/dist/checks/no-return.js +4 -3
  26. package/dist/checks/nth-usage.js +1 -1
  27. package/dist/checks/parameter-naming.js +2 -2
  28. package/dist/checks/quote-vs-function.js +1 -1
  29. package/dist/checks/recursive-call.js +1 -1
  30. package/dist/checks/redundant-let.js +1 -1
  31. package/dist/checks/redundant-nil-else.js +1 -1
  32. package/dist/checks/redundant-progn.js +1 -1
  33. package/dist/checks/redundant-setq.js +1 -1
  34. package/dist/checks/self-compare.js +1 -1
  35. package/dist/checks/setq-single-arg.js +3 -2
  36. package/dist/checks/single-arg-and-or.js +1 -1
  37. package/dist/checks/undeclared-setq.d.ts +2 -1
  38. package/dist/checks/undeclared-setq.js +6 -5
  39. package/dist/checks/unused-let.js +3 -3
  40. package/dist/checks/unused-local-fun.js +2 -2
  41. package/dist/checks/unused-package-dep.d.ts +0 -1
  42. package/dist/checks/unused-package-dep.js +6 -93
  43. package/dist/checks/unused-param.js +2 -2
  44. package/dist/checks/unused-variable.js +2 -2
  45. package/dist/checks/variable-shadow.js +3 -3
  46. package/dist/checks/while-constant.js +1 -1
  47. package/dist/cli/collector.js +26 -19
  48. package/dist/config.d.ts +1 -1
  49. package/dist/config.js +18 -9
  50. package/dist/fixes/index.d.ts +4 -0
  51. package/dist/fixes/index.js +843 -140
  52. package/dist/formatter.d.ts +0 -1
  53. package/dist/formatter.js +141 -31
  54. package/dist/index.js +59 -51
  55. package/dist/project.d.ts +1 -1
  56. package/dist/project.js +98 -112
  57. package/dist/runner.d.ts +1 -1
  58. package/dist/runner.js +17 -8
  59. package/dist/utils.js +21 -30
  60. package/dist/validate.js +3 -3
  61. package/dist/visitor-runner.d.ts +0 -28
  62. package/dist/visitor-runner.js +3 -809
  63. package/dist/visitors/equal-nil.d.ts +2 -2
  64. package/dist/visitors/equal-nil.js +5 -4
  65. package/dist/visitors/index.d.ts +1 -1
  66. package/dist/visitors/progn-in-cond.d.ts +2 -2
  67. package/dist/visitors/progn-in-cond.js +5 -4
  68. package/dist/visitors/redundant-and-or.d.ts +2 -2
  69. package/dist/visitors/redundant-and-or.js +3 -3
  70. package/dist/visitors/redundant-car-cdr.d.ts +2 -2
  71. package/dist/visitors/redundant-car-cdr.js +3 -3
  72. package/dist/visitors/register.d.ts +6 -0
  73. package/dist/visitors/register.js +625 -0
  74. package/dist/visitors/shared.d.ts +40 -0
  75. package/dist/visitors/shared.js +129 -0
  76. package/dist/visitors/types.d.ts +2 -7
  77. package/dist/visitors/types.js +0 -69
  78. package/dist/watch.js +33 -12
  79. package/dist/worker.js +1 -1
  80. package/package.json +2 -2
@@ -5,6 +5,7 @@ const locale_1 = require("./locale");
5
5
  const disable_1 = require("./disable");
6
6
  const parser_1 = require("@atlisp/parser");
7
7
  const index_1 = require("./visitors/index");
8
+ const register_1 = require("./visitors/register");
8
9
  function runChecksWithVisitor(ast, file, config, disableMap) {
9
10
  const issues = [];
10
11
  const checks = config.checks;
@@ -29,814 +30,7 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
29
30
  issues.push({ file, line, severity: severity, rule, message });
30
31
  }
31
32
  }
32
- function isInQuote(node) {
33
- let p = node.parent;
34
- while (p) {
35
- if (p.type === 'list' &&
36
- p.children &&
37
- p.children.length > 0 &&
38
- p.children[0].type === 'symbol' &&
39
- (p.children[0].name === 'quote' || p.children[0].name === 'function')) {
40
- return true;
41
- }
42
- p = p.parent;
43
- }
44
- return false;
45
- }
46
- function isLiteral(node) {
47
- if (node.type === 'symbol' && (node.name === 'T' || node.name === 't' || node.name === 'nil'))
48
- return true;
49
- if (node.type === 'number')
50
- return true;
51
- return false;
52
- }
53
- function astEqual(a, b) {
54
- if (a.type !== b.type)
55
- return false;
56
- if (a.type === 'symbol')
57
- return a.name === b.name;
58
- if (a.type === 'string')
59
- return a.value === b.value;
60
- if (a.type === 'number')
61
- return a.value === b.value;
62
- if (a.type === 'list') {
63
- const ac = a.children || [];
64
- const bc = b.children || [];
65
- if (ac.length !== bc.length)
66
- return false;
67
- for (let i = 0; i < ac.length; i++) {
68
- if (!astEqual(ac[i], bc[i]))
69
- return false;
70
- }
71
- return true;
72
- }
73
- return false;
74
- }
75
- function getCondKey(node) {
76
- if (node.type === 'symbol' || node.type === 'keyword')
77
- return node.name || '';
78
- if (node.type === 'string' || node.type === 'number')
79
- return String(node.value);
80
- if (node.type === 'list' && node.children) {
81
- return '(' + node.children.map(c => getCondKey(c)).join(' ') + ')';
82
- }
83
- return '';
84
- }
85
- function hasSelfCall(body, fnName) {
86
- for (const form of body) {
87
- if (form.type === 'list' &&
88
- form.children &&
89
- form.children.length > 0 &&
90
- form.children[0].type === 'symbol' &&
91
- form.children[0].name === fnName) {
92
- return true;
93
- }
94
- }
95
- return false;
96
- }
97
- function hasConditionalForm(body) {
98
- for (const form of body) {
99
- if (form.type === 'list' && form.children && form.children.length > 0 && form.children[0].type === 'symbol') {
100
- const name = form.children[0].name;
101
- if (name === 'if' || name === 'cond' || name === 'when' || name === 'unless') {
102
- return true;
103
- }
104
- }
105
- }
106
- return false;
107
- }
108
- function defunParams(defunNode) {
109
- if (!defunNode.children || defunNode.children.length < 3)
110
- return [];
111
- const paramList = defunNode.children[2];
112
- if (!paramList.children)
113
- return [];
114
- const params = [];
115
- for (const child of paramList.children) {
116
- if (child.type === 'symbol' && child.name && child.name !== '/') {
117
- params.push(child.name);
118
- }
119
- }
120
- return params;
121
- }
122
- function defunLocals(defunNode) {
123
- if (!defunNode.children || defunNode.children.length < 3)
124
- return [];
125
- const paramList = defunNode.children[2];
126
- if (!paramList.children)
127
- return [];
128
- const locals = [];
129
- let afterSlash = false;
130
- for (const child of paramList.children) {
131
- if (child.type === 'symbol' && child.name === '/') {
132
- afterSlash = true;
133
- continue;
134
- }
135
- if (afterSlash && child.type === 'symbol' && child.name) {
136
- locals.push(child.name);
137
- }
138
- }
139
- return locals;
140
- }
141
- function collectSymbolRefs(node, result) {
142
- if (node.type === 'symbol' && node.name) {
143
- result.add(node.name);
144
- }
145
- if (node.children) {
146
- for (const c of node.children) {
147
- collectSymbolRefs(c, result);
148
- }
149
- }
150
- }
151
- const higherOrderFuncs = new Set([
152
- 'mapcar',
153
- 'apply',
154
- 'lambda',
155
- 'vl-sort',
156
- 'vl-sort-i',
157
- 'vl-remove-if',
158
- 'vl-remove-if-not',
159
- 'vl-member-if',
160
- 'vl-some',
161
- 'vl-every',
162
- ]);
163
- // redundant_progn
164
- if (checks['redundant_progn'] !== 'off') {
165
- visitor.on('progn', node => {
166
- if (node.children && node.children.length === 2) {
167
- addIssue('redundant_progn', node.pos.line, (0, locale_1.t)('redundant_progn', 'progn'));
168
- }
169
- });
170
- }
171
- // redundant_setq
172
- if (checks['redundant_setq'] !== 'off') {
173
- visitor.on('setq', node => {
174
- if (!node.children)
175
- return;
176
- for (let i = 1; i < node.children.length - 1; i += 2) {
177
- const varNode = node.children[i];
178
- const valNode = node.children[i + 1];
179
- if (varNode.type === 'symbol' && valNode.type === 'symbol' && varNode.name === valNode.name) {
180
- addIssue('redundant_setq', varNode.pos.line, (0, locale_1.t)('redundant_setq', varNode.name || ''));
181
- }
182
- }
183
- });
184
- }
185
- // redundant_nil_else
186
- if (checks['redundant_nil_else'] !== 'off') {
187
- visitor.on('if', node => {
188
- if (node.children && node.children.length === 4) {
189
- const last = node.children[3];
190
- if (last.type === 'symbol' && last.name === 'nil') {
191
- addIssue('redundant_nil_else', node.pos.line, (0, locale_1.t)('redundant_nil_else'));
192
- }
193
- }
194
- });
195
- }
196
- // single_arg_and_or
197
- if (checks['single_arg_and_or'] !== 'off') {
198
- const checkSingleArg = (node) => {
199
- if (node.children && node.children.length === 2) {
200
- const name = node.children[0].type === 'symbol' ? node.children[0].name : '';
201
- addIssue('single_arg_and_or', node.pos.line, (0, locale_1.t)('single_arg_and_or', name || 'and/or'));
202
- }
203
- };
204
- visitor.on('and', checkSingleArg);
205
- visitor.on('or', checkSingleArg);
206
- }
207
- // redundant_let
208
- if (checks['redundant_let'] !== 'off') {
209
- visitor.on('let', node => {
210
- if (node.children && node.children.length >= 2) {
211
- const bindings = node.children[1];
212
- if (bindings.type === 'list' && (!bindings.children || bindings.children.length === 0)) {
213
- addIssue('redundant_let', node.pos.line, (0, locale_1.t)('redundant_let'));
214
- }
215
- }
216
- });
217
- }
218
- // self_compare
219
- if (checks['self_compare'] !== 'off') {
220
- const compareFns = new Set(['=', '/=', '<', '>', '<=', '>=', 'eq', 'equal']);
221
- visitor.on('*', node => {
222
- if (node.type === 'list' && node.children && node.children.length === 3) {
223
- const op = node.children[0];
224
- if (op.type === 'symbol' && op.name && compareFns.has(op.name)) {
225
- const a = node.children[1];
226
- const b = node.children[2];
227
- if (a.type === 'symbol' && b.type === 'symbol' && a.name === b.name) {
228
- addIssue('self_compare', node.pos.line, (0, locale_1.t)('self_compare', `(${op.name} ${a.name} ${b.name})`));
229
- }
230
- }
231
- }
232
- });
233
- }
234
- // double_not
235
- if (checks['double_not'] !== 'off') {
236
- visitor.on('not', node => {
237
- if (node.children &&
238
- node.children.length >= 2 &&
239
- node.children[1].type === 'list' &&
240
- node.children[1].children &&
241
- node.children[1].children.length > 0 &&
242
- node.children[1].children[0].type === 'symbol' &&
243
- node.children[1].children[0].name === 'not') {
244
- addIssue('double_not', node.pos.line, (0, locale_1.t)('double_not'));
245
- }
246
- });
247
- }
248
- // setq_single_arg
249
- if (checks['setq_single_arg'] !== 'off') {
250
- visitor.on('setq', node => {
251
- if (node.children && node.children.length === 2) {
252
- const varName = node.children[1].type === 'symbol' ? node.children[1].name : '';
253
- addIssue('setq_single_arg', node.pos.line, (0, locale_1.t)('setq_single_arg', varName || ''));
254
- }
255
- });
256
- }
257
- // empty_branch
258
- if (checks['empty_branch'] !== 'off') {
259
- visitor.on('if', node => {
260
- if (node.children && node.children.length < 3) {
261
- addIssue('empty_branch', node.pos.line, (0, locale_1.t)('empty_branch', 'if'));
262
- }
263
- });
264
- }
265
- // misplaced_else
266
- if (checks['misplaced_else'] !== 'off') {
267
- visitor.on('if', node => {
268
- if (node.children && node.children.length === 4) {
269
- const cond = node.children[1];
270
- if (cond.type === 'list' &&
271
- cond.children &&
272
- cond.children.length >= 2 &&
273
- cond.children[0].type === 'symbol' &&
274
- cond.children[0].name === 'not') {
275
- const innerName = cond.children[1].type === 'symbol' ? cond.children[1].name || '' : '';
276
- addIssue('misplaced_else', node.pos.line, (0, locale_1.t)('misplaced_else', innerName));
277
- }
278
- }
279
- });
280
- }
281
- // while_constant
282
- if (checks['while_constant'] !== 'off') {
283
- visitor.on('while', node => {
284
- if (node.children && node.children.length >= 2) {
285
- const cond = node.children[1];
286
- if (isLiteral(cond)) {
287
- const name = cond.type === 'symbol' ? cond.name || '' : String(cond.value ?? '');
288
- addIssue('while_constant', node.pos.line, (0, locale_1.t)('while_constant', name));
289
- }
290
- }
291
- });
292
- }
293
- // constant_condition
294
- if (checks['constant_condition'] !== 'off') {
295
- const checkConstant = (node, formName) => {
296
- if (node.children && node.children.length >= 2) {
297
- const cond = node.children[1];
298
- if (isLiteral(cond)) {
299
- const name = cond.type === 'symbol' ? cond.name || '' : String(cond.value ?? '');
300
- addIssue('constant_condition', node.pos.line, (0, locale_1.t)('constant_condition', name, formName));
301
- }
302
- }
303
- };
304
- visitor.on('if', node => checkConstant(node, 'if'));
305
- visitor.on('while', node => checkConstant(node, 'while'));
306
- visitor.on('cond', node => {
307
- if (node.children && node.children.length >= 2) {
308
- const clause = node.children[1];
309
- if (clause.type === 'list' && clause.children && clause.children.length >= 2) {
310
- const test = clause.children[1];
311
- if (isLiteral(test)) {
312
- const name = test.type === 'symbol' ? test.name || '' : String(test.value ?? '');
313
- addIssue('constant_condition', node.pos.line, (0, locale_1.t)('constant_condition', name, 'cond'));
314
- }
315
- }
316
- }
317
- });
318
- }
319
- // quote_vs_function
320
- if (checks['quote_vs_function'] !== 'off') {
321
- visitor.on('quote', node => {
322
- if (node.children &&
323
- node.children.length >= 2 &&
324
- node.children[1].type === 'list' &&
325
- node.children[1].children &&
326
- node.children[1].children.length > 0 &&
327
- node.children[1].children[0].type === 'symbol' &&
328
- node.children[1].children[0].name === 'lambda') {
329
- const parent = node.parent;
330
- if (parent && parent.children && parent.children.length > 0) {
331
- const pName = (parent.children[0].type === 'symbol' && parent.children[0].name) || '';
332
- if (higherOrderFuncs.has(pName)) {
333
- addIssue('quote_vs_function', node.pos.line, (0, locale_1.t)('quote_vs_function', pName));
334
- }
335
- }
336
- }
337
- });
338
- }
339
- // redundant_quotes
340
- if (checks['redundant_quotes'] !== 'off') {
341
- visitor.on('quote', node => {
342
- if (node.children &&
343
- node.children.length >= 2 &&
344
- node.children[1].type === 'list' &&
345
- node.children[1].children &&
346
- node.children[1].children.length > 0 &&
347
- node.children[1].children[0].type === 'symbol' &&
348
- node.children[1].children[0].name === 'quote') {
349
- addIssue('redundant_quotes', node.pos.line, (0, locale_1.t)('redundant_quotes'));
350
- }
351
- });
352
- }
353
- // parameter_naming
354
- if (checks['parameter_naming'] !== 'off') {
355
- visitor.on('defun', node => {
356
- if (!node.children || node.children.length < 3)
357
- return;
358
- const paramList = node.children[2];
359
- if (!paramList.children)
360
- return;
361
- const fnName = node.children[1].type === 'symbol' && node.children[1].name ? node.children[1].name : '';
362
- for (const child of paramList.children) {
363
- if (child.type !== 'symbol' || !child.name)
364
- continue;
365
- if (/^[A-Z]/.test(child.name) && child.name !== 'T' && child.name !== 'nil') {
366
- addIssue('parameter_naming', child.pos.line, (0, locale_1.t)('parameter_naming', child.name, fnName));
367
- }
368
- }
369
- });
370
- }
371
- // assoc_without_cdr
372
- if (checks['assoc_without_cdr'] !== 'off') {
373
- visitor.on('assoc', node => {
374
- const parent = node.parent;
375
- if (!parent) {
376
- addIssue('assoc_without_cdr', node.pos.line, (0, locale_1.t)('assoc_without_cdr'));
377
- return;
378
- }
379
- if (parent.type === 'list' &&
380
- parent.children &&
381
- parent.children.length > 0 &&
382
- parent.children[0].type === 'symbol' &&
383
- (parent.children[0].name === 'cdr' || parent.children[0].name === 'cadr')) {
384
- return;
385
- }
386
- addIssue('assoc_without_cdr', node.pos.line, (0, locale_1.t)('assoc_without_cdr'));
387
- });
388
- }
389
- // identical_branches
390
- if (checks['identical_branches'] !== 'off') {
391
- visitor.on('if', node => {
392
- if (node.children && node.children.length === 4) {
393
- const thenBranch = node.children[2];
394
- const elseBranch = node.children[3];
395
- if (astEqual(thenBranch, elseBranch)) {
396
- addIssue('identical_branches', node.pos.line, (0, locale_1.t)('identical_branches'));
397
- }
398
- }
399
- });
400
- }
401
- // open_without_close (data collection)
402
- if (checks['open_without_close'] !== 'off') {
403
- visitor.on('open', () => {
404
- state.openCount++;
405
- });
406
- visitor.on('close', () => {
407
- state.closeCount++;
408
- });
409
- }
410
- // cond_duplicate (data collection)
411
- if (checks['cond_duplicate'] !== 'off') {
412
- visitor.on('cond', node => {
413
- if (!node.children)
414
- return;
415
- const seen = new Set();
416
- for (let i = 1; i < node.children.length; i++) {
417
- const clause = node.children[i];
418
- if (clause.type !== 'list' || !clause.children || clause.children.length < 1)
419
- continue;
420
- const condTest = clause.children[0];
421
- const key = getCondKey(condTest);
422
- if (seen.has(key)) {
423
- addIssue('cond_duplicate', clause.pos.line, (0, locale_1.t)('cond_duplicate'));
424
- }
425
- seen.add(key);
426
- }
427
- });
428
- }
429
- // dangerous_calls (data collection) - skip if in quote
430
- if (checks['quit_exit'] !== 'off') {
431
- const dangerNames = new Set();
432
- const dc = config.dangerous_calls;
433
- if (dc.quit !== 'off')
434
- dangerNames.add('quit');
435
- if (dc.exit !== 'off')
436
- dangerNames.add('exit');
437
- if (dc.startapp !== 'off')
438
- dangerNames.add('startapp');
439
- if (dc.vl_registry_write !== 'off')
440
- dangerNames.add('vl-registry-write');
441
- if (dc.eval !== 'off')
442
- dangerNames.add('eval');
443
- if (dangerNames.size > 0) {
444
- visitor.on('*', node => {
445
- if (node.type === 'list' &&
446
- node.children &&
447
- node.children.length > 0 &&
448
- node.children[0].type === 'symbol' &&
449
- node.children[0].name &&
450
- dangerNames.has(node.children[0].name)) {
451
- if (!isInQuote(node)) {
452
- state.dangerousCalls.push({ name: node.children[0].name, node });
453
- }
454
- }
455
- });
456
- }
457
- }
458
- // multiple_setq (data collection via wildcard)
459
- if (checks['multiple_setq'] !== 'off') {
460
- visitor.on('*', node => {
461
- if (node.type === 'list' && node.children) {
462
- let prevSetq = false;
463
- for (const child of node.children) {
464
- if (child.type === 'list' &&
465
- child.children &&
466
- child.children.length > 0 &&
467
- child.children[0].type === 'symbol' &&
468
- child.children[0].name === 'setq') {
469
- if (prevSetq) {
470
- addIssue('multiple_setq', child.pos.line, (0, locale_1.t)('multiple_setq'));
471
- prevSetq = false;
472
- }
473
- else {
474
- prevSetq = true;
475
- }
476
- }
477
- else {
478
- prevSetq = false;
479
- }
480
- }
481
- }
482
- });
483
- }
484
- // recursive_call (data collection)
485
- if (checks['recursive_call'] !== 'off') {
486
- visitor.on('defun', node => {
487
- if (!node.children || node.children.length < 4)
488
- return;
489
- const fnName = node.children[1].type === 'symbol' ? node.children[1].name : '';
490
- if (!fnName)
491
- return;
492
- const body = node.children.slice(3);
493
- if (hasSelfCall(body, fnName) && !hasConditionalForm(body)) {
494
- addIssue('recursive_call', node.pos.line, (0, locale_1.t)('recursive_call', fnName));
495
- }
496
- });
497
- }
498
- // variable_shadow (data collection)
499
- if (checks['variable_shadow'] !== 'off') {
500
- visitor.on('let', node => {
501
- if (!node.children || node.children.length < 2)
502
- return;
503
- const bindings = node.children[1];
504
- if (bindings.type !== 'list' || !bindings.children)
505
- return;
506
- const letVars = new Set();
507
- for (const b of bindings.children) {
508
- if (b.type === 'list' &&
509
- b.children &&
510
- b.children.length > 0 &&
511
- b.children[0].type === 'symbol' &&
512
- b.children[0].name) {
513
- letVars.add(b.children[0].name);
514
- }
515
- }
516
- // Walk body for setq of let-bound vars (shallow, don't descend into nested lets)
517
- const walkBodyForSetq = (forms) => {
518
- for (const form of forms) {
519
- if (form.type === 'list' && form.children && form.children.length > 0 && form.children[0].type === 'symbol') {
520
- if (form.children[0].name === 'setq' && form.children.length > 1) {
521
- for (let i = 1; i < form.children.length; i += 2) {
522
- const child = form.children[i];
523
- if (child.type !== 'symbol' || !child.name)
524
- continue;
525
- if (letVars.has(child.name)) {
526
- addIssue('variable_shadow', child.pos.line, (0, locale_1.t)('variable_shadow', child.name));
527
- }
528
- }
529
- }
530
- else if (form.children[0].name !== 'let') {
531
- // Don't descend into nested lets
532
- if (form.children) {
533
- walkBodyForSetq(form.children.slice(1));
534
- }
535
- }
536
- }
537
- }
538
- };
539
- walkBodyForSetq(node.children.slice(2));
540
- });
541
- }
542
- // unused_let_binding (data collection)
543
- if (checks['unused_let_binding'] !== 'off') {
544
- visitor.on('let', node => {
545
- if (!node.children || node.children.length < 2)
546
- return;
547
- const bindings = node.children[1];
548
- if (bindings.type !== 'list' || !bindings.children)
549
- return;
550
- const letVars = [];
551
- for (const b of bindings.children) {
552
- if (b.type === 'list' &&
553
- b.children &&
554
- b.children.length > 0 &&
555
- b.children[0].type === 'symbol' &&
556
- b.children[0].name) {
557
- letVars.push({ name: b.children[0].name, line: b.children[0].pos.line });
558
- }
559
- }
560
- if (letVars.length === 0)
561
- return;
562
- const bodyRefs = new Set();
563
- for (const form of node.children.slice(2)) {
564
- collectSymbolRefs(form, bodyRefs);
565
- }
566
- for (const v of letVars) {
567
- if (!bodyRefs.has(v.name)) {
568
- addIssue('unused_let_binding', v.line, (0, locale_1.t)('unused_let_binding', v.name));
569
- }
570
- }
571
- });
572
- }
573
- // defun-based unused checks (unused_parameter, unused_local_fun) - data collection
574
- if (checks['unused_parameter'] !== 'off' || checks['unused_local_fun'] !== 'off') {
575
- visitor.on('defun', node => {
576
- if (!node.children || node.children.length < 4)
577
- return;
578
- const fnName = node.children[1].type === 'symbol' ? node.children[1].name : '';
579
- if (!fnName || fnName.startsWith('C:') || fnName.startsWith('c:'))
580
- return;
581
- const body = node.children.slice(3);
582
- if (checks['unused_parameter'] !== 'off') {
583
- const params = defunParams(node);
584
- if (params.length > 0) {
585
- const bodyRefs = new Set();
586
- for (const form of body) {
587
- collectSymbolRefs(form, bodyRefs);
588
- }
589
- for (const p of params) {
590
- if (!bodyRefs.has(p) && p !== fnName) {
591
- addIssue('unused_parameter', node.pos.line, (0, locale_1.t)('unused_parameter', p, fnName));
592
- }
593
- }
594
- }
595
- }
596
- if (checks['unused_local_fun'] !== 'off') {
597
- const locals = defunLocals(node);
598
- if (locals.length > 0) {
599
- const bodyRefs = new Set();
600
- for (const form of body) {
601
- collectSymbolRefs(form, bodyRefs);
602
- }
603
- for (const l of locals) {
604
- if (!bodyRefs.has(l)) {
605
- addIssue('unused_local_fun', node.pos.line, (0, locale_1.t)('unused_local_fun', l));
606
- }
607
- }
608
- }
609
- }
610
- });
611
- }
612
- // unused_variable (data collection)
613
- if (checks['unused_variable'] !== 'off') {
614
- visitor.on('setq', node => {
615
- if (!node.children)
616
- return;
617
- for (let i = 1; i < node.children.length; i += 2) {
618
- const varNode = node.children[i];
619
- if (varNode.type === 'symbol' &&
620
- varNode.name &&
621
- !varNode.name.startsWith('*') &&
622
- !varNode.name.includes(':*') &&
623
- varNode.name !== 'T' &&
624
- varNode.name !== 'nil' &&
625
- !varNode.name.startsWith('/')) {
626
- state.setqDefs.set(varNode.name, { line: varNode.pos.line, file });
627
- }
628
- }
629
- });
630
- }
631
- // Collect all symbol references for unused_variable
632
- if (checks['unused_variable'] !== 'off') {
633
- visitor.on('*', node => {
634
- if (node.type === 'symbol' && node.name) {
635
- state.symbolRefs.set(node.name, (state.symbolRefs.get(node.name) || 0) + 1);
636
- }
637
- });
638
- }
639
- // function_complexity (data collection)
640
- if (checks['function_complexity'] !== 'off') {
641
- visitor.on('defun', node => {
642
- if (node.children && node.children.length >= 3) {
643
- const fnName = (node.children[1].type === 'symbol' ? node.children[1].name : '<anonymous>') || '<anonymous>';
644
- state.defunScopes.push({
645
- name: fnName,
646
- params: [],
647
- body: node.children.slice(3),
648
- line: node.pos.line,
649
- file,
650
- });
651
- }
652
- });
653
- }
654
- // if_without_else
655
- if (checks['if_without_else'] !== 'off') {
656
- visitor.on('if', node => {
657
- if (node.children && node.children.length === 3) {
658
- const parent = node.parent;
659
- if (parent &&
660
- parent.type === 'list' &&
661
- parent.children &&
662
- parent.children.length > 0 &&
663
- parent.children[0].type === 'symbol') {
664
- const parentName = parent.children[0].name;
665
- if (parentName === 'cond' || parentName === 'if')
666
- return;
667
- }
668
- addIssue('if_without_else', node.pos.line, (0, locale_1.t)('if_without_else'));
669
- }
670
- });
671
- }
672
- // strcat_or_arg
673
- if (checks['strcat_or_arg'] !== 'off') {
674
- visitor.on('*', node => {
675
- if (node.type === 'list' &&
676
- node.children &&
677
- node.children.length > 1 &&
678
- node.children[0].type === 'symbol' &&
679
- node.children[0].name === 'strcat') {
680
- if (isInQuote(node))
681
- return;
682
- for (let i = 1; i < node.children.length; i++) {
683
- const arg = node.children[i];
684
- if (arg.type === 'list' &&
685
- arg.children &&
686
- arg.children.length > 0 &&
687
- arg.children[0].type === 'symbol' &&
688
- arg.children[0].name === 'or') {
689
- addIssue('strcat_or_arg', arg.pos.line, (0, locale_1.t)('strcat_or_arg'));
690
- }
691
- }
692
- }
693
- });
694
- }
695
- // redundant_list
696
- if (checks['redundant_list'] !== 'off') {
697
- visitor.on('list', node => {
698
- if (node.children && node.children.length === 1) {
699
- addIssue('redundant_list', node.pos.line, (0, locale_1.t)('redundant_list'));
700
- }
701
- });
702
- }
703
- // command_s — recommend (command-s ...) over (command ...)
704
- if (checks['command_s'] !== 'off') {
705
- visitor.on('command', node => {
706
- if (node.children && node.children.length >= 2) {
707
- const subCmd = node.children[1];
708
- if (subCmd.type === 'string' && typeof subCmd.value === 'string' && subCmd.value === 'shell')
709
- return;
710
- addIssue('command_s', node.pos.line, (0, locale_1.t)('command_s'));
711
- }
712
- });
713
- }
714
- // zerop_usage — (= x 0) → (zerop x)
715
- if (checks['zerop_usage'] !== 'off') {
716
- const checkZero = (node) => {
717
- if (node.children && node.children.length === 3) {
718
- const a = node.children[1];
719
- const b = node.children[2];
720
- const extract = (n) => n.type === 'symbol' ? n.name || '' : n.type === 'number' ? String(n.value ?? '') : '';
721
- if (a.type === 'number' && a.value === 0 && b.type === 'symbol') {
722
- addIssue('zerop_usage', node.pos.line, (0, locale_1.t)('zerop_usage', extract(b)));
723
- }
724
- else if (b.type === 'number' && b.value === 0 && a.type === 'symbol') {
725
- addIssue('zerop_usage', node.pos.line, (0, locale_1.t)('zerop_usage', extract(a)));
726
- }
727
- }
728
- };
729
- visitor.on('=', checkZero);
730
- visitor.on('equal', checkZero);
731
- }
732
- // progn_in_body — redundant progn in while/repeat/foreach
733
- if (checks['progn_in_body'] !== 'off') {
734
- const checkPrognInBody = (node, formName, bodyStartIdx) => {
735
- if (node.children && node.children.length > bodyStartIdx) {
736
- const firstBody = node.children[bodyStartIdx];
737
- if (firstBody.type === 'list' &&
738
- firstBody.children &&
739
- firstBody.children.length > 0 &&
740
- firstBody.children[0].type === 'symbol' &&
741
- firstBody.children[0].name === 'progn') {
742
- addIssue('progn_in_body', firstBody.pos.line, (0, locale_1.t)('progn_in_body', formName));
743
- }
744
- }
745
- };
746
- visitor.on('while', node => checkPrognInBody(node, 'while', 2));
747
- visitor.on('repeat', node => checkPrognInBody(node, 'repeat', 2));
748
- visitor.on('foreach', node => checkPrognInBody(node, 'foreach', 3));
749
- }
750
- // if_progn_both — both branches of if use progn
751
- if (checks['if_progn_both'] !== 'off') {
752
- visitor.on('if', node => {
753
- if (node.children && node.children.length === 4) {
754
- const thenBranch = node.children[2];
755
- const elseBranch = node.children[3];
756
- const isProgn = (n) => n.type === 'list' &&
757
- n.children !== undefined &&
758
- n.children.length > 0 &&
759
- n.children[0].type === 'symbol' &&
760
- n.children[0].name === 'progn';
761
- if (isProgn(thenBranch) && isProgn(elseBranch)) {
762
- addIssue('if_progn_both', node.pos.line, (0, locale_1.t)('if_progn_both'));
763
- }
764
- }
765
- });
766
- }
767
- // redundant_cond — single-clause cond
768
- if (checks['redundant_cond'] !== 'off') {
769
- visitor.on('cond', node => {
770
- if (node.children && node.children.length === 2) {
771
- const clause = node.children[1];
772
- if (clause.type === 'list' && clause.children && clause.children.length === 2) {
773
- const test = clause.children[0];
774
- if (test.type === 'symbol' && test.name === 't')
775
- return;
776
- addIssue('redundant_cond', node.pos.line, (0, locale_1.t)('redundant_cond'));
777
- }
778
- }
779
- });
780
- }
781
- // empty_branch_cond — cond clause with no body
782
- if (checks['empty_branch_cond'] !== 'off') {
783
- visitor.on('cond', node => {
784
- if (!node.children)
785
- return;
786
- for (let i = 1; i < node.children.length; i++) {
787
- const clause = node.children[i];
788
- if (clause.type === 'list' && (!clause.children || clause.children.length < 2)) {
789
- addIssue('empty_branch_cond', clause.pos.line, (0, locale_1.t)('empty_branch_cond'));
790
- }
791
- }
792
- });
793
- }
794
- // selection_set_leak (data collection)
795
- if (checks['selection_set_leak'] !== 'off') {
796
- visitor.on('ssget', () => {
797
- state.ssgetCount++;
798
- });
799
- visitor.on('ssset', () => {
800
- state.sssetCount++;
801
- });
802
- }
803
- // promise_handler
804
- if (checks['promise_handler'] !== 'off') {
805
- visitor.on('*', node => {
806
- if (node.type === 'list' &&
807
- node.children &&
808
- node.children.length > 0 &&
809
- node.children[0].type === 'symbol' &&
810
- node.children[0].name &&
811
- (node.children[0].name.startsWith('vlax-invoke') || node.children[0].name.startsWith('vlax-invoke-method'))) {
812
- if (isInQuote(node))
813
- return;
814
- const parent = node.parent;
815
- if (parent &&
816
- parent.type === 'list' &&
817
- parent.children &&
818
- parent.children.length > 0 &&
819
- parent.children[0].type === 'symbol' &&
820
- parent.children[0].name === 'vl-catch-all-apply') {
821
- return;
822
- }
823
- let hasCallback = false;
824
- for (const child of (node.children || []).slice(1)) {
825
- if (child.type === 'list' &&
826
- child.children &&
827
- child.children.length > 0 &&
828
- child.children[0].type === 'symbol' &&
829
- (child.children[0].name === 'lambda' || child.children[0].name === 'function')) {
830
- hasCallback = true;
831
- break;
832
- }
833
- }
834
- if (!hasCallback) {
835
- addIssue('promise_handler', node.pos.line, (0, locale_1.t)('promise_handler'));
836
- }
837
- }
838
- });
839
- }
33
+ (0, register_1.registerVisitorRules)(visitor, state, checks, addIssue, config, file);
840
34
  (0, index_1.registerAll)(visitor, state, checks, addIssue);
841
35
  visitor.run(ast);
842
36
  // ===== POST-PROCESSING =====
@@ -894,7 +88,7 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
894
88
  for (const n of nodes) {
895
89
  if (n.end && n.end.line > maxLine)
896
90
  maxLine = n.end.line;
897
- if (n.children)
91
+ if ((0, parser_1.astIsList)(n))
898
92
  computeEnd(n.children);
899
93
  }
900
94
  };