@atlisp/lint 0.1.5 → 0.1.8
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/LICENSE +21 -0
- package/README.md +219 -58
- package/atlisp-lint.default.json +32 -1
- package/dist/atlisp-lint.default.json +90 -0
- package/dist/cache.d.ts +2 -2
- package/dist/cache.js +6 -6
- package/dist/checks/append-single.d.ts +3 -0
- package/dist/checks/append-single.js +17 -0
- package/dist/checks/arg-count.d.ts +3 -0
- package/dist/checks/arg-count.js +123 -0
- package/dist/checks/assoc-without-cdr.d.ts +5 -0
- package/dist/checks/assoc-without-cdr.js +32 -0
- package/dist/checks/comment-style.d.ts +3 -0
- package/dist/checks/comment-style.js +24 -0
- package/dist/checks/cond-duplicate.d.ts +5 -0
- package/dist/checks/cond-duplicate.js +52 -0
- package/dist/checks/cond-simplify.d.ts +3 -0
- package/dist/checks/cond-simplify.js +45 -0
- package/dist/checks/constant-condition.js +4 -4
- package/dist/checks/dangerous-calls.js +2 -2
- package/dist/checks/dangling-defun.d.ts +3 -0
- package/dist/checks/dangling-defun.js +10 -28
- package/dist/checks/double-not.js +1 -1
- package/dist/checks/duplicate-defun.d.ts +6 -0
- package/dist/checks/duplicate-defun.js +50 -0
- package/dist/checks/dynamic-doc.d.ts +3 -0
- package/dist/checks/dynamic-doc.js +21 -0
- package/dist/checks/empty-branch.js +1 -1
- package/dist/checks/empty-catch.d.ts +3 -0
- package/dist/checks/empty-catch.js +34 -0
- package/dist/checks/eq-usage.d.ts +3 -0
- package/dist/checks/eq-usage.js +25 -0
- package/dist/checks/error-handling.d.ts +3 -0
- package/dist/checks/error-handling.js +56 -0
- package/dist/checks/extra-parens.d.ts +3 -0
- package/dist/checks/extra-parens.js +45 -0
- package/dist/checks/format-indent.d.ts +3 -0
- package/dist/checks/format-indent.js +29 -0
- package/dist/checks/function-complexity.js +1 -1
- package/dist/checks/function-order.d.ts +3 -0
- package/dist/checks/function-order.js +33 -0
- package/dist/checks/global-naming.d.ts +3 -0
- package/dist/checks/global-naming.js +62 -0
- package/dist/checks/identical-branches.d.ts +5 -0
- package/dist/checks/identical-branches.js +54 -0
- package/dist/checks/index.d.ts +3 -0
- package/dist/checks/index.js +117 -0
- package/dist/checks/lambda-syntax.d.ts +3 -0
- package/dist/checks/lambda-syntax.js +25 -0
- package/dist/checks/long-function-call.d.ts +3 -0
- package/dist/checks/long-function-call.js +54 -0
- package/dist/checks/loop-optimization.d.ts +3 -0
- package/dist/checks/loop-optimization.js +17 -0
- package/dist/checks/magic-number.d.ts +3 -0
- package/dist/checks/magic-number.js +21 -0
- package/dist/checks/misplaced-else.js +1 -1
- package/dist/checks/missing-export.js +2 -2
- package/dist/checks/mixed-indent.d.ts +3 -0
- package/dist/checks/mixed-indent.js +19 -0
- package/dist/checks/module-reg.js +1 -1
- package/dist/checks/multiple-setq.js +1 -1
- package/dist/checks/no-return.d.ts +3 -0
- package/dist/checks/no-return.js +51 -0
- package/dist/checks/nth-usage.d.ts +3 -0
- package/dist/checks/nth-usage.js +17 -0
- package/dist/checks/quote-style.d.ts +3 -0
- package/dist/checks/quote-style.js +25 -0
- package/dist/checks/quote-vs-function.js +1 -1
- package/dist/checks/recursive-call.js +1 -1
- package/dist/checks/redundant-if.d.ts +3 -0
- package/dist/checks/redundant-if.js +17 -0
- package/dist/checks/redundant-let.js +1 -1
- package/dist/checks/redundant-nil-else.js +1 -1
- package/dist/checks/redundant-progn.js +1 -1
- package/dist/checks/redundant-quotes.js +1 -1
- package/dist/checks/redundant-setq.js +1 -1
- package/dist/checks/self-compare.js +1 -1
- package/dist/checks/setq-multiple.d.ts +3 -0
- package/dist/checks/setq-multiple.js +17 -0
- package/dist/checks/setq-single-arg.d.ts +5 -0
- package/dist/checks/setq-single-arg.js +30 -0
- package/dist/checks/shadow-builtin.d.ts +3 -0
- package/dist/checks/shadow-builtin.js +77 -0
- package/dist/checks/single-arg-and-or.js +1 -1
- package/dist/checks/strcat-usage.d.ts +3 -0
- package/dist/checks/strcat-usage.js +25 -0
- package/dist/checks/type-check.d.ts +3 -0
- package/dist/checks/type-check.js +26 -0
- package/dist/checks/unused-let.js +1 -1
- package/dist/checks/unused-package-dep.js +3 -3
- package/dist/checks/variable-shadow.js +1 -1
- package/dist/checks/while-constant.d.ts +5 -0
- package/dist/checks/while-constant.js +40 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +71 -2
- package/dist/disable.js +1 -1
- package/dist/formatter.d.ts +2 -0
- package/dist/formatter.js +51 -0
- package/dist/formatters.d.ts +1 -0
- package/dist/formatters.js +18 -2
- package/dist/index.js +172 -32
- package/dist/lib/lint-sbcl.lisp +161 -0
- package/dist/locale.js +76 -0
- package/dist/presets.d.ts +4 -0
- package/dist/presets.js +159 -0
- package/dist/project.js +37 -6
- package/dist/rules.d.ts +9 -0
- package/dist/rules.js +239 -0
- package/dist/runner.d.ts +2 -0
- package/dist/runner.js +329 -12
- package/dist/sbcl.js +1 -1
- package/dist/stub-packages.json +41 -0
- package/dist/types.d.ts +6 -0
- package/dist/validate.d.ts +8 -0
- package/dist/validate.js +126 -0
- package/dist/watch.d.ts +9 -0
- package/dist/watch.js +113 -0
- package/package.json +1 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkArgCount = checkArgCount;
|
|
4
|
+
const locale_1 = require("../locale");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
function checkArgCount(content, file) {
|
|
7
|
+
const issues = [];
|
|
8
|
+
const lines = content.split('\n');
|
|
9
|
+
const defuns = [];
|
|
10
|
+
// Collect all defun definitions
|
|
11
|
+
for (let i = 0; i < lines.length; i++) {
|
|
12
|
+
const stripped = (0, utils_1.stripLine)(lines[i]);
|
|
13
|
+
const m = stripped.match(/\(defun\s+(\S+)\s+\(([^)]*)\)/);
|
|
14
|
+
if (m) {
|
|
15
|
+
const all = m[2];
|
|
16
|
+
const slashIdx = all.indexOf('/');
|
|
17
|
+
const paramStr = slashIdx >= 0 ? all.slice(0, slashIdx).trim() : all;
|
|
18
|
+
const params = paramStr.split(/\s+/).filter(Boolean);
|
|
19
|
+
defuns.push({ name: m[1], line: i + 1, paramCount: params.length });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Check calls against definitions
|
|
23
|
+
for (const fn of defuns) {
|
|
24
|
+
if (fn.name.startsWith('c:') || fn.name.includes('/'))
|
|
25
|
+
continue;
|
|
26
|
+
if (fn.paramCount === 0)
|
|
27
|
+
continue;
|
|
28
|
+
for (let i = 0; i < lines.length; i++) {
|
|
29
|
+
const stripped = (0, utils_1.stripLine)(lines[i]);
|
|
30
|
+
const re = new RegExp('\\(\\s*' + fn.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\s+');
|
|
31
|
+
// Don't match the definition line
|
|
32
|
+
if (stripped.includes('defun') && stripped.includes(fn.name))
|
|
33
|
+
continue;
|
|
34
|
+
const callMatch = re.exec(stripped);
|
|
35
|
+
if (callMatch) {
|
|
36
|
+
// Collect all text from the call across lines
|
|
37
|
+
let callText = stripped.slice(callMatch.index);
|
|
38
|
+
let parenDepth = 0;
|
|
39
|
+
for (const ch of callText) {
|
|
40
|
+
if (ch === '(')
|
|
41
|
+
parenDepth++;
|
|
42
|
+
else if (ch === ')')
|
|
43
|
+
parenDepth--;
|
|
44
|
+
}
|
|
45
|
+
// If not closed on this line, read next lines
|
|
46
|
+
let lineIdx = i + 1;
|
|
47
|
+
while (parenDepth > 0 && lineIdx < lines.length) {
|
|
48
|
+
const nextStripped = (0, utils_1.stripLine)(lines[lineIdx]);
|
|
49
|
+
callText += ' ' + nextStripped;
|
|
50
|
+
for (const ch of nextStripped) {
|
|
51
|
+
if (ch === '(')
|
|
52
|
+
parenDepth++;
|
|
53
|
+
else if (ch === ')')
|
|
54
|
+
parenDepth--;
|
|
55
|
+
}
|
|
56
|
+
lineIdx++;
|
|
57
|
+
}
|
|
58
|
+
// Extract args: remove the function call prefix
|
|
59
|
+
const afterName = callText.slice(callMatch[0].length - 1); // keep the leading (
|
|
60
|
+
// Find matching closing paren
|
|
61
|
+
let closeIdx = -1;
|
|
62
|
+
let pd = 1;
|
|
63
|
+
for (let j = 0; j < afterName.length; j++) {
|
|
64
|
+
if (afterName[j] === '(')
|
|
65
|
+
pd++;
|
|
66
|
+
else if (afterName[j] === ')') {
|
|
67
|
+
pd--;
|
|
68
|
+
if (pd === 0) {
|
|
69
|
+
closeIdx = j;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const argsStr = closeIdx >= 0 ? afterName.slice(0, closeIdx).trim() : afterName.trim();
|
|
75
|
+
if (!argsStr)
|
|
76
|
+
continue;
|
|
77
|
+
// Count space-delimited args at depth 0
|
|
78
|
+
let argCount = 0;
|
|
79
|
+
let depth = 0;
|
|
80
|
+
let inArg = false;
|
|
81
|
+
for (const ch of argsStr) {
|
|
82
|
+
if (ch === '(') {
|
|
83
|
+
depth++;
|
|
84
|
+
if (!inArg) {
|
|
85
|
+
inArg = true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (ch === ')') {
|
|
89
|
+
depth--;
|
|
90
|
+
if (depth === 0) {
|
|
91
|
+
if (inArg) {
|
|
92
|
+
argCount++;
|
|
93
|
+
inArg = false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (depth === 0 && (ch === ' ' || ch === '\t')) {
|
|
98
|
+
if (inArg) {
|
|
99
|
+
argCount++;
|
|
100
|
+
inArg = false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (!inArg) {
|
|
104
|
+
inArg = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (inArg)
|
|
108
|
+
argCount++;
|
|
109
|
+
if (argCount !== fn.paramCount) {
|
|
110
|
+
issues.push({
|
|
111
|
+
file,
|
|
112
|
+
line: i + 1,
|
|
113
|
+
severity: 'warn',
|
|
114
|
+
rule: 'arg_count',
|
|
115
|
+
message: (0, locale_1.t)('arg_count', fn.name, argCount, fn.paramCount),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return issues;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=arg-count.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Issue } from '../types';
|
|
2
|
+
import { AstNode } from '@atlisp/parser';
|
|
3
|
+
export declare function checkAssocWithoutCdr(content: string, file: string): Issue[];
|
|
4
|
+
export declare function checkAssocWithoutCdrAst(ast: AstNode, file: string): Issue[];
|
|
5
|
+
//# sourceMappingURL=assoc-without-cdr.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkAssocWithoutCdr = checkAssocWithoutCdr;
|
|
4
|
+
exports.checkAssocWithoutCdrAst = checkAssocWithoutCdrAst;
|
|
5
|
+
const locale_1 = require("../locale");
|
|
6
|
+
const parser_1 = require("@atlisp/parser");
|
|
7
|
+
function checkAssocWithoutCdr(content, file) {
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content);
|
|
9
|
+
return checkAssocWithoutCdrAst(ast, file);
|
|
10
|
+
}
|
|
11
|
+
function checkAssocWithoutCdrAst(ast, file) {
|
|
12
|
+
const issues = [];
|
|
13
|
+
const assocNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'assoc'));
|
|
14
|
+
for (const node of assocNodes) {
|
|
15
|
+
if (!node.parent)
|
|
16
|
+
continue;
|
|
17
|
+
// Check if parent is not a cdr or cadr call
|
|
18
|
+
const parent = node.parent;
|
|
19
|
+
const isWrapped = (0, parser_1.astIsList)(parent, 'cdr') || (0, parser_1.astIsList)(parent, 'cadr');
|
|
20
|
+
if (!isWrapped) {
|
|
21
|
+
issues.push({
|
|
22
|
+
file,
|
|
23
|
+
line: node.pos.line,
|
|
24
|
+
severity: 'warn',
|
|
25
|
+
rule: 'assoc_without_cdr',
|
|
26
|
+
message: (0, locale_1.t)('assoc_without_cdr'),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return issues;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=assoc-without-cdr.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkCommentStyle = checkCommentStyle;
|
|
4
|
+
const locale_1 = require("../locale");
|
|
5
|
+
function checkCommentStyle(content, file) {
|
|
6
|
+
const issues = [];
|
|
7
|
+
const lines = content.split('\n');
|
|
8
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9
|
+
const line = lines[i];
|
|
10
|
+
const trimmed = line.trim();
|
|
11
|
+
// Detect ; not followed by space (except ;; and ;|)
|
|
12
|
+
if (/^;[^ ;|]/.test(trimmed)) {
|
|
13
|
+
issues.push({
|
|
14
|
+
file,
|
|
15
|
+
line: i + 1,
|
|
16
|
+
severity: 'warn',
|
|
17
|
+
rule: 'comment_style',
|
|
18
|
+
message: (0, locale_1.t)('comment_style'),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return issues;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=comment-style.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Issue } from '../types';
|
|
2
|
+
import { AstNode } from '@atlisp/parser';
|
|
3
|
+
export declare function checkCondDuplicate(content: string, file: string): Issue[];
|
|
4
|
+
export declare function checkCondDuplicateAst(ast: AstNode, file: string): Issue[];
|
|
5
|
+
//# sourceMappingURL=cond-duplicate.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkCondDuplicate = checkCondDuplicate;
|
|
4
|
+
exports.checkCondDuplicateAst = checkCondDuplicateAst;
|
|
5
|
+
const locale_1 = require("../locale");
|
|
6
|
+
const parser_1 = require("@atlisp/parser");
|
|
7
|
+
function condKey(node) {
|
|
8
|
+
if (node.type === 'symbol')
|
|
9
|
+
return `s:${node.name}`;
|
|
10
|
+
if (node.type === 'number')
|
|
11
|
+
return `n:${node.value}`;
|
|
12
|
+
if (node.type === 'string')
|
|
13
|
+
return `str:${node.value}`;
|
|
14
|
+
if (node.type === 'list') {
|
|
15
|
+
const children = node.children || [];
|
|
16
|
+
return `list(${children.map(condKey).join(',')})`;
|
|
17
|
+
}
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
function checkCondDuplicate(content, file) {
|
|
21
|
+
const ast = (0, parser_1.parseAst)(content);
|
|
22
|
+
return checkCondDuplicateAst(ast, file);
|
|
23
|
+
}
|
|
24
|
+
function checkCondDuplicateAst(ast, file) {
|
|
25
|
+
const issues = [];
|
|
26
|
+
const condNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'cond'));
|
|
27
|
+
for (const node of condNodes) {
|
|
28
|
+
if (!node.children)
|
|
29
|
+
continue;
|
|
30
|
+
const seen = new Map();
|
|
31
|
+
for (let i = 1; i < node.children.length; i++) {
|
|
32
|
+
const clause = node.children[i];
|
|
33
|
+
if (!clause.children || clause.children.length < 1)
|
|
34
|
+
continue;
|
|
35
|
+
const condition = clause.children[0];
|
|
36
|
+
const key = condKey(condition);
|
|
37
|
+
if (seen.has(key)) {
|
|
38
|
+
issues.push({
|
|
39
|
+
file,
|
|
40
|
+
line: node.pos.line,
|
|
41
|
+
severity: 'warn',
|
|
42
|
+
rule: 'cond_duplicate',
|
|
43
|
+
message: (0, locale_1.t)('cond_duplicate'),
|
|
44
|
+
});
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
seen.set(key, i);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return issues;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=cond-duplicate.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkCondSimplify = checkCondSimplify;
|
|
4
|
+
const locale_1 = require("../locale");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
function checkCondSimplify(content, file) {
|
|
7
|
+
const issues = [];
|
|
8
|
+
const lines = content.split('\n');
|
|
9
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10
|
+
const stripped = (0, utils_1.stripLine)(lines[i]);
|
|
11
|
+
// Match (cond (t ...)) or (cond (expr ...)) - single branch cond
|
|
12
|
+
// A single-branch cond typically looks like: (cond (expr body)) on one line
|
|
13
|
+
const m = stripped.match(/\(cond\s+\(/);
|
|
14
|
+
if (m) {
|
|
15
|
+
// Count how many top-level branches inside this cond
|
|
16
|
+
const condStart = stripped.indexOf('(cond');
|
|
17
|
+
const afterCond = stripped.slice(condStart + 5).trim();
|
|
18
|
+
let branchCount = 0;
|
|
19
|
+
let parenDepth = 0;
|
|
20
|
+
for (const ch of afterCond) {
|
|
21
|
+
if (ch === '(') {
|
|
22
|
+
if (parenDepth === 0)
|
|
23
|
+
branchCount++;
|
|
24
|
+
parenDepth++;
|
|
25
|
+
}
|
|
26
|
+
else if (ch === ')') {
|
|
27
|
+
parenDepth--;
|
|
28
|
+
if (parenDepth < 0)
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (branchCount === 1) {
|
|
33
|
+
issues.push({
|
|
34
|
+
file,
|
|
35
|
+
line: i + 1,
|
|
36
|
+
severity: 'warn',
|
|
37
|
+
rule: 'cond_simplify',
|
|
38
|
+
message: (0, locale_1.t)('cond_simplify'),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return issues;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=cond-simplify.js.map
|
|
@@ -14,7 +14,7 @@ function checkConstantConditionAst(ast, file) {
|
|
|
14
14
|
const issues = [];
|
|
15
15
|
// Check if / while with constant condition
|
|
16
16
|
for (const form of CONDITION_FORMS) {
|
|
17
|
-
const nodes = (0, parser_1.astFindAll)(ast,
|
|
17
|
+
const nodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, form));
|
|
18
18
|
for (const node of nodes) {
|
|
19
19
|
if (!node.children || node.children.length < 2)
|
|
20
20
|
continue;
|
|
@@ -31,7 +31,7 @@ function checkConstantConditionAst(ast, file) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
// Check (cond (T ...))
|
|
34
|
-
const condNodes = (0, parser_1.astFindAll)(ast,
|
|
34
|
+
const condNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'cond'));
|
|
35
35
|
for (const condNode of condNodes) {
|
|
36
36
|
if (!condNode.children)
|
|
37
37
|
continue;
|
|
@@ -52,7 +52,7 @@ function checkConstantConditionAst(ast, file) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
// Check (or T ...) — first arg is T, always short-circuits to T
|
|
55
|
-
const orNodes = (0, parser_1.astFindAll)(ast,
|
|
55
|
+
const orNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'or'));
|
|
56
56
|
for (const node of orNodes) {
|
|
57
57
|
if (!node.children || node.children.length < 2)
|
|
58
58
|
continue;
|
|
@@ -68,7 +68,7 @@ function checkConstantConditionAst(ast, file) {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
// Check (and nil ...) — first arg is nil, always short-circuits to nil
|
|
71
|
-
const andNodes = (0, parser_1.astFindAll)(ast,
|
|
71
|
+
const andNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'and'));
|
|
72
72
|
for (const node of andNodes) {
|
|
73
73
|
if (!node.children || node.children.length < 2)
|
|
74
74
|
continue;
|
|
@@ -47,7 +47,7 @@ function checkDangerousCallsAst(ast, file, config) {
|
|
|
47
47
|
continue;
|
|
48
48
|
if (pattern.commandShell) {
|
|
49
49
|
// Find (command "shell" ...) calls
|
|
50
|
-
const commandCalls = (0, parser_1.astFindAll)(ast,
|
|
50
|
+
const commandCalls = (0, parser_1.astFindAll)(ast, n => isFunctionCall(n, 'command'));
|
|
51
51
|
for (const call of commandCalls) {
|
|
52
52
|
if (isInQuote(call))
|
|
53
53
|
continue;
|
|
@@ -67,7 +67,7 @@ function checkDangerousCallsAst(ast, file, config) {
|
|
|
67
67
|
}
|
|
68
68
|
else if (pattern.functionCall) {
|
|
69
69
|
// Find actual function calls
|
|
70
|
-
const calls = (0, parser_1.astFindAll)(ast,
|
|
70
|
+
const calls = (0, parser_1.astFindAll)(ast, n => isFunctionCall(n, pattern.rule));
|
|
71
71
|
for (const call of calls) {
|
|
72
72
|
if (isInQuote(call))
|
|
73
73
|
continue;
|
|
@@ -2,5 +2,8 @@ import { Issue } from '../types';
|
|
|
2
2
|
export declare function checkDanglingDefun(file: string, allDefuns: Map<string, {
|
|
3
3
|
file: string;
|
|
4
4
|
line: number;
|
|
5
|
+
}[]>, allReferences: Map<string, {
|
|
6
|
+
file: string;
|
|
7
|
+
line: number;
|
|
5
8
|
}[]>): Issue[];
|
|
6
9
|
//# sourceMappingURL=dangling-defun.d.ts.map
|
|
@@ -37,46 +37,28 @@ exports.checkDanglingDefun = checkDanglingDefun;
|
|
|
37
37
|
const locale_1 = require("../locale");
|
|
38
38
|
const parser_1 = require("@atlisp/parser");
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
|
-
function checkDanglingDefun(file, allDefuns) {
|
|
40
|
+
function checkDanglingDefun(file, allDefuns, allReferences) {
|
|
41
41
|
const issues = [];
|
|
42
42
|
const content = fs.readFileSync(file, 'utf-8');
|
|
43
43
|
const ast = (0, parser_1.parseAst)(content);
|
|
44
44
|
const localDefuns = [];
|
|
45
|
-
const defunNodes = (0, parser_1.astFindAll)(ast,
|
|
45
|
+
const defunNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'defun') || (0, parser_1.astIsList)(n, 'defun-q'));
|
|
46
46
|
for (const node of defunNodes) {
|
|
47
47
|
if (node.children && node.children.length >= 2 && (0, parser_1.astIsSymbol)(node.children[1])) {
|
|
48
|
-
localDefuns.push(node.children[1].name);
|
|
48
|
+
localDefuns.push({ name: node.children[1].name, line: node.pos.line });
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
});
|
|
57
|
-
for (const call of allCalls) {
|
|
58
|
-
const name = call.children[0].name;
|
|
59
|
-
if (name !== 'defun' && name !== 'defun-q' && !name.startsWith('c:') && !name.includes(':')) {
|
|
60
|
-
calledSymbols.add(name);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
for (const defunName of localDefuns) {
|
|
64
|
-
let isCalled = calledSymbols.has(defunName);
|
|
65
|
-
if (!isCalled) {
|
|
66
|
-
for (const [, defs] of allDefuns) {
|
|
67
|
-
if (defs.some((d) => d.file !== file)) {
|
|
68
|
-
isCalled = true;
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
if (!isCalled) {
|
|
51
|
+
for (const defun of localDefuns) {
|
|
52
|
+
const refs = allReferences.get(defun.name) || [];
|
|
53
|
+
const calledElsewhere = refs.some(r => r.file !== file);
|
|
54
|
+
const calledLocally = refs.some(r => r.file === file);
|
|
55
|
+
if (!calledLocally && !calledElsewhere) {
|
|
74
56
|
issues.push({
|
|
75
57
|
file,
|
|
76
|
-
line:
|
|
58
|
+
line: defun.line,
|
|
77
59
|
severity: 'warn',
|
|
78
60
|
rule: 'dangling_defun',
|
|
79
|
-
message: (0, locale_1.t)('dangling_defun',
|
|
61
|
+
message: (0, locale_1.t)('dangling_defun', defun.name),
|
|
80
62
|
});
|
|
81
63
|
}
|
|
82
64
|
}
|
|
@@ -10,7 +10,7 @@ function checkDoubleNot(content, file) {
|
|
|
10
10
|
}
|
|
11
11
|
function checkDoubleNotAst(ast, file) {
|
|
12
12
|
const issues = [];
|
|
13
|
-
const notNodes = (0, parser_1.astFindAll)(ast,
|
|
13
|
+
const notNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'not'));
|
|
14
14
|
for (const node of notNodes) {
|
|
15
15
|
if (!node.children || node.children.length < 2)
|
|
16
16
|
continue;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkDuplicateDefun = checkDuplicateDefun;
|
|
4
|
+
const locale_1 = require("../locale");
|
|
5
|
+
function checkDuplicateDefun(filepath, allDefuns) {
|
|
6
|
+
const issues = [];
|
|
7
|
+
const defunsInFile = new Map();
|
|
8
|
+
// Collect defuns in this file
|
|
9
|
+
for (const [name, locations] of allDefuns) {
|
|
10
|
+
for (const loc of locations) {
|
|
11
|
+
if (loc.file === filepath) {
|
|
12
|
+
const lines = defunsInFile.get(name) || [];
|
|
13
|
+
lines.push(loc.line);
|
|
14
|
+
defunsInFile.set(name, lines);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// Check for duplicates within this file
|
|
19
|
+
for (const [name, lines] of defunsInFile) {
|
|
20
|
+
if (lines.length > 1) {
|
|
21
|
+
for (const line of lines.slice(1)) {
|
|
22
|
+
issues.push({
|
|
23
|
+
file: filepath,
|
|
24
|
+
line,
|
|
25
|
+
severity: 'warn',
|
|
26
|
+
rule: 'duplicate_defun',
|
|
27
|
+
message: (0, locale_1.t)('duplicate_defun', name),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Check for duplicates across files
|
|
33
|
+
for (const [name, locations] of allDefuns) {
|
|
34
|
+
const matchingFiles = locations.filter(l => l.file !== filepath);
|
|
35
|
+
if (matchingFiles.length > 0) {
|
|
36
|
+
const thisFileLoc = locations.find(l => l.file === filepath);
|
|
37
|
+
if (thisFileLoc) {
|
|
38
|
+
issues.push({
|
|
39
|
+
file: filepath,
|
|
40
|
+
line: thisFileLoc.line,
|
|
41
|
+
severity: 'warn',
|
|
42
|
+
rule: 'duplicate_defun',
|
|
43
|
+
message: (0, locale_1.t)('duplicate_defun', name),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return issues;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=duplicate-defun.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkDynamicDoc = checkDynamicDoc;
|
|
4
|
+
const locale_1 = require("../locale");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
function checkDynamicDoc(content, file) {
|
|
7
|
+
const issues = [];
|
|
8
|
+
const lines = content.split('\n');
|
|
9
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10
|
+
const stripped = (0, utils_1.stripLine)(lines[i]);
|
|
11
|
+
const m = stripped.match(/\(defun\s+(C:\S+)\b/);
|
|
12
|
+
if (m) {
|
|
13
|
+
const body = lines.slice(i + 1).join('\n');
|
|
14
|
+
if (!body.includes('(princ)')) {
|
|
15
|
+
issues.push({ file, line: i + 1, severity: 'warn', rule: 'dynamic_doc', message: (0, locale_1.t)('dynamic_doc', m[1]) });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return issues;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=dynamic-doc.js.map
|
|
@@ -13,7 +13,7 @@ function checkEmptyBranch(content, file) {
|
|
|
13
13
|
function checkEmptyBranchAst(ast, file) {
|
|
14
14
|
const issues = [];
|
|
15
15
|
for (const form of BRANCH_FORMS) {
|
|
16
|
-
const nodes = (0, parser_1.astFindAll)(ast,
|
|
16
|
+
const nodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, form));
|
|
17
17
|
for (const node of nodes) {
|
|
18
18
|
if (!node.children)
|
|
19
19
|
continue;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkEmptyCatch = checkEmptyCatch;
|
|
4
|
+
const locale_1 = require("../locale");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
function checkEmptyCatch(content, file) {
|
|
7
|
+
const issues = [];
|
|
8
|
+
const lines = content.split('\n');
|
|
9
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10
|
+
const stripped = (0, utils_1.stripLine)(lines[i]);
|
|
11
|
+
// Detect (vl-catch-all-apply ...) not inside (vl-catch-all-error-p ...) check
|
|
12
|
+
if (stripped.includes('vl-catch-all-apply')) {
|
|
13
|
+
// Check subsequent lines for error-p check
|
|
14
|
+
let hasErrorCheck = false;
|
|
15
|
+
for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) {
|
|
16
|
+
if ((0, utils_1.stripLine)(lines[j]).includes('vl-catch-all-error-p')) {
|
|
17
|
+
hasErrorCheck = true;
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (!hasErrorCheck) {
|
|
22
|
+
issues.push({
|
|
23
|
+
file,
|
|
24
|
+
line: i + 1,
|
|
25
|
+
severity: 'warn',
|
|
26
|
+
rule: 'empty_catch',
|
|
27
|
+
message: (0, locale_1.t)('empty_catch'),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return issues;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=empty-catch.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkEqUsage = checkEqUsage;
|
|
4
|
+
const locale_1 = require("../locale");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
function checkEqUsage(content, file) {
|
|
7
|
+
const issues = [];
|
|
8
|
+
const lines = content.split('\n');
|
|
9
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10
|
+
const stripped = (0, utils_1.stripLine)(lines[i]);
|
|
11
|
+
// Detect (eq num ...) where num is literal number
|
|
12
|
+
const m = stripped.match(/\(eq\s+\S+\s+(\d+)/);
|
|
13
|
+
if (m) {
|
|
14
|
+
issues.push({
|
|
15
|
+
file,
|
|
16
|
+
line: i + 1,
|
|
17
|
+
severity: 'warn',
|
|
18
|
+
rule: 'eq_usage',
|
|
19
|
+
message: (0, locale_1.t)('eq_usage'),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return issues;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=eq-usage.js.map
|