@atlisp/lint 0.1.15 → 0.1.16
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.default.json +4 -3
- package/dist/checks/arg-count.js +39 -108
- package/dist/checks/bare-names.js +2 -0
- package/dist/checks/global-naming.js +6 -1
- package/dist/checks/index.js +2 -0
- package/dist/checks/no-return.js +23 -40
- package/dist/checks/recursive-call.js +30 -12
- package/dist/checks/undeclared-setq.d.ts +3 -0
- package/dist/checks/undeclared-setq.js +76 -0
- package/dist/config.js +3 -2
- package/dist/locale.js +6 -0
- package/dist/presets.js +1 -1
- package/dist/rules.js +7 -1
- package/dist/runner.js +2 -0
- package/dist/validate.js +1 -0
- package/package.json +1 -1
package/atlisp-lint.default.json
CHANGED
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"cond_simplify": "warn",
|
|
64
64
|
"quote_style": "warn",
|
|
65
65
|
"eq_usage": "warn",
|
|
66
|
-
"lambda_syntax": "
|
|
66
|
+
"lambda_syntax": "off",
|
|
67
67
|
"comment_style": "warn",
|
|
68
68
|
"empty_catch": "warn",
|
|
69
69
|
"nth_usage": "warn",
|
|
@@ -79,7 +79,8 @@
|
|
|
79
79
|
"dynamic_doc": "warn",
|
|
80
80
|
"loop_optimization": "off",
|
|
81
81
|
"type_check": "off",
|
|
82
|
-
"redundant_if": "warn"
|
|
82
|
+
"redundant_if": "warn",
|
|
83
|
+
"undeclared_setq": "warn"
|
|
83
84
|
},
|
|
84
85
|
"line_length": {
|
|
85
86
|
"max": 100,
|
|
@@ -87,7 +88,7 @@
|
|
|
87
88
|
},
|
|
88
89
|
"function_complexity": {
|
|
89
90
|
"max_lines": 60,
|
|
90
|
-
"max_nesting":
|
|
91
|
+
"max_nesting": 15
|
|
91
92
|
},
|
|
92
93
|
"cl_syntax": {
|
|
93
94
|
"keywords": ["&key"]
|
package/dist/checks/arg-count.js
CHANGED
|
@@ -2,119 +2,50 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.checkArgCount = checkArgCount;
|
|
4
4
|
const locale_1 = require("../locale");
|
|
5
|
-
const
|
|
5
|
+
const parser_1 = require("@atlisp/parser");
|
|
6
6
|
function checkArgCount(content, file) {
|
|
7
7
|
const issues = [];
|
|
8
|
-
const
|
|
9
|
-
const defuns =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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('/'))
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content);
|
|
9
|
+
const defuns = (0, parser_1.astFindListWithHead)(ast, 'defun');
|
|
10
|
+
for (const defun of defuns) {
|
|
11
|
+
if (!defun.children || defun.children.length < 3)
|
|
12
|
+
continue;
|
|
13
|
+
const nameNode = defun.children[1];
|
|
14
|
+
if (nameNode.type !== 'symbol' || !nameNode.name)
|
|
15
|
+
continue;
|
|
16
|
+
const funcName = nameNode.name;
|
|
17
|
+
// Skip C: commands and local functions
|
|
18
|
+
if (funcName.startsWith('c:') || funcName.startsWith('C:') || funcName.includes('/'))
|
|
25
19
|
continue;
|
|
26
|
-
|
|
20
|
+
const paramList = defun.children[2];
|
|
21
|
+
if (paramList.type !== 'list' || !paramList.children)
|
|
22
|
+
continue;
|
|
23
|
+
// Count params before /
|
|
24
|
+
let paramCount = 0;
|
|
25
|
+
for (const p of paramList.children) {
|
|
26
|
+
if (p.type === 'symbol' && p.name === '/')
|
|
27
|
+
break;
|
|
28
|
+
if (p.type === 'symbol' && p.name && p.name !== 'T' && p.name !== 'nil') {
|
|
29
|
+
paramCount++;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (paramCount === 0)
|
|
27
33
|
continue;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (stripped.includes('defun') && stripped.includes(fn.name))
|
|
34
|
+
// Find all calls to this function
|
|
35
|
+
const calls = (0, parser_1.astFindListWithHead)(ast, funcName);
|
|
36
|
+
for (const call of calls) {
|
|
37
|
+
if (!call.children || call.children.length === 0)
|
|
33
38
|
continue;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
}
|
|
39
|
+
// Skip if the first child is the function name — that's the head of this call
|
|
40
|
+
const argCount = call.children.length - 1;
|
|
41
|
+
if (argCount !== paramCount) {
|
|
42
|
+
issues.push({
|
|
43
|
+
file,
|
|
44
|
+
line: call.pos.line,
|
|
45
|
+
severity: 'warn',
|
|
46
|
+
rule: 'arg_count',
|
|
47
|
+
message: (0, locale_1.t)('arg_count', funcName, argCount, paramCount),
|
|
48
|
+
});
|
|
118
49
|
}
|
|
119
50
|
}
|
|
120
51
|
}
|
|
@@ -16,6 +16,8 @@ function checkBareFunctionNames(content, file, allowlist, namespacePattern) {
|
|
|
16
16
|
continue;
|
|
17
17
|
if (name.includes('/'))
|
|
18
18
|
continue; // local: defun foo/bar
|
|
19
|
+
if (name.startsWith('cb-'))
|
|
20
|
+
continue; // DCL callback
|
|
19
21
|
if (!nsRegex.test(name) && !name.includes(':')) {
|
|
20
22
|
issues.push({
|
|
21
23
|
file,
|
|
@@ -3,6 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.checkGlobalNaming = checkGlobalNaming;
|
|
4
4
|
const locale_1 = require("../locale");
|
|
5
5
|
const parser_1 = require("@atlisp/parser");
|
|
6
|
+
/** Check if a variable name is earmuffed (*var*), optionally with a namespace prefix (ns:*var*) */
|
|
7
|
+
function isEarmuffed(name) {
|
|
8
|
+
const localName = name.includes(':') ? name.slice(name.lastIndexOf(':') + 1) : name;
|
|
9
|
+
return localName.startsWith('*') && localName.endsWith('*') && localName.length > 2;
|
|
10
|
+
}
|
|
6
11
|
function isInsideLocalScope(node) {
|
|
7
12
|
let cur = node.parent;
|
|
8
13
|
while (cur) {
|
|
@@ -34,7 +39,7 @@ function checkGlobalNaming(content, file) {
|
|
|
34
39
|
continue;
|
|
35
40
|
if (v.name === 'T' || v.name === 'nil')
|
|
36
41
|
continue;
|
|
37
|
-
if (
|
|
42
|
+
if (isEarmuffed(v.name))
|
|
38
43
|
continue;
|
|
39
44
|
issues.push({
|
|
40
45
|
file,
|
package/dist/checks/index.js
CHANGED
|
@@ -44,6 +44,7 @@ const loop_optimization_1 = require("./loop-optimization");
|
|
|
44
44
|
const type_check_1 = require("./type-check");
|
|
45
45
|
const redundant_if_1 = require("./redundant-if");
|
|
46
46
|
const trailing_ws_1 = require("./trailing-ws");
|
|
47
|
+
const undeclared_setq_1 = require("./undeclared-setq");
|
|
47
48
|
function runChecks(content, file, config) {
|
|
48
49
|
const issues = [];
|
|
49
50
|
const disableMap = (0, disable_1.parseDisableComments)(content);
|
|
@@ -108,6 +109,7 @@ function runChecks(content, file, config) {
|
|
|
108
109
|
{ rule: 'type_check', fn: () => (0, type_check_1.checkTypeCheck)(content, file) },
|
|
109
110
|
{ rule: 'redundant_if', fn: () => (0, redundant_if_1.checkRedundantIf)(content, file) },
|
|
110
111
|
{ rule: 'trailing_whitespace', fn: () => (0, trailing_ws_1.checkTrailingWhitespace)(content, file) },
|
|
112
|
+
{ rule: 'undeclared_setq', fn: () => (0, undeclared_setq_1.checkUndeclaredSetq)(content, file) },
|
|
111
113
|
];
|
|
112
114
|
for (const c of checks) {
|
|
113
115
|
addIfEnabled(c.rule, c.fn);
|
package/dist/checks/no-return.js
CHANGED
|
@@ -2,48 +2,31 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.checkNoReturn = checkNoReturn;
|
|
4
4
|
const locale_1 = require("../locale");
|
|
5
|
-
const
|
|
5
|
+
const parser_1 = require("@atlisp/parser");
|
|
6
6
|
function checkNoReturn(content, file) {
|
|
7
7
|
const issues = [];
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
else if (ch === ')')
|
|
31
|
-
depth--;
|
|
32
|
-
}
|
|
33
|
-
if (stripped.trim().startsWith('(') && !stripped.includes('defun'))
|
|
34
|
-
hasReturn = true;
|
|
35
|
-
if (depth === 0 && defunName && !defunName.startsWith('c:')) {
|
|
36
|
-
if (!hasReturn) {
|
|
37
|
-
issues.push({
|
|
38
|
-
file,
|
|
39
|
-
line: defunStart,
|
|
40
|
-
severity: 'warn',
|
|
41
|
-
rule: 'no_return',
|
|
42
|
-
message: (0, locale_1.t)('no_return', defunName),
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
inDefun = false;
|
|
46
|
-
}
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content);
|
|
9
|
+
const defuns = (0, parser_1.astFindListWithHead)(ast, 'defun');
|
|
10
|
+
for (const defun of defuns) {
|
|
11
|
+
if (!defun.children || defun.children.length < 3)
|
|
12
|
+
continue;
|
|
13
|
+
const nameNode = defun.children[1];
|
|
14
|
+
if (nameNode.type !== 'symbol' || !nameNode.name)
|
|
15
|
+
continue;
|
|
16
|
+
const funcName = nameNode.name;
|
|
17
|
+
// Skip C: commands — they don't need return values
|
|
18
|
+
if (/^[cC]:/.test(funcName))
|
|
19
|
+
continue;
|
|
20
|
+
// Body starts at index 3 (defun, name, params, body...)
|
|
21
|
+
const bodyExprs = defun.children.slice(3);
|
|
22
|
+
if (bodyExprs.length === 0) {
|
|
23
|
+
issues.push({
|
|
24
|
+
file,
|
|
25
|
+
line: defun.pos.line,
|
|
26
|
+
severity: 'warn',
|
|
27
|
+
rule: 'no_return',
|
|
28
|
+
message: (0, locale_1.t)('no_return', funcName),
|
|
29
|
+
});
|
|
47
30
|
}
|
|
48
31
|
}
|
|
49
32
|
return issues;
|
|
@@ -8,6 +8,8 @@ function checkRecursiveCall(content, file) {
|
|
|
8
8
|
const ast = (0, parser_1.parseAst)(content);
|
|
9
9
|
return checkRecursiveCallAst(ast, file);
|
|
10
10
|
}
|
|
11
|
+
/** Forms that provide conditional branching (exit condition for recursion) */
|
|
12
|
+
const CONDITIONAL_FORMS = new Set(['if', 'cond', 'when', 'unless']);
|
|
11
13
|
function checkRecursiveCallAst(ast, file) {
|
|
12
14
|
const issues = [];
|
|
13
15
|
const defuns = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'defun'));
|
|
@@ -18,25 +20,30 @@ function checkRecursiveCallAst(ast, file) {
|
|
|
18
20
|
const funcName = nameNode.type === 'symbol' && nameNode.name ? nameNode.name : '';
|
|
19
21
|
if (!funcName)
|
|
20
22
|
continue;
|
|
21
|
-
//
|
|
23
|
+
// Check body for self-call (skip name and param list)
|
|
24
|
+
let hasRecursiveCall = false;
|
|
25
|
+
let hasConditional = false;
|
|
22
26
|
for (let i = 3; i < defun.children.length; i++) {
|
|
23
|
-
if (hasSelfCall(defun.children[i], funcName))
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
if (hasSelfCall(defun.children[i], funcName))
|
|
28
|
+
hasRecursiveCall = true;
|
|
29
|
+
if (hasConditionalForm(defun.children[i]))
|
|
30
|
+
hasConditional = true;
|
|
31
|
+
}
|
|
32
|
+
// Only warn if the function calls itself AND has no exit condition
|
|
33
|
+
if (hasRecursiveCall && !hasConditional) {
|
|
34
|
+
issues.push({
|
|
35
|
+
file,
|
|
36
|
+
line: defun.pos.line,
|
|
37
|
+
severity: 'warn',
|
|
38
|
+
rule: 'recursive_call',
|
|
39
|
+
message: (0, locale_1.t)('recursive_call', funcName),
|
|
40
|
+
});
|
|
33
41
|
}
|
|
34
42
|
}
|
|
35
43
|
return issues;
|
|
36
44
|
}
|
|
37
45
|
function hasSelfCall(node, funcName) {
|
|
38
46
|
for (const n of (0, parser_1.astWalk)(node)) {
|
|
39
|
-
// Check if this node is a list whose car is the function name
|
|
40
47
|
if (n.type === 'list' && n.children && n.children.length > 0) {
|
|
41
48
|
const car = n.children[0];
|
|
42
49
|
if (car.type === 'symbol' && car.name === funcName) {
|
|
@@ -46,4 +53,15 @@ function hasSelfCall(node, funcName) {
|
|
|
46
53
|
}
|
|
47
54
|
return false;
|
|
48
55
|
}
|
|
56
|
+
function hasConditionalForm(node) {
|
|
57
|
+
for (const n of (0, parser_1.astWalk)(node)) {
|
|
58
|
+
if (n.type === 'list' && n.children && n.children.length > 0) {
|
|
59
|
+
const car = n.children[0];
|
|
60
|
+
if (car.type === 'symbol' && car.name && CONDITIONAL_FORMS.has(car.name)) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
49
67
|
//# sourceMappingURL=recursive-call.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkUndeclaredSetq = checkUndeclaredSetq;
|
|
4
|
+
const locale_1 = require("../locale");
|
|
5
|
+
const parser_1 = require("@atlisp/parser");
|
|
6
|
+
/** Extract declared variable names from a defun's parameter list (both sides of /) */
|
|
7
|
+
function getDeclaredVars(defun) {
|
|
8
|
+
const vars = new Set();
|
|
9
|
+
if (!defun.children || defun.children.length < 3)
|
|
10
|
+
return vars;
|
|
11
|
+
const paramList = defun.children[2];
|
|
12
|
+
if (paramList.type !== 'list' || !paramList.children)
|
|
13
|
+
return vars;
|
|
14
|
+
for (const p of paramList.children) {
|
|
15
|
+
if (p.type === 'symbol' && p.name && p.name !== '/' && p.name !== 'T' && p.name !== 'nil') {
|
|
16
|
+
vars.add(p.name);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return vars;
|
|
20
|
+
}
|
|
21
|
+
/** Check if a variable name is earmuffed (*var*), optionally with a namespace prefix (ns:*var*) */
|
|
22
|
+
function isEarmuffed(name) {
|
|
23
|
+
const localName = name.includes(':') ? name.slice(name.lastIndexOf(':') + 1) : name;
|
|
24
|
+
return localName.startsWith('*') && localName.endsWith('*') && localName.length > 2;
|
|
25
|
+
}
|
|
26
|
+
/** Find the nearest enclosing defun that is not inside a nested lambda/defun */
|
|
27
|
+
function findEnclosingDefun(node) {
|
|
28
|
+
let cur = node.parent;
|
|
29
|
+
while (cur) {
|
|
30
|
+
if (cur.type === 'list' && cur.children && cur.children.length > 0) {
|
|
31
|
+
const head = cur.children[0];
|
|
32
|
+
if (head.type === 'symbol') {
|
|
33
|
+
if (head.name === 'defun')
|
|
34
|
+
return cur;
|
|
35
|
+
if (head.name === 'lambda')
|
|
36
|
+
return null; // stopped by lambda boundary
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
cur = cur.parent;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function checkUndeclaredSetq(content, file) {
|
|
44
|
+
const issues = [];
|
|
45
|
+
const ast = (0, parser_1.parseAst)(content);
|
|
46
|
+
const setqForms = (0, parser_1.astFindListWithHead)(ast, 'setq');
|
|
47
|
+
for (const setq of setqForms) {
|
|
48
|
+
if (!setq.children || setq.children.length < 2)
|
|
49
|
+
continue;
|
|
50
|
+
// Skip top-level setq (not inside any defun)
|
|
51
|
+
const defun = findEnclosingDefun(setq);
|
|
52
|
+
if (!defun)
|
|
53
|
+
continue;
|
|
54
|
+
const declared = getDeclaredVars(defun);
|
|
55
|
+
for (let i = 1; i < setq.children.length; i += 2) {
|
|
56
|
+
const v = setq.children[i];
|
|
57
|
+
if (v.type !== 'symbol' || !v.name)
|
|
58
|
+
continue;
|
|
59
|
+
if (v.name === 'T' || v.name === 'nil')
|
|
60
|
+
continue;
|
|
61
|
+
if (isEarmuffed(v.name))
|
|
62
|
+
continue;
|
|
63
|
+
if (declared.has(v.name))
|
|
64
|
+
continue;
|
|
65
|
+
issues.push({
|
|
66
|
+
file,
|
|
67
|
+
line: v.pos.line,
|
|
68
|
+
severity: 'warn',
|
|
69
|
+
rule: 'undeclared_setq',
|
|
70
|
+
message: (0, locale_1.t)('undeclared_setq', v.name),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return issues;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=undeclared-setq.js.map
|
package/dist/config.js
CHANGED
|
@@ -64,6 +64,7 @@ const DEFAULT_CONFIG = {
|
|
|
64
64
|
unused_variable: 'warn',
|
|
65
65
|
missing_doc: 'warn',
|
|
66
66
|
trailing_whitespace: 'warn',
|
|
67
|
+
undeclared_setq: 'warn',
|
|
67
68
|
module_registration: 'off',
|
|
68
69
|
namespace_header: 'off',
|
|
69
70
|
unused_parameter: 'warn',
|
|
@@ -105,7 +106,7 @@ const DEFAULT_CONFIG = {
|
|
|
105
106
|
cond_simplify: 'warn',
|
|
106
107
|
quote_style: 'warn',
|
|
107
108
|
eq_usage: 'warn',
|
|
108
|
-
lambda_syntax: '
|
|
109
|
+
lambda_syntax: 'off',
|
|
109
110
|
comment_style: 'warn',
|
|
110
111
|
empty_catch: 'warn',
|
|
111
112
|
nth_usage: 'warn',
|
|
@@ -129,7 +130,7 @@ const DEFAULT_CONFIG = {
|
|
|
129
130
|
},
|
|
130
131
|
function_complexity: {
|
|
131
132
|
max_lines: 60,
|
|
132
|
-
max_nesting:
|
|
133
|
+
max_nesting: 15,
|
|
133
134
|
},
|
|
134
135
|
cl_syntax: {
|
|
135
136
|
keywords: ['&key'],
|
package/dist/locale.js
CHANGED
|
@@ -18,11 +18,13 @@ const messages = {
|
|
|
18
18
|
vlax_without_loading: 'vlax-* call without vl-load-com in file',
|
|
19
19
|
token_in_url: 'Possible token exposure in string: token= in URL',
|
|
20
20
|
open_without_close: 'File has {0} open() calls but {1} close() calls (possible resource leak)',
|
|
21
|
+
arg_count: "Function '{0}' called with {1} arguments but defined with {2}",
|
|
21
22
|
bare_function_names: "Unnamespaced function '{0}' (missing @:: or C: prefix)",
|
|
22
23
|
module_registration: 'Module file missing @::*modules* registration (expected in last lines)',
|
|
23
24
|
'namespace_header.missing': 'Missing (in-package {0}) header',
|
|
24
25
|
'namespace_header.wrong': 'in-package target is not {0}',
|
|
25
26
|
trailing_whitespace: 'Trailing whitespace',
|
|
27
|
+
undeclared_setq: "Variable '{0}' is setq'd inside defun but not declared in the parameter list — add to / list",
|
|
26
28
|
line_length: 'Line length {0} exceeds {1} characters',
|
|
27
29
|
'function_complexity.lines': "Function '{0}' has {1} lines (max {2})",
|
|
28
30
|
'function_complexity.nesting': "Function '{0}' nesting depth {1} exceeds {2}",
|
|
@@ -64,6 +66,7 @@ const messages = {
|
|
|
64
66
|
duplicate_defun: "Function '{0}' is defined multiple times",
|
|
65
67
|
dangling_defun: "Function '{0}' is defined but never called",
|
|
66
68
|
missing_export: "Function '{0}' is not registered in @::*modules*",
|
|
69
|
+
no_return: "Function '{0}' has no return value (body is empty)",
|
|
67
70
|
unused_package_dep: "Imported package '{0}' is never referenced",
|
|
68
71
|
'runner.read_error': 'Cannot read file',
|
|
69
72
|
'sbcl.script_not_found': 'lint-sbcl.lisp not found at {0}',
|
|
@@ -127,11 +130,13 @@ Options:
|
|
|
127
130
|
vlax_without_loading: '文件中使用了 vlax-* 函数但未调用 vl-load-com',
|
|
128
131
|
token_in_url: '字符串中可能泄露令牌:URL 中包含 token=',
|
|
129
132
|
open_without_close: '文件中有 {0} 个 open() 调用,但只有 {1} 个 close() 调用(可能存在资源泄露)',
|
|
133
|
+
arg_count: "函数 '{0}' 调用时传入了 {1} 个参数,但定义时为 {2} 个",
|
|
130
134
|
bare_function_names: "函数 '{0}' 缺少命名空间前缀(缺少 @:: 或 C: 前缀)",
|
|
131
135
|
module_registration: '模块文件缺少 @::*modules* 注册(应在文件末尾附近)',
|
|
132
136
|
'namespace_header.missing': '缺少 (in-package {0}) 头',
|
|
133
137
|
'namespace_header.wrong': 'in-package 目标不是 {0}',
|
|
134
138
|
trailing_whitespace: '行尾有多余空格',
|
|
139
|
+
undeclared_setq: "变量 '{0}' 在 defun 内被 setq 但未声明到参数表中——建议加入 / 私有变量表",
|
|
135
140
|
line_length: '行长度 {0} 超过 {1} 个字符',
|
|
136
141
|
'function_complexity.lines': "函数 '{0}' 有 {1} 行(最大 {2} 行)",
|
|
137
142
|
'function_complexity.nesting': "函数 '{0}' 嵌套深度 {1} 超过 {2}",
|
|
@@ -173,6 +178,7 @@ Options:
|
|
|
173
178
|
duplicate_defun: "函数 '{0}' 被多次定义",
|
|
174
179
|
dangling_defun: "函数 '{0}' 定义了但从未被调用",
|
|
175
180
|
missing_export: "函数 '{0}' 未注册到 @::*modules*",
|
|
181
|
+
no_return: "函数 '{0}' 没有表达式——函数体为空",
|
|
176
182
|
unused_package_dep: "导入的包 '{0}' 从未被引用",
|
|
177
183
|
'runner.read_error': '无法读取文件',
|
|
178
184
|
'sbcl.script_not_found': '未找到 lint-sbcl.lisp:{0}',
|
package/dist/presets.js
CHANGED
package/dist/rules.js
CHANGED
|
@@ -92,7 +92,7 @@ exports.RULES = [
|
|
|
92
92
|
{ name: 'global_naming', defaultSeverity: 'warn', description: '检测全局变量是否使用 *...* 命名', category: '风格' },
|
|
93
93
|
{
|
|
94
94
|
name: 'lambda_syntax',
|
|
95
|
-
defaultSeverity: '
|
|
95
|
+
defaultSeverity: 'off',
|
|
96
96
|
description: '检测 (function (lambda ...)) 建议使用缩写',
|
|
97
97
|
category: '风格',
|
|
98
98
|
},
|
|
@@ -189,6 +189,12 @@ exports.RULES = [
|
|
|
189
189
|
{ name: 'trailing_paren', defaultSeverity: 'warn', description: '检测多余闭合括号', category: '语法' },
|
|
190
190
|
{ name: 'trailing_whitespace', defaultSeverity: 'warn', description: '检测行尾多余空格', category: '风格' },
|
|
191
191
|
{ name: 'type_check', defaultSeverity: 'off', description: '检测 getvar 结果未做类型检查', category: '最佳实践' },
|
|
192
|
+
{
|
|
193
|
+
name: 'undeclared_setq',
|
|
194
|
+
defaultSeverity: 'warn',
|
|
195
|
+
description: '检测 defun 内 setq 了未声明的变量(建议加入 / 私有变量表)',
|
|
196
|
+
category: '正确性',
|
|
197
|
+
},
|
|
192
198
|
{
|
|
193
199
|
name: 'unused_let_binding',
|
|
194
200
|
defaultSeverity: 'warn',
|
package/dist/runner.js
CHANGED
|
@@ -111,6 +111,7 @@ const dynamic_doc_1 = require("./checks/dynamic-doc");
|
|
|
111
111
|
const loop_optimization_1 = require("./checks/loop-optimization");
|
|
112
112
|
const type_check_1 = require("./checks/type-check");
|
|
113
113
|
const redundant_if_1 = require("./checks/redundant-if");
|
|
114
|
+
const undeclared_setq_1 = require("./checks/undeclared-setq");
|
|
114
115
|
const format_indent_1 = require("./checks/format-indent");
|
|
115
116
|
const config_1 = require("./config");
|
|
116
117
|
const locale_1 = require("./locale");
|
|
@@ -235,6 +236,7 @@ function runChecks(content, file, config) {
|
|
|
235
236
|
addIfEnabled('loop_optimization', () => (0, loop_optimization_1.checkLoopOptimization)(content, file));
|
|
236
237
|
addIfEnabled('type_check', () => (0, type_check_1.checkTypeCheck)(content, file));
|
|
237
238
|
addIfEnabled('redundant_if', () => (0, redundant_if_1.checkRedundantIf)(content, file));
|
|
239
|
+
addIfEnabled('undeclared_setq', () => (0, undeclared_setq_1.checkUndeclaredSetq)(content, file));
|
|
238
240
|
addIfEnabled('format_indent', () => (0, format_indent_1.checkFormatIndent)(content, file, config.line_length.tab_width));
|
|
239
241
|
return issues;
|
|
240
242
|
}
|
package/dist/validate.js
CHANGED