@atlisp/lint 0.1.16 → 0.1.19

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.
@@ -0,0 +1,709 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runChecksWithVisitor = runChecksWithVisitor;
4
+ const locale_1 = require("./locale");
5
+ const disable_1 = require("./disable");
6
+ const parser_1 = require("@atlisp/parser");
7
+ function emit(issues, rule, line, severity, message, file) {
8
+ if (severity === 'off')
9
+ return;
10
+ issues.push({ file, line, severity: severity, rule, message });
11
+ }
12
+ function runChecksWithVisitor(ast, file, config, disableMap) {
13
+ const issues = [];
14
+ const checks = config.checks;
15
+ const visitor = new parser_1.AstVisitor();
16
+ const state = {
17
+ openCount: 0,
18
+ closeCount: 0,
19
+ setqDefs: new Map(),
20
+ symbolRefs: new Set(),
21
+ condKeys: new Map(),
22
+ defunScopes: [],
23
+ letScopes: [],
24
+ dangerousCalls: [],
25
+ };
26
+ function addIssue(rule, line, message) {
27
+ const severity = checks[rule];
28
+ if (!severity || severity === 'off')
29
+ return;
30
+ if (!(0, disable_1.isDisabled)(rule, line, disableMap)) {
31
+ issues.push({ file, line, severity: severity, rule, message });
32
+ }
33
+ }
34
+ function isInQuote(node) {
35
+ let p = node.parent;
36
+ while (p) {
37
+ if (p.type === 'list' && p.children && p.children.length > 0 && p.children[0].type === 'symbol' && (p.children[0].name === 'quote' || p.children[0].name === 'function')) {
38
+ return true;
39
+ }
40
+ p = p.parent;
41
+ }
42
+ return false;
43
+ }
44
+ function isLiteral(node) {
45
+ if (node.type === 'symbol' && (node.name === 'T' || node.name === 't' || node.name === 'nil'))
46
+ return true;
47
+ if (node.type === 'number')
48
+ return true;
49
+ return false;
50
+ }
51
+ function astEqual(a, b) {
52
+ if (a.type !== b.type)
53
+ return false;
54
+ if (a.type === 'symbol')
55
+ return a.name === b.name;
56
+ if (a.type === 'string')
57
+ return a.value === b.value;
58
+ if (a.type === 'number')
59
+ return a.value === b.value;
60
+ if (a.type === 'list') {
61
+ const ac = a.children || [];
62
+ const bc = b.children || [];
63
+ if (ac.length !== bc.length)
64
+ return false;
65
+ for (let i = 0; i < ac.length; i++) {
66
+ if (!astEqual(ac[i], bc[i]))
67
+ return false;
68
+ }
69
+ return true;
70
+ }
71
+ return false;
72
+ }
73
+ function getCondKey(node) {
74
+ if (node.type === 'symbol')
75
+ return node.name || '';
76
+ if (node.type === 'string' || node.type === 'number')
77
+ return String(node.value);
78
+ if (node.type === 'list' && node.children) {
79
+ return '(' + node.children.map(c => getCondKey(c)).join(' ') + ')';
80
+ }
81
+ return '';
82
+ }
83
+ function hasSelfCall(body, fnName) {
84
+ for (const form of body) {
85
+ if (form.type === 'list' && form.children && form.children.length > 0 && form.children[0].type === 'symbol' && form.children[0].name === fnName) {
86
+ return true;
87
+ }
88
+ }
89
+ return false;
90
+ }
91
+ function hasConditionalForm(body) {
92
+ for (const form of body) {
93
+ if (form.type === 'list' && form.children && form.children.length > 0 && form.children[0].type === 'symbol') {
94
+ const name = form.children[0].name;
95
+ if (name === 'if' || name === 'cond' || name === 'when' || name === 'unless') {
96
+ return true;
97
+ }
98
+ }
99
+ }
100
+ return false;
101
+ }
102
+ function defunParams(defunNode) {
103
+ if (!defunNode.children || defunNode.children.length < 3)
104
+ return [];
105
+ const paramList = defunNode.children[2];
106
+ if (!paramList.children)
107
+ return [];
108
+ const params = [];
109
+ for (const child of paramList.children) {
110
+ if (child.type === 'symbol' && child.name && child.name !== '/') {
111
+ params.push(child.name);
112
+ }
113
+ }
114
+ return params;
115
+ }
116
+ function defunLocals(defunNode) {
117
+ if (!defunNode.children || defunNode.children.length < 3)
118
+ return [];
119
+ const paramList = defunNode.children[2];
120
+ if (!paramList.children)
121
+ return [];
122
+ const locals = [];
123
+ let afterSlash = false;
124
+ for (const child of paramList.children) {
125
+ if (child.type === 'symbol' && child.name === '/') {
126
+ afterSlash = true;
127
+ continue;
128
+ }
129
+ if (afterSlash && child.type === 'symbol' && child.name) {
130
+ locals.push(child.name);
131
+ }
132
+ }
133
+ return locals;
134
+ }
135
+ function collectSymbolRefs(node, result) {
136
+ if (node.type === 'symbol' && node.name) {
137
+ result.add(node.name);
138
+ }
139
+ if (node.children) {
140
+ for (const c of node.children) {
141
+ collectSymbolRefs(c, result);
142
+ }
143
+ }
144
+ }
145
+ const higherOrderFuncs = new Set([
146
+ 'mapcar', 'apply', 'lambda', 'vl-sort', 'vl-sort-i',
147
+ 'vl-remove-if', 'vl-remove-if-not', 'vl-member-if', 'vl-some', 'vl-every',
148
+ ]);
149
+ // redundant_progn
150
+ if (checks['redundant_progn'] !== 'off') {
151
+ visitor.on('progn', node => {
152
+ if (node.children && node.children.length === 2) {
153
+ addIssue('redundant_progn', node.pos.line, (0, locale_1.t)('redundant_progn', 'progn'));
154
+ }
155
+ });
156
+ }
157
+ // redundant_setq
158
+ if (checks['redundant_setq'] !== 'off') {
159
+ visitor.on('setq', node => {
160
+ if (!node.children)
161
+ return;
162
+ for (let i = 1; i < node.children.length - 1; i += 2) {
163
+ const varNode = node.children[i];
164
+ const valNode = node.children[i + 1];
165
+ if (varNode.type === 'symbol' && valNode.type === 'symbol' && varNode.name === valNode.name) {
166
+ addIssue('redundant_setq', varNode.pos.line, (0, locale_1.t)('redundant_setq', varNode.name || ''));
167
+ }
168
+ }
169
+ });
170
+ }
171
+ // redundant_nil_else
172
+ if (checks['redundant_nil_else'] !== 'off') {
173
+ visitor.on('if', node => {
174
+ if (node.children && node.children.length === 4) {
175
+ const last = node.children[3];
176
+ if (last.type === 'symbol' && last.name === 'nil') {
177
+ addIssue('redundant_nil_else', node.pos.line, (0, locale_1.t)('redundant_nil_else'));
178
+ }
179
+ }
180
+ });
181
+ }
182
+ // single_arg_and_or
183
+ if (checks['single_arg_and_or'] !== 'off') {
184
+ const checkSingleArg = (node) => {
185
+ if (node.children && node.children.length === 2) {
186
+ const name = node.children[0].type === 'symbol' ? node.children[0].name : '';
187
+ addIssue('single_arg_and_or', node.pos.line, (0, locale_1.t)('single_arg_and_or', name || 'and/or'));
188
+ }
189
+ };
190
+ visitor.on('and', checkSingleArg);
191
+ visitor.on('or', checkSingleArg);
192
+ }
193
+ // redundant_let
194
+ if (checks['redundant_let'] !== 'off') {
195
+ visitor.on('let', node => {
196
+ if (node.children && node.children.length >= 2) {
197
+ const bindings = node.children[1];
198
+ if (bindings.type === 'list' && (!bindings.children || bindings.children.length === 0)) {
199
+ addIssue('redundant_let', node.pos.line, (0, locale_1.t)('redundant_let'));
200
+ }
201
+ }
202
+ });
203
+ }
204
+ // self_compare
205
+ if (checks['self_compare'] !== 'off') {
206
+ const compareFns = new Set(['=', '/=', '<', '>', '<=', '>=', 'eq', 'equal']);
207
+ visitor.on('*', node => {
208
+ if (node.type === 'list' && node.children && node.children.length === 3) {
209
+ const op = node.children[0];
210
+ if (op.type === 'symbol' && op.name && compareFns.has(op.name)) {
211
+ const a = node.children[1];
212
+ const b = node.children[2];
213
+ if (a.type === 'symbol' && b.type === 'symbol' && a.name === b.name) {
214
+ addIssue('self_compare', node.pos.line, (0, locale_1.t)('self_compare', `(${op.name} ${a.name} ${b.name})`));
215
+ }
216
+ }
217
+ }
218
+ });
219
+ }
220
+ // double_not
221
+ if (checks['double_not'] !== 'off') {
222
+ visitor.on('not', node => {
223
+ if (node.children && node.children.length >= 2 && node.children[1].type === 'list' &&
224
+ node.children[1].children && node.children[1].children.length > 0 &&
225
+ node.children[1].children[0].type === 'symbol' && node.children[1].children[0].name === 'not') {
226
+ addIssue('double_not', node.pos.line, (0, locale_1.t)('double_not'));
227
+ }
228
+ });
229
+ }
230
+ // setq_single_arg
231
+ if (checks['setq_single_arg'] !== 'off') {
232
+ visitor.on('setq', node => {
233
+ if (node.children && node.children.length === 2) {
234
+ const varName = node.children[1].type === 'symbol' ? node.children[1].name : '';
235
+ addIssue('setq_single_arg', node.pos.line, (0, locale_1.t)('setq_single_arg', varName || ''));
236
+ }
237
+ });
238
+ }
239
+ // empty_branch
240
+ if (checks['empty_branch'] !== 'off') {
241
+ visitor.on('if', node => {
242
+ if (node.children && node.children.length < 3) {
243
+ addIssue('empty_branch', node.pos.line, (0, locale_1.t)('empty_branch', 'if'));
244
+ }
245
+ });
246
+ }
247
+ // misplaced_else
248
+ if (checks['misplaced_else'] !== 'off') {
249
+ visitor.on('if', node => {
250
+ if (node.children && node.children.length === 4) {
251
+ const cond = node.children[1];
252
+ if (cond.type === 'list' && cond.children && cond.children.length >= 2 &&
253
+ cond.children[0].type === 'symbol' && cond.children[0].name === 'not') {
254
+ const innerName = cond.children[1].type === 'symbol' ? (cond.children[1].name || '') : '';
255
+ addIssue('misplaced_else', node.pos.line, (0, locale_1.t)('misplaced_else', innerName));
256
+ }
257
+ }
258
+ });
259
+ }
260
+ // while_constant
261
+ if (checks['while_constant'] !== 'off') {
262
+ visitor.on('while', node => {
263
+ if (node.children && node.children.length >= 2) {
264
+ const cond = node.children[1];
265
+ if (isLiteral(cond)) {
266
+ const name = cond.type === 'symbol' ? (cond.name || '') : String(cond.value ?? '');
267
+ addIssue('while_constant', node.pos.line, (0, locale_1.t)('while_constant', name));
268
+ }
269
+ }
270
+ });
271
+ }
272
+ // constant_condition
273
+ if (checks['constant_condition'] !== 'off') {
274
+ const checkConstant = (node, formName) => {
275
+ if (node.children && node.children.length >= 2) {
276
+ const cond = node.children[1];
277
+ if (isLiteral(cond)) {
278
+ const name = cond.type === 'symbol' ? (cond.name || '') : String(cond.value ?? '');
279
+ addIssue('constant_condition', node.pos.line, (0, locale_1.t)('constant_condition', name, formName));
280
+ }
281
+ }
282
+ };
283
+ visitor.on('if', node => checkConstant(node, 'if'));
284
+ visitor.on('while', node => checkConstant(node, 'while'));
285
+ visitor.on('cond', node => {
286
+ if (node.children && node.children.length >= 2) {
287
+ const clause = node.children[1];
288
+ if (clause.type === 'list' && clause.children && clause.children.length >= 2) {
289
+ const test = clause.children[1];
290
+ if (isLiteral(test)) {
291
+ const name = test.type === 'symbol' ? (test.name || '') : String(test.value ?? '');
292
+ addIssue('constant_condition', node.pos.line, (0, locale_1.t)('constant_condition', name, 'cond'));
293
+ }
294
+ }
295
+ }
296
+ });
297
+ }
298
+ // quote_vs_function
299
+ if (checks['quote_vs_function'] !== 'off') {
300
+ visitor.on('quote', node => {
301
+ if (node.children && node.children.length >= 2 && node.children[1].type === 'list' &&
302
+ node.children[1].children && node.children[1].children.length > 0 &&
303
+ node.children[1].children[0].type === 'symbol' && node.children[1].children[0].name === 'lambda') {
304
+ const parent = node.parent;
305
+ if (parent && parent.children && parent.children.length > 0) {
306
+ const pName = (parent.children[0].type === 'symbol' && parent.children[0].name) || '';
307
+ if (higherOrderFuncs.has(pName)) {
308
+ addIssue('quote_vs_function', node.pos.line, (0, locale_1.t)('quote_vs_function', pName));
309
+ }
310
+ }
311
+ }
312
+ });
313
+ }
314
+ // redundant_quotes
315
+ if (checks['redundant_quotes'] !== 'off') {
316
+ visitor.on('quote', node => {
317
+ if (node.children && node.children.length >= 2 && node.children[1].type === 'list' &&
318
+ node.children[1].children && node.children[1].children.length > 0 &&
319
+ node.children[1].children[0].type === 'symbol' && node.children[1].children[0].name === 'quote') {
320
+ addIssue('redundant_quotes', node.pos.line, (0, locale_1.t)('redundant_quotes'));
321
+ }
322
+ });
323
+ }
324
+ // parameter_naming
325
+ if (checks['parameter_naming'] !== 'off') {
326
+ visitor.on('defun', node => {
327
+ if (!node.children || node.children.length < 3)
328
+ return;
329
+ const paramList = node.children[2];
330
+ if (!paramList.children)
331
+ return;
332
+ const fnName = node.children[1].type === 'symbol' && node.children[1].name ? node.children[1].name : '';
333
+ for (const child of paramList.children) {
334
+ if (child.type !== 'symbol' || !child.name)
335
+ continue;
336
+ if (/^[A-Z]/.test(child.name) && child.name !== 'T' && child.name !== 'nil') {
337
+ addIssue('parameter_naming', child.pos.line, (0, locale_1.t)('parameter_naming', child.name, fnName));
338
+ }
339
+ }
340
+ });
341
+ }
342
+ // assoc_without_cdr
343
+ if (checks['assoc_without_cdr'] !== 'off') {
344
+ visitor.on('assoc', node => {
345
+ const parent = node.parent;
346
+ if (!parent) {
347
+ addIssue('assoc_without_cdr', node.pos.line, (0, locale_1.t)('assoc_without_cdr'));
348
+ return;
349
+ }
350
+ if (parent.type === 'list' && parent.children && parent.children.length > 0 &&
351
+ parent.children[0].type === 'symbol' && (parent.children[0].name === 'cdr' || parent.children[0].name === 'cadr')) {
352
+ return;
353
+ }
354
+ addIssue('assoc_without_cdr', node.pos.line, (0, locale_1.t)('assoc_without_cdr'));
355
+ });
356
+ }
357
+ // identical_branches
358
+ if (checks['identical_branches'] !== 'off') {
359
+ visitor.on('if', node => {
360
+ if (node.children && node.children.length === 4) {
361
+ const thenBranch = node.children[2];
362
+ const elseBranch = node.children[3];
363
+ if (astEqual(thenBranch, elseBranch)) {
364
+ addIssue('identical_branches', node.pos.line, (0, locale_1.t)('identical_branches'));
365
+ }
366
+ }
367
+ });
368
+ }
369
+ // open_without_close (data collection)
370
+ if (checks['open_without_close'] !== 'off') {
371
+ visitor.on('open', () => { state.openCount++; });
372
+ visitor.on('close', () => { state.closeCount++; });
373
+ }
374
+ // cond_duplicate (data collection)
375
+ if (checks['cond_duplicate'] !== 'off') {
376
+ visitor.on('cond', node => {
377
+ if (!node.children)
378
+ return;
379
+ const seen = new Set();
380
+ for (let i = 1; i < node.children.length; i++) {
381
+ const clause = node.children[i];
382
+ if (clause.type !== 'list' || !clause.children || clause.children.length < 2)
383
+ continue;
384
+ const condTest = clause.children[1];
385
+ const key = getCondKey(condTest);
386
+ if (seen.has(key)) {
387
+ addIssue('cond_duplicate', clause.pos.line, (0, locale_1.t)('cond_duplicate'));
388
+ }
389
+ seen.add(key);
390
+ }
391
+ });
392
+ }
393
+ // dangerous_calls (data collection) - skip if in quote
394
+ if (checks['quit_exit'] !== 'off') {
395
+ const dangerNames = new Set();
396
+ const dc = config.dangerous_calls;
397
+ if (dc.quit !== 'off')
398
+ dangerNames.add('quit');
399
+ if (dc.exit !== 'off')
400
+ dangerNames.add('exit');
401
+ if (dc.startapp !== 'off')
402
+ dangerNames.add('startapp');
403
+ if (dc.vl_registry_write !== 'off')
404
+ dangerNames.add('vl-registry-write');
405
+ if (dangerNames.size > 0) {
406
+ visitor.on('*', node => {
407
+ if (node.type === 'list' && node.children && node.children.length > 0 &&
408
+ node.children[0].type === 'symbol' && node.children[0].name && dangerNames.has(node.children[0].name)) {
409
+ if (!isInQuote(node)) {
410
+ state.dangerousCalls.push({ name: node.children[0].name, node });
411
+ }
412
+ }
413
+ });
414
+ }
415
+ }
416
+ // multiple_setq (data collection via wildcard)
417
+ if (checks['multiple_setq'] !== 'off') {
418
+ visitor.on('*', node => {
419
+ if (node.type === 'list' && node.children) {
420
+ let prevSetq = false;
421
+ for (const child of node.children) {
422
+ if (child.type === 'list' && child.children && child.children.length > 0 &&
423
+ child.children[0].type === 'symbol' && child.children[0].name === 'setq') {
424
+ if (prevSetq) {
425
+ addIssue('multiple_setq', child.pos.line, (0, locale_1.t)('multiple_setq'));
426
+ prevSetq = false;
427
+ }
428
+ else {
429
+ prevSetq = true;
430
+ }
431
+ }
432
+ else {
433
+ prevSetq = false;
434
+ }
435
+ }
436
+ }
437
+ });
438
+ }
439
+ // recursive_call (data collection)
440
+ if (checks['recursive_call'] !== 'off') {
441
+ visitor.on('defun', node => {
442
+ if (!node.children || node.children.length < 4)
443
+ return;
444
+ const fnName = node.children[1].type === 'symbol' ? node.children[1].name : '';
445
+ if (!fnName)
446
+ return;
447
+ const body = node.children.slice(3);
448
+ if (hasSelfCall(body, fnName) && !hasConditionalForm(body)) {
449
+ addIssue('recursive_call', node.pos.line, (0, locale_1.t)('recursive_call', fnName));
450
+ }
451
+ });
452
+ }
453
+ // variable_shadow (data collection)
454
+ if (checks['variable_shadow'] !== 'off') {
455
+ visitor.on('let', node => {
456
+ if (!node.children || node.children.length < 2)
457
+ return;
458
+ const bindings = node.children[1];
459
+ if (bindings.type !== 'list' || !bindings.children)
460
+ return;
461
+ const letVars = new Set();
462
+ for (const b of bindings.children) {
463
+ if (b.type === 'list' && b.children && b.children.length > 0 && b.children[0].type === 'symbol' && b.children[0].name) {
464
+ letVars.add(b.children[0].name);
465
+ }
466
+ }
467
+ // Walk body for setq of let-bound vars (shallow, don't descend into nested lets)
468
+ const walkBodyForSetq = (forms) => {
469
+ for (const form of forms) {
470
+ if (form.type === 'list' && form.children && form.children.length > 0 &&
471
+ form.children[0].type === 'symbol') {
472
+ if (form.children[0].name === 'setq' && form.children.length > 1) {
473
+ for (let i = 1; i < form.children.length; i += 2) {
474
+ const child = form.children[i];
475
+ if (child.type !== 'symbol' || !child.name)
476
+ continue;
477
+ if (letVars.has(child.name)) {
478
+ addIssue('variable_shadow', child.pos.line, (0, locale_1.t)('variable_shadow', child.name));
479
+ }
480
+ }
481
+ }
482
+ else if (form.children[0].name !== 'let') {
483
+ // Don't descend into nested lets
484
+ if (form.children) {
485
+ walkBodyForSetq(form.children.slice(1));
486
+ }
487
+ }
488
+ }
489
+ }
490
+ };
491
+ walkBodyForSetq(node.children.slice(2));
492
+ });
493
+ }
494
+ // unused_let_binding (data collection)
495
+ if (checks['unused_let_binding'] !== 'off') {
496
+ visitor.on('let', node => {
497
+ if (!node.children || node.children.length < 2)
498
+ return;
499
+ const bindings = node.children[1];
500
+ if (bindings.type !== 'list' || !bindings.children)
501
+ return;
502
+ const letVars = [];
503
+ for (const b of bindings.children) {
504
+ if (b.type === 'list' && b.children && b.children.length > 0 && b.children[0].type === 'symbol' && b.children[0].name) {
505
+ letVars.push({ name: b.children[0].name, line: b.children[0].pos.line });
506
+ }
507
+ }
508
+ if (letVars.length === 0)
509
+ return;
510
+ const bodyRefs = new Set();
511
+ for (const form of node.children.slice(2)) {
512
+ collectSymbolRefs(form, bodyRefs);
513
+ }
514
+ for (const v of letVars) {
515
+ if (!bodyRefs.has(v.name)) {
516
+ addIssue('unused_let_binding', v.line, (0, locale_1.t)('unused_let_binding', v.name));
517
+ }
518
+ }
519
+ });
520
+ }
521
+ // defun-based unused checks (unused_parameter, unused_local_fun) - data collection
522
+ if (checks['unused_parameter'] !== 'off' || checks['unused_local_fun'] !== 'off') {
523
+ visitor.on('defun', node => {
524
+ if (!node.children || node.children.length < 4)
525
+ return;
526
+ const fnName = node.children[1].type === 'symbol' ? node.children[1].name : '';
527
+ if (!fnName || (fnName.startsWith('C:') || fnName.startsWith('c:')))
528
+ return;
529
+ const body = node.children.slice(3);
530
+ if (checks['unused_parameter'] !== 'off') {
531
+ const params = defunParams(node);
532
+ if (params.length > 0) {
533
+ const bodyRefs = new Set();
534
+ for (const form of body) {
535
+ collectSymbolRefs(form, bodyRefs);
536
+ }
537
+ for (const p of params) {
538
+ if (!bodyRefs.has(p) && p !== fnName) {
539
+ addIssue('unused_parameter', node.pos.line, (0, locale_1.t)('unused_parameter', p, fnName));
540
+ }
541
+ }
542
+ }
543
+ }
544
+ if (checks['unused_local_fun'] !== 'off') {
545
+ const locals = defunLocals(node);
546
+ if (locals.length > 0) {
547
+ const bodyRefs = new Set();
548
+ for (const form of body) {
549
+ collectSymbolRefs(form, bodyRefs);
550
+ }
551
+ for (const l of locals) {
552
+ if (!bodyRefs.has(l)) {
553
+ addIssue('unused_local_fun', node.pos.line, (0, locale_1.t)('unused_local_fun', l));
554
+ }
555
+ }
556
+ }
557
+ }
558
+ });
559
+ }
560
+ // unused_variable (data collection)
561
+ if (checks['unused_variable'] !== 'off') {
562
+ visitor.on('setq', node => {
563
+ if (!node.children)
564
+ return;
565
+ for (let i = 1; i < node.children.length; i += 2) {
566
+ const varNode = node.children[i];
567
+ if (varNode.type === 'symbol' && varNode.name && !varNode.name.startsWith('*') && !varNode.name.includes(':*') && varNode.name !== 'T' && varNode.name !== 'nil' && !varNode.name.startsWith('/')) {
568
+ state.setqDefs.set(varNode.name, { line: varNode.pos.line, file });
569
+ }
570
+ }
571
+ });
572
+ }
573
+ // Collect all symbol references for unused_variable
574
+ if (checks['unused_variable'] !== 'off') {
575
+ visitor.on('*', node => {
576
+ if (node.type === 'symbol' && node.name) {
577
+ state.symbolRefs.add(node.name);
578
+ }
579
+ });
580
+ }
581
+ // function_complexity (data collection)
582
+ if (checks['function_complexity'] !== 'off') {
583
+ visitor.on('defun', node => {
584
+ if (node.children && node.children.length >= 3) {
585
+ const fnName = (node.children[1].type === 'symbol' ? node.children[1].name : '<anonymous>') || '<anonymous>';
586
+ state.defunScopes.push({
587
+ name: fnName,
588
+ params: [],
589
+ body: node.children.slice(3),
590
+ line: node.pos.line,
591
+ file,
592
+ });
593
+ }
594
+ });
595
+ }
596
+ // if_without_else
597
+ if (checks['if_without_else'] !== 'off') {
598
+ visitor.on('if', node => {
599
+ if (node.children && node.children.length === 3) {
600
+ const parent = node.parent;
601
+ if (parent && parent.type === 'list' && parent.children && parent.children.length > 0 &&
602
+ parent.children[0].type === 'symbol') {
603
+ const parentName = parent.children[0].name;
604
+ if (parentName === 'cond' || parentName === 'if')
605
+ return;
606
+ }
607
+ addIssue('if_without_else', node.pos.line, (0, locale_1.t)('if_without_else'));
608
+ }
609
+ });
610
+ }
611
+ // redundant_list
612
+ if (checks['redundant_list'] !== 'off') {
613
+ visitor.on('list', node => {
614
+ if (node.children && node.children.length === 1) {
615
+ addIssue('redundant_list', node.pos.line, (0, locale_1.t)('redundant_list'));
616
+ }
617
+ });
618
+ }
619
+ // promise_handler
620
+ if (checks['promise_handler'] !== 'off') {
621
+ visitor.on('*', node => {
622
+ if (node.type === 'list' && node.children && node.children.length > 0 &&
623
+ node.children[0].type === 'symbol' && node.children[0].name &&
624
+ (node.children[0].name.startsWith('vlax-invoke') || node.children[0].name.startsWith('vlax-invoke-method'))) {
625
+ if (isInQuote(node))
626
+ return;
627
+ const parent = node.parent;
628
+ if (parent && parent.type === 'list' && parent.children && parent.children.length > 0 &&
629
+ parent.children[0].type === 'symbol' && parent.children[0].name === 'vl-catch-all-apply') {
630
+ return;
631
+ }
632
+ let hasCallback = false;
633
+ for (const child of (node.children || []).slice(1)) {
634
+ if (child.type === 'list' && child.children && child.children.length > 0 &&
635
+ child.children[0].type === 'symbol' &&
636
+ (child.children[0].name === 'lambda' || child.children[0].name === 'function')) {
637
+ hasCallback = true;
638
+ break;
639
+ }
640
+ }
641
+ if (!hasCallback) {
642
+ addIssue('promise_handler', node.pos.line, (0, locale_1.t)('promise_handler'));
643
+ }
644
+ }
645
+ });
646
+ }
647
+ visitor.run(ast);
648
+ // ===== POST-PROCESSING =====
649
+ // open_without_close
650
+ if (checks['open_without_close'] !== 'off') {
651
+ if (state.openCount > state.closeCount) {
652
+ addIssue('open_without_close', 1, (0, locale_1.t)('open_without_close', String(state.openCount), String(state.closeCount)));
653
+ }
654
+ }
655
+ // dangerous_calls
656
+ if (checks['quit_exit'] !== 'off') {
657
+ const dc = config.dangerous_calls;
658
+ for (const call of state.dangerousCalls) {
659
+ const sev = config.dangerous_calls[call.name === 'vl-registry-write' ? 'vl_registry_write' : call.name] || 'off';
660
+ if (sev === 'off')
661
+ continue;
662
+ const msgKey = call.name === 'quit' ? 'dangerous.quit' :
663
+ call.name === 'exit' ? 'dangerous.exit' :
664
+ call.name === 'startapp' ? 'dangerous.startapp' :
665
+ call.name === 'vl-registry-write' ? 'dangerous.vl_registry_write' : '';
666
+ if (msgKey) {
667
+ addIssue(call.name === 'vl-registry-write' ? 'vl_registry_write' : call.name, call.node.pos.line, (0, locale_1.t)(msgKey));
668
+ }
669
+ }
670
+ }
671
+ // unused_variable
672
+ if (checks['unused_variable'] !== 'off') {
673
+ for (const [name, def] of state.setqDefs) {
674
+ if (state.setqDefs.size > 1) {
675
+ let refCount = 0;
676
+ for (const ref of state.symbolRefs) {
677
+ if (ref === name)
678
+ refCount++;
679
+ }
680
+ if (refCount <= 1) {
681
+ addIssue('unused_variable', def.line, (0, locale_1.t)('unused_variable', name));
682
+ }
683
+ }
684
+ }
685
+ }
686
+ // function_complexity
687
+ if (checks['function_complexity'] !== 'off') {
688
+ const maxLines = config.function_complexity.max_lines;
689
+ const maxNesting = config.function_complexity.max_nesting;
690
+ for (const scope of state.defunScopes) {
691
+ let maxLine = scope.line;
692
+ const computeEnd = (nodes) => {
693
+ for (const n of nodes) {
694
+ if (n.end && n.end.line > maxLine)
695
+ maxLine = n.end.line;
696
+ if (n.children)
697
+ computeEnd(n.children);
698
+ }
699
+ };
700
+ computeEnd(scope.body);
701
+ const lineCount = maxLine - scope.line + 1;
702
+ if (lineCount > maxLines) {
703
+ addIssue('function_complexity', scope.line, (0, locale_1.t)('function_complexity.lines', scope.name, String(lineCount), String(maxLines)));
704
+ }
705
+ }
706
+ }
707
+ return issues;
708
+ }
709
+ //# sourceMappingURL=visitor-runner.js.map