@atlisp/lint 0.2.12 → 0.2.13
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.
- package/atlisp-lint.schema.json +27 -4
- package/dist/cache.js +19 -9
- package/dist/checks/builtin-arg-count.d.ts +1 -0
- package/dist/checks/builtin-arg-count.js +5 -0
- package/dist/checks/dangerous-calls.js +1 -0
- package/dist/checks/global-function-naming.js +159 -35
- package/dist/checks/missing-princ.js +2 -2
- package/dist/checks/nth-usage.d.ts +1 -0
- package/dist/checks/nth-usage.js +16 -1
- package/dist/cli/collector.d.ts +7 -0
- package/dist/cli/collector.js +154 -0
- package/dist/cli/parser.d.ts +30 -0
- package/dist/cli/parser.js +119 -0
- package/dist/config.js +28 -6
- package/dist/disable.js +12 -0
- package/dist/fixes/index.d.ts +8 -0
- package/dist/fixes/index.js +200 -0
- package/dist/index.js +56 -236
- package/dist/locale.js +30 -2
- package/dist/presets.js +24 -0
- package/dist/project.js +64 -34
- package/dist/rules.d.ts +1 -0
- package/dist/rules.js +85 -0
- package/dist/runner.d.ts +2 -3
- package/dist/runner.js +46 -371
- package/dist/types.d.ts +1 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +43 -0
- package/dist/validate.js +12 -0
- package/dist/visitor-runner.d.ts +28 -0
- package/dist/visitor-runner.js +115 -1
- package/dist/visitors/equal-nil.d.ts +8 -0
- package/dist/visitors/equal-nil.js +26 -0
- package/dist/visitors/index.d.ts +5 -0
- package/dist/visitors/index.js +14 -0
- package/dist/visitors/progn-in-cond.d.ts +8 -0
- package/dist/visitors/progn-in-cond.js +33 -0
- package/dist/visitors/redundant-and-or.d.ts +8 -0
- package/dist/visitors/redundant-and-or.js +27 -0
- package/dist/visitors/redundant-car-cdr.d.ts +8 -0
- package/dist/visitors/redundant-car-cdr.js +35 -0
- package/dist/visitors/types.d.ts +13 -0
- package/dist/visitors/types.js +72 -0
- package/package.json +1 -1
package/dist/visitor-runner.d.ts
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
import { Issue, LintConfig } from './types';
|
|
2
2
|
import { AstNode } from '@atlisp/parser';
|
|
3
|
+
export interface CollectState {
|
|
4
|
+
openCount: number;
|
|
5
|
+
closeCount: number;
|
|
6
|
+
setqDefs: Map<string, {
|
|
7
|
+
line: number;
|
|
8
|
+
file: string;
|
|
9
|
+
}>;
|
|
10
|
+
symbolRefs: Map<string, number>;
|
|
11
|
+
condKeys: Map<number, Set<string>>;
|
|
12
|
+
defunScopes: Array<{
|
|
13
|
+
name: string;
|
|
14
|
+
params: string[];
|
|
15
|
+
body: AstNode[];
|
|
16
|
+
line: number;
|
|
17
|
+
file: string;
|
|
18
|
+
}>;
|
|
19
|
+
letScopes: Array<{
|
|
20
|
+
bindings: string[];
|
|
21
|
+
body: AstNode[];
|
|
22
|
+
line: number;
|
|
23
|
+
}>;
|
|
24
|
+
dangerousCalls: Array<{
|
|
25
|
+
name: string;
|
|
26
|
+
node: AstNode;
|
|
27
|
+
}>;
|
|
28
|
+
ssgetCount: number;
|
|
29
|
+
sssetCount: number;
|
|
30
|
+
}
|
|
3
31
|
export declare function runChecksWithVisitor(ast: AstNode, file: string, config: LintConfig, disableMap: Map<string, Set<number>>): Issue[];
|
|
4
32
|
//# sourceMappingURL=visitor-runner.d.ts.map
|
package/dist/visitor-runner.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.runChecksWithVisitor = runChecksWithVisitor;
|
|
|
4
4
|
const locale_1 = require("./locale");
|
|
5
5
|
const disable_1 = require("./disable");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
|
+
const index_1 = require("./visitors/index");
|
|
7
8
|
function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
8
9
|
const issues = [];
|
|
9
10
|
const checks = config.checks;
|
|
@@ -17,6 +18,8 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
17
18
|
defunScopes: [],
|
|
18
19
|
letScopes: [],
|
|
19
20
|
dangerousCalls: [],
|
|
21
|
+
ssgetCount: 0,
|
|
22
|
+
sssetCount: 0,
|
|
20
23
|
};
|
|
21
24
|
function addIssue(rule, line, message) {
|
|
22
25
|
const severity = checks[rule];
|
|
@@ -435,6 +438,8 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
435
438
|
dangerNames.add('startapp');
|
|
436
439
|
if (dc.vl_registry_write !== 'off')
|
|
437
440
|
dangerNames.add('vl-registry-write');
|
|
441
|
+
if (dc.eval !== 'off')
|
|
442
|
+
dangerNames.add('eval');
|
|
438
443
|
if (dangerNames.size > 0) {
|
|
439
444
|
visitor.on('*', node => {
|
|
440
445
|
if (node.type === 'list' &&
|
|
@@ -695,6 +700,106 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
695
700
|
}
|
|
696
701
|
});
|
|
697
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
|
+
}
|
|
698
803
|
// promise_handler
|
|
699
804
|
if (checks['promise_handler'] !== 'off') {
|
|
700
805
|
visitor.on('*', node => {
|
|
@@ -732,6 +837,7 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
732
837
|
}
|
|
733
838
|
});
|
|
734
839
|
}
|
|
840
|
+
(0, index_1.registerAll)(visitor, state, checks, addIssue);
|
|
735
841
|
visitor.run(ast);
|
|
736
842
|
// ===== POST-PROCESSING =====
|
|
737
843
|
// open_without_close
|
|
@@ -754,12 +860,20 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
754
860
|
? 'dangerous.startapp'
|
|
755
861
|
: call.name === 'vl-registry-write'
|
|
756
862
|
? 'dangerous.vl_registry_write'
|
|
757
|
-
: ''
|
|
863
|
+
: call.name === 'eval'
|
|
864
|
+
? 'dangerous.eval'
|
|
865
|
+
: '';
|
|
758
866
|
if (msgKey) {
|
|
759
867
|
addIssue(call.name === 'vl-registry-write' ? 'vl_registry_write' : call.name, call.node.pos.line, (0, locale_1.t)(msgKey));
|
|
760
868
|
}
|
|
761
869
|
}
|
|
762
870
|
}
|
|
871
|
+
// selection_set_leak
|
|
872
|
+
if (checks['selection_set_leak'] !== 'off') {
|
|
873
|
+
if (state.ssgetCount > state.sssetCount) {
|
|
874
|
+
addIssue('selection_set_leak', 1, (0, locale_1.t)('selection_set_leak', String(state.ssgetCount), String(state.sssetCount)));
|
|
875
|
+
}
|
|
876
|
+
}
|
|
763
877
|
// unused_variable
|
|
764
878
|
if (checks['unused_variable'] !== 'off') {
|
|
765
879
|
for (const [name, def] of state.setqDefs) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AstVisitor } from '@atlisp/parser';
|
|
2
|
+
import { CollectState } from '../visitor-runner';
|
|
3
|
+
import { AddIssueFn } from './types';
|
|
4
|
+
export declare const equalNilModule: {
|
|
5
|
+
name: string;
|
|
6
|
+
register(visitor: AstVisitor, state: CollectState, checks: Record<string, string>, addIssue: AddIssueFn): void;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=equal-nil.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.equalNilModule = void 0;
|
|
4
|
+
const types_1 = require("./types");
|
|
5
|
+
exports.equalNilModule = {
|
|
6
|
+
name: 'equal_nil',
|
|
7
|
+
register(visitor, state, checks, addIssue) {
|
|
8
|
+
if (checks['equal_nil'] === 'off')
|
|
9
|
+
return;
|
|
10
|
+
const checkEqualNil = (node) => {
|
|
11
|
+
if (!node.children || node.children.length < 3)
|
|
12
|
+
return;
|
|
13
|
+
if ((0, types_1.isInQuote)(node))
|
|
14
|
+
return;
|
|
15
|
+
const a = node.children[1];
|
|
16
|
+
const b = node.children[2];
|
|
17
|
+
const isNilSym = (n) => n.type === 'symbol' && (n.name === 'nil' || n.name === 'NIL');
|
|
18
|
+
const varName = a.type === 'symbol' && isNilSym(b) ? a.name || '' : b.type === 'symbol' && isNilSym(a) ? b.name || '' : '';
|
|
19
|
+
if (varName) {
|
|
20
|
+
addIssue('equal_nil', node.pos.line, `(equal ${varName} nil) — use (null ${varName}) or (not ${varName})`);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
visitor.on('equal', checkEqualNil);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=equal-nil.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { AstVisitor } from '@atlisp/parser';
|
|
2
|
+
import { CollectState } from '../visitor-runner';
|
|
3
|
+
import { AddIssueFn } from './types';
|
|
4
|
+
export declare function registerAll(visitor: AstVisitor, state: CollectState, checks: Record<string, string>, addIssue: AddIssueFn): void;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerAll = registerAll;
|
|
4
|
+
const progn_in_cond_1 = require("./progn-in-cond");
|
|
5
|
+
const redundant_car_cdr_1 = require("./redundant-car-cdr");
|
|
6
|
+
const equal_nil_1 = require("./equal-nil");
|
|
7
|
+
const redundant_and_or_1 = require("./redundant-and-or");
|
|
8
|
+
const modules = [progn_in_cond_1.prognInCondModule, redundant_car_cdr_1.redundantCarCdrModule, equal_nil_1.equalNilModule, redundant_and_or_1.redundantAndOrModule];
|
|
9
|
+
function registerAll(visitor, state, checks, addIssue) {
|
|
10
|
+
for (const mod of modules) {
|
|
11
|
+
mod.register(visitor, state, checks, addIssue);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AstVisitor } from '@atlisp/parser';
|
|
2
|
+
import { CollectState } from '../visitor-runner';
|
|
3
|
+
import { AddIssueFn } from './types';
|
|
4
|
+
export declare const prognInCondModule: {
|
|
5
|
+
name: string;
|
|
6
|
+
register(visitor: AstVisitor, state: CollectState, checks: Record<string, string>, addIssue: AddIssueFn): void;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=progn-in-cond.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prognInCondModule = void 0;
|
|
4
|
+
const types_1 = require("./types");
|
|
5
|
+
exports.prognInCondModule = {
|
|
6
|
+
name: 'progn_in_cond',
|
|
7
|
+
register(visitor, state, checks, addIssue) {
|
|
8
|
+
if (checks['progn_in_cond'] === 'off')
|
|
9
|
+
return;
|
|
10
|
+
visitor.on('cond', (node) => {
|
|
11
|
+
if (!node.children)
|
|
12
|
+
return;
|
|
13
|
+
for (let i = 1; i < node.children.length; i++) {
|
|
14
|
+
const clause = node.children[i];
|
|
15
|
+
if (clause.type !== 'list' || !clause.children || clause.children.length < 2)
|
|
16
|
+
continue;
|
|
17
|
+
for (let j = 1; j < clause.children.length; j++) {
|
|
18
|
+
const form = clause.children[j];
|
|
19
|
+
if (form.type === 'list' &&
|
|
20
|
+
form.children &&
|
|
21
|
+
form.children.length > 0 &&
|
|
22
|
+
form.children[0].type === 'symbol' &&
|
|
23
|
+
form.children[0].name === 'progn') {
|
|
24
|
+
if ((0, types_1.isInQuote)(form))
|
|
25
|
+
continue;
|
|
26
|
+
addIssue('progn_in_cond', form.pos.line, 'cond clause with progn — cond already allows multiple forms');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=progn-in-cond.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AstVisitor } from '@atlisp/parser';
|
|
2
|
+
import { CollectState } from '../visitor-runner';
|
|
3
|
+
import { AddIssueFn } from './types';
|
|
4
|
+
export declare const redundantAndOrModule: {
|
|
5
|
+
name: string;
|
|
6
|
+
register(visitor: AstVisitor, state: CollectState, checks: Record<string, string>, addIssue: AddIssueFn): void;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=redundant-and-or.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.redundantAndOrModule = void 0;
|
|
4
|
+
const types_1 = require("./types");
|
|
5
|
+
exports.redundantAndOrModule = {
|
|
6
|
+
name: 'redundant_and_or',
|
|
7
|
+
register(visitor, state, checks, addIssue) {
|
|
8
|
+
if (checks['redundant_and_or'] === 'off')
|
|
9
|
+
return;
|
|
10
|
+
visitor.on('*', (node) => {
|
|
11
|
+
if (node.type !== 'list' || !node.children || node.children.length !== 1)
|
|
12
|
+
return;
|
|
13
|
+
const fn = node.children[0];
|
|
14
|
+
if (fn.type !== 'symbol')
|
|
15
|
+
return;
|
|
16
|
+
if ((0, types_1.isInQuote)(node))
|
|
17
|
+
return;
|
|
18
|
+
if (fn.name === 'and') {
|
|
19
|
+
addIssue('redundant_and_or', node.pos.line, '(and) always returns T — may be leftover code');
|
|
20
|
+
}
|
|
21
|
+
else if (fn.name === 'or') {
|
|
22
|
+
addIssue('redundant_and_or', node.pos.line, '(or) always returns nil — may be leftover code');
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=redundant-and-or.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AstVisitor } from '@atlisp/parser';
|
|
2
|
+
import { CollectState } from '../visitor-runner';
|
|
3
|
+
import { AddIssueFn } from './types';
|
|
4
|
+
export declare const redundantCarCdrModule: {
|
|
5
|
+
name: string;
|
|
6
|
+
register(visitor: AstVisitor, state: CollectState, checks: Record<string, string>, addIssue: AddIssueFn): void;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=redundant-car-cdr.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.redundantCarCdrModule = void 0;
|
|
4
|
+
const types_1 = require("./types");
|
|
5
|
+
const CAR_CDR_MAP = {
|
|
6
|
+
car: { car: 'caar', cdr: 'cadr' },
|
|
7
|
+
cdr: { car: 'cdar', cdr: 'cddr' },
|
|
8
|
+
};
|
|
9
|
+
exports.redundantCarCdrModule = {
|
|
10
|
+
name: 'redundant_car_cdr',
|
|
11
|
+
register(visitor, state, checks, addIssue) {
|
|
12
|
+
if (checks['redundant_car_cdr'] === 'off')
|
|
13
|
+
return;
|
|
14
|
+
visitor.on('*', (node) => {
|
|
15
|
+
if (node.type !== 'list' || !node.children || node.children.length !== 2)
|
|
16
|
+
return;
|
|
17
|
+
const outer = node.children[0];
|
|
18
|
+
if (outer.type !== 'symbol' || !outer.name)
|
|
19
|
+
return;
|
|
20
|
+
const inner = node.children[1];
|
|
21
|
+
if (inner.type !== 'list' || !inner.children || inner.children.length !== 2)
|
|
22
|
+
return;
|
|
23
|
+
const innerFn = inner.children[0];
|
|
24
|
+
if (innerFn.type !== 'symbol' || !innerFn.name)
|
|
25
|
+
return;
|
|
26
|
+
const replacement = CAR_CDR_MAP[outer.name]?.[innerFn.name];
|
|
27
|
+
if (!replacement)
|
|
28
|
+
return;
|
|
29
|
+
if ((0, types_1.isInQuote)(node))
|
|
30
|
+
return;
|
|
31
|
+
addIssue('redundant_car_cdr', node.pos.line, `(${outer.name} (${innerFn.name} x)) → (${replacement} x)`);
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=redundant-car-cdr.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AstNode, AstVisitor } from '@atlisp/parser';
|
|
2
|
+
import { CollectState } from '../visitor-runner';
|
|
3
|
+
export type AddIssueFn = (rule: string, line: number, message: string) => void;
|
|
4
|
+
export interface VisitorModule {
|
|
5
|
+
name: string;
|
|
6
|
+
register: (visitor: AstVisitor, state: CollectState, checks: Record<string, string>, addIssue: AddIssueFn) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function isInQuote(node: AstNode): boolean;
|
|
9
|
+
export declare function isLiteral(node: AstNode): boolean;
|
|
10
|
+
export declare function astEqual(a: AstNode, b: AstNode): boolean;
|
|
11
|
+
export declare function getCondKey(node: AstNode): string;
|
|
12
|
+
export declare function hasConditionalForm(body: AstNode[]): boolean;
|
|
13
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isInQuote = isInQuote;
|
|
4
|
+
exports.isLiteral = isLiteral;
|
|
5
|
+
exports.astEqual = astEqual;
|
|
6
|
+
exports.getCondKey = getCondKey;
|
|
7
|
+
exports.hasConditionalForm = hasConditionalForm;
|
|
8
|
+
function isInQuote(node) {
|
|
9
|
+
let p = node.parent;
|
|
10
|
+
while (p) {
|
|
11
|
+
if (p.type === 'list' &&
|
|
12
|
+
p.children &&
|
|
13
|
+
p.children.length > 0 &&
|
|
14
|
+
p.children[0].type === 'symbol' &&
|
|
15
|
+
(p.children[0].name === 'quote' || p.children[0].name === 'function')) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
p = p.parent;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
function isLiteral(node) {
|
|
23
|
+
if (node.type === 'symbol' && (node.name === 'T' || node.name === 't' || node.name === 'nil'))
|
|
24
|
+
return true;
|
|
25
|
+
if (node.type === 'number')
|
|
26
|
+
return true;
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
function astEqual(a, b) {
|
|
30
|
+
if (a.type !== b.type)
|
|
31
|
+
return false;
|
|
32
|
+
if (a.type === 'symbol')
|
|
33
|
+
return a.name === b.name;
|
|
34
|
+
if (a.type === 'string')
|
|
35
|
+
return a.value === b.value;
|
|
36
|
+
if (a.type === 'number')
|
|
37
|
+
return a.value === b.value;
|
|
38
|
+
if (a.type === 'list') {
|
|
39
|
+
const ac = a.children || [];
|
|
40
|
+
const bc = b.children || [];
|
|
41
|
+
if (ac.length !== bc.length)
|
|
42
|
+
return false;
|
|
43
|
+
for (let i = 0; i < ac.length; i++) {
|
|
44
|
+
if (!astEqual(ac[i], bc[i]))
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
function getCondKey(node) {
|
|
52
|
+
if (node.type === 'symbol' || node.type === 'keyword')
|
|
53
|
+
return node.name || '';
|
|
54
|
+
if (node.type === 'string' || node.type === 'number')
|
|
55
|
+
return String(node.value);
|
|
56
|
+
if (node.type === 'list' && node.children) {
|
|
57
|
+
return '(' + node.children.map(c => getCondKey(c)).join(' ') + ')';
|
|
58
|
+
}
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
function hasConditionalForm(body) {
|
|
62
|
+
for (const form of body) {
|
|
63
|
+
if (form.type === 'list' && form.children && form.children.length > 0 && form.children[0].type === 'symbol') {
|
|
64
|
+
const name = form.children[0].name;
|
|
65
|
+
if (name === 'if' || name === 'cond' || name === 'when' || name === 'unless') {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=types.js.map
|