@atlisp/lint 0.1.23 → 0.1.24
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/dist/atlisp-lint.default.json +90 -0
- package/dist/checks/arg-count.js +1 -1
- package/dist/checks/assoc-without-cdr.js +1 -1
- package/dist/checks/cond-duplicate.js +1 -1
- package/dist/checks/constant-condition.js +3 -3
- package/dist/checks/dangerous-calls.js +1 -1
- package/dist/checks/dangling-defun.js +1 -1
- package/dist/checks/double-not.js +1 -1
- package/dist/checks/empty-branch.js +1 -1
- package/dist/checks/empty-catch.js +1 -1
- package/dist/checks/function-complexity.js +1 -1
- package/dist/checks/global-naming.js +1 -1
- package/dist/checks/identical-branches.js +1 -1
- package/dist/checks/misplaced-else.js +1 -1
- package/dist/checks/multiple-setq.js +1 -1
- package/dist/checks/no-return.js +1 -1
- package/dist/checks/open-close.js +1 -1
- package/dist/checks/parameter-naming.js +1 -1
- package/dist/checks/quote-vs-function.js +1 -1
- package/dist/checks/recursive-call.js +1 -1
- 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-single-arg.js +1 -1
- package/dist/checks/single-arg-and-or.js +1 -1
- package/dist/checks/undeclared-setq.js +1 -1
- package/dist/checks/unused-catch-result.js +1 -1
- package/dist/checks/unused-let.js +1 -1
- package/dist/checks/unused-local-fun.js +1 -1
- package/dist/checks/unused-package-dep.js +1 -1
- package/dist/checks/unused-param.js +1 -1
- package/dist/checks/unused-variable.js +1 -1
- package/dist/checks/variable-shadow.js +1 -1
- package/dist/checks/while-constant.js +1 -1
- package/dist/formatters.d.ts +0 -1
- package/dist/formatters.js +0 -16
- package/dist/lib/lint-sbcl.lisp +161 -0
- package/dist/project.js +5 -5
- package/dist/runner.d.ts +0 -1
- package/dist/runner.js +148 -100
- package/dist/stub-packages.json +41 -0
- package/package.json +2 -2
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"locale": "zh",
|
|
3
|
+
"source": {
|
|
4
|
+
"globs": ["**/*.lsp"],
|
|
5
|
+
"exclude": ["**/node_modules/**", "**/vendor/**", "**/.git/**"]
|
|
6
|
+
},
|
|
7
|
+
"checks": {
|
|
8
|
+
"parens": "error",
|
|
9
|
+
"encoding": "warn",
|
|
10
|
+
"cl_syntax": "warn",
|
|
11
|
+
"quit_exit": "error",
|
|
12
|
+
"command_shell": "error",
|
|
13
|
+
"startapp": "warn",
|
|
14
|
+
"vl_registry_write": "warn",
|
|
15
|
+
"vlax_without_loading": "off",
|
|
16
|
+
"token_in_url": "warn",
|
|
17
|
+
"open_without_close": "warn",
|
|
18
|
+
"bare_function_names": "off",
|
|
19
|
+
"line_length": "warn",
|
|
20
|
+
"function_complexity": "warn",
|
|
21
|
+
"parameter_naming": "warn",
|
|
22
|
+
"unused_variable": "warn",
|
|
23
|
+
"missing_doc": "warn",
|
|
24
|
+
"error_handling": "warn",
|
|
25
|
+
"global_naming": "warn",
|
|
26
|
+
"extra_parens": "warn",
|
|
27
|
+
"arg_count": "warn",
|
|
28
|
+
"strcat_usage": "warn",
|
|
29
|
+
"cond_simplify": "warn",
|
|
30
|
+
"redundant_progn": "warn",
|
|
31
|
+
"quote_style": "warn",
|
|
32
|
+
"eq_usage": "warn",
|
|
33
|
+
"lambda_syntax": "warn",
|
|
34
|
+
"comment_style": "warn",
|
|
35
|
+
"empty_catch": "warn",
|
|
36
|
+
"nth_usage": "warn",
|
|
37
|
+
"append_single": "warn",
|
|
38
|
+
"setq_multiple": "warn",
|
|
39
|
+
"function_order": "off",
|
|
40
|
+
"magic_number": "off",
|
|
41
|
+
"mixed_indent": "warn",
|
|
42
|
+
"long_function_call": "warn",
|
|
43
|
+
"no_return": "warn",
|
|
44
|
+
"shadow_builtin": "warn",
|
|
45
|
+
"dynamic_doc": "warn",
|
|
46
|
+
"loop_optimization": "off",
|
|
47
|
+
"type_check": "off",
|
|
48
|
+
"redundant_if": "warn",
|
|
49
|
+
"trailing_whitespace": "warn",
|
|
50
|
+
"module_registration": "off",
|
|
51
|
+
"namespace_header": "off"
|
|
52
|
+
},
|
|
53
|
+
"line_length": {
|
|
54
|
+
"max": 100,
|
|
55
|
+
"tab_width": 2
|
|
56
|
+
},
|
|
57
|
+
"function_complexity": {
|
|
58
|
+
"max_lines": 60,
|
|
59
|
+
"max_nesting": 6
|
|
60
|
+
},
|
|
61
|
+
"cl_syntax": {
|
|
62
|
+
"keywords": ["&key"]
|
|
63
|
+
},
|
|
64
|
+
"dangerous_calls": {
|
|
65
|
+
"quit": "error",
|
|
66
|
+
"exit": "error",
|
|
67
|
+
"command_shell": "error",
|
|
68
|
+
"startapp": "warn",
|
|
69
|
+
"vl_registry_write": "warn"
|
|
70
|
+
},
|
|
71
|
+
"module_registration": {
|
|
72
|
+
"severity": "off",
|
|
73
|
+
"patterns": [],
|
|
74
|
+
"dirs": ["modules"]
|
|
75
|
+
},
|
|
76
|
+
"namespace_header": {
|
|
77
|
+
"severity": "off",
|
|
78
|
+
"package": "",
|
|
79
|
+
"deps": [],
|
|
80
|
+
"search_lines": 15
|
|
81
|
+
},
|
|
82
|
+
"bare_function_names": {
|
|
83
|
+
"allowlist": ["T", "nil"],
|
|
84
|
+
"namespace_pattern": "^[a-z]+:"
|
|
85
|
+
},
|
|
86
|
+
"sbcl": {
|
|
87
|
+
"walk_exclude": [".vscode", "vendor", ".git"],
|
|
88
|
+
"defmacro_allow_files": ["compat-cl"]
|
|
89
|
+
}
|
|
90
|
+
}
|
package/dist/checks/arg-count.js
CHANGED
|
@@ -5,7 +5,7 @@ const locale_1 = require("../locale");
|
|
|
5
5
|
const parser_1 = require("@atlisp/parser");
|
|
6
6
|
function checkArgCount(content, file) {
|
|
7
7
|
const issues = [];
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
const defuns = (0, parser_1.astFindListWithHead)(ast, 'defun');
|
|
10
10
|
for (const defun of defuns) {
|
|
11
11
|
if (!defun.children || defun.children.length < 3)
|
|
@@ -5,7 +5,7 @@ exports.checkAssocWithoutCdrAst = checkAssocWithoutCdrAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkAssocWithoutCdr(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkAssocWithoutCdrAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkAssocWithoutCdrAst(ast, file) {
|
|
@@ -18,7 +18,7 @@ function condKey(node) {
|
|
|
18
18
|
return '';
|
|
19
19
|
}
|
|
20
20
|
function checkCondDuplicate(content, file) {
|
|
21
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
21
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
22
22
|
return checkCondDuplicateAst(ast, file);
|
|
23
23
|
}
|
|
24
24
|
function checkCondDuplicateAst(ast, file) {
|
|
@@ -7,7 +7,7 @@ const parser_1 = require("@atlisp/parser");
|
|
|
7
7
|
const CONDITION_FORMS = new Set(['if', 'while']);
|
|
8
8
|
const CONSTANTS = new Set(['T', 'nil']);
|
|
9
9
|
function checkConstantCondition(content, file) {
|
|
10
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
10
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
11
11
|
return checkConstantConditionAst(ast, file);
|
|
12
12
|
}
|
|
13
13
|
function checkConstantConditionAst(ast, file) {
|
|
@@ -41,8 +41,8 @@ function checkConstantConditionAst(ast, file) {
|
|
|
41
41
|
continue;
|
|
42
42
|
const condTest = clause.children[0];
|
|
43
43
|
if (condTest.type === 'symbol' && condTest.name && CONSTANTS.has(condTest.name)) {
|
|
44
|
-
// Skip last clause with T
|
|
45
|
-
if (condTest.name === 'T' && i === condNode.children.length - 1)
|
|
44
|
+
// Skip last clause with T (only when there are multiple clauses — else/default pattern)
|
|
45
|
+
if (condTest.name === 'T' && i === condNode.children.length - 1 && i > 1)
|
|
46
46
|
continue;
|
|
47
47
|
issues.push({
|
|
48
48
|
file,
|
|
@@ -36,7 +36,7 @@ function isFunctionCall(list, name) {
|
|
|
36
36
|
return list.children[0].type === 'symbol' && list.children[0].name === name;
|
|
37
37
|
}
|
|
38
38
|
function checkDangerousCalls(content, file, config) {
|
|
39
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
39
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
40
40
|
return checkDangerousCallsAst(ast, file, config);
|
|
41
41
|
}
|
|
42
42
|
function checkDangerousCallsAst(ast, file, config) {
|
|
@@ -40,7 +40,7 @@ const fs = __importStar(require("fs"));
|
|
|
40
40
|
function checkDanglingDefun(file, allDefuns, allReferences) {
|
|
41
41
|
const issues = [];
|
|
42
42
|
const content = fs.readFileSync(file, 'utf-8');
|
|
43
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
43
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
44
44
|
const localDefuns = [];
|
|
45
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) {
|
|
@@ -5,7 +5,7 @@ exports.checkDoubleNotAst = checkDoubleNotAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkDoubleNot(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkDoubleNotAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkDoubleNotAst(ast, file) {
|
|
@@ -7,7 +7,7 @@ const parser_1 = require("@atlisp/parser");
|
|
|
7
7
|
// AutoLISP only has `if` for conditional branching; `while` with no body is valid (no-op loop)
|
|
8
8
|
const BRANCH_FORMS = new Set(['if']);
|
|
9
9
|
function checkEmptyBranch(content, file) {
|
|
10
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
10
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
11
11
|
return checkEmptyBranchAst(ast, file);
|
|
12
12
|
}
|
|
13
13
|
function checkEmptyBranchAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ const locale_1 = require("../locale");
|
|
|
5
5
|
const parser_1 = require("@atlisp/parser");
|
|
6
6
|
function checkEmptyCatch(content, file) {
|
|
7
7
|
const issues = [];
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
const catchNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'vl-catch-all-apply'));
|
|
10
10
|
for (const node of catchNodes) {
|
|
11
11
|
if (isInQuote(node))
|
|
@@ -5,7 +5,7 @@ exports.checkFunctionComplexityAst = checkFunctionComplexityAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkFunctionComplexity(content, file, maxLines, maxNesting) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkFunctionComplexityAst(ast, file, maxLines, maxNesting);
|
|
10
10
|
}
|
|
11
11
|
function checkFunctionComplexityAst(ast, file, maxLines, maxNesting) {
|
|
@@ -25,7 +25,7 @@ function isInsideLocalScope(node) {
|
|
|
25
25
|
}
|
|
26
26
|
function checkGlobalNaming(content, file) {
|
|
27
27
|
const issues = [];
|
|
28
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
28
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
29
29
|
const setqForms = (0, parser_1.astFindListWithHead)(ast, 'setq');
|
|
30
30
|
for (const form of setqForms) {
|
|
31
31
|
if (!form.children || form.children.length < 2)
|
|
@@ -27,7 +27,7 @@ function astEqual(a, b) {
|
|
|
27
27
|
return false;
|
|
28
28
|
}
|
|
29
29
|
function checkIdenticalBranches(content, file) {
|
|
30
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
30
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
31
31
|
return checkIdenticalBranchesAst(ast, file);
|
|
32
32
|
}
|
|
33
33
|
function checkIdenticalBranchesAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ exports.checkMisplacedElseAst = checkMisplacedElseAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkMisplacedElse(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkMisplacedElseAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkMisplacedElseAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ exports.checkMultipleSetqAst = checkMultipleSetqAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkMultipleSetq(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkMultipleSetqAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkMultipleSetqAst(ast, file) {
|
package/dist/checks/no-return.js
CHANGED
|
@@ -5,7 +5,7 @@ const locale_1 = require("../locale");
|
|
|
5
5
|
const parser_1 = require("@atlisp/parser");
|
|
6
6
|
function checkNoReturn(content, file) {
|
|
7
7
|
const issues = [];
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
const defuns = (0, parser_1.astFindListWithHead)(ast, 'defun');
|
|
10
10
|
for (const defun of defuns) {
|
|
11
11
|
if (!defun.children || defun.children.length < 3)
|
|
@@ -5,7 +5,7 @@ exports.checkOpenWithoutCloseAst = checkOpenWithoutCloseAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkOpenWithoutClose(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkOpenWithoutCloseAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkOpenWithoutCloseAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ exports.checkParameterNamingAst = checkParameterNamingAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkParameterNaming(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkParameterNamingAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkParameterNamingAst(ast, file) {
|
|
@@ -17,7 +17,7 @@ const HIGHER_ORDER_FUNCS = new Set([
|
|
|
17
17
|
'vl-every',
|
|
18
18
|
]);
|
|
19
19
|
function checkQuoteVsFunction(content, file) {
|
|
20
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
20
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
21
21
|
return checkQuoteVsFunctionAst(ast, file);
|
|
22
22
|
}
|
|
23
23
|
function checkQuoteVsFunctionAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ exports.checkRecursiveCallAst = checkRecursiveCallAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkRecursiveCall(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkRecursiveCallAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
/** Forms that provide conditional branching (exit condition for recursion) */
|
|
@@ -5,7 +5,7 @@ exports.checkRedundantLetAst = checkRedundantLetAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkRedundantLet(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkRedundantLetAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkRedundantLetAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ exports.checkRedundantNilElseAst = checkRedundantNilElseAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkRedundantNilElse(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkRedundantNilElseAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkRedundantNilElseAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ exports.checkRedundantPrognAst = checkRedundantPrognAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkRedundantProgn(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkRedundantPrognAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkRedundantPrognAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ exports.checkRedundantQuotesAst = checkRedundantQuotesAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkRedundantQuotes(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkRedundantQuotesAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkRedundantQuotesAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ exports.checkRedundantSetqAst = checkRedundantSetqAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkRedundantSetq(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkRedundantSetqAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkRedundantSetqAst(ast, file) {
|
|
@@ -6,7 +6,7 @@ const locale_1 = require("../locale");
|
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
const COMPARE_FUNCTIONS = new Set(['=', '/=', '<', '>', '<=', '>=', 'eq', 'equal']);
|
|
8
8
|
function checkSelfCompare(content, file) {
|
|
9
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
9
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
10
10
|
return checkSelfCompareAst(ast, file);
|
|
11
11
|
}
|
|
12
12
|
function checkSelfCompareAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ exports.checkSetqSingleArgAst = checkSetqSingleArgAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkSetqSingleArg(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkSetqSingleArgAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkSetqSingleArgAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ exports.checkSingleArgAndOrAst = checkSingleArgAndOrAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkSingleArgAndOr(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkSingleArgAndOrAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkSingleArgAndOrAst(ast, file) {
|
|
@@ -42,7 +42,7 @@ function findEnclosingDefun(node) {
|
|
|
42
42
|
}
|
|
43
43
|
function checkUndeclaredSetq(content, file) {
|
|
44
44
|
const issues = [];
|
|
45
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
45
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
46
46
|
const setqForms = (0, parser_1.astFindListWithHead)(ast, 'setq');
|
|
47
47
|
for (const setq of setqForms) {
|
|
48
48
|
if (!setq.children || setq.children.length < 2)
|
|
@@ -5,7 +5,7 @@ const locale_1 = require("../locale");
|
|
|
5
5
|
const parser_1 = require("@atlisp/parser");
|
|
6
6
|
function checkUnusedCatchResult(content, file) {
|
|
7
7
|
const issues = [];
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
const catchNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'vl-catch-all-apply'));
|
|
10
10
|
for (const node of catchNodes) {
|
|
11
11
|
if (isInQuote(node))
|
|
@@ -5,7 +5,7 @@ exports.checkUnusedLetBindingAst = checkUnusedLetBindingAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkUnusedLetBinding(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkUnusedLetBindingAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkUnusedLetBindingAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ exports.checkUnusedLocalFunAst = checkUnusedLocalFunAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkUnusedLocalFun(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkUnusedLocalFunAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkUnusedLocalFunAst(ast, file) {
|
|
@@ -40,7 +40,7 @@ const fs = __importStar(require("fs"));
|
|
|
40
40
|
function checkUnusedPackageDep(file) {
|
|
41
41
|
const issues = [];
|
|
42
42
|
const content = fs.readFileSync(file, 'utf-8');
|
|
43
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
43
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
44
44
|
const inPackageNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'in-package'));
|
|
45
45
|
if (inPackageNodes.length === 0)
|
|
46
46
|
return issues;
|
|
@@ -5,7 +5,7 @@ exports.checkUnusedParameterAst = checkUnusedParameterAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkUnusedParameter(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkUnusedParameterAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkUnusedParameterAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ exports.checkUnusedVariableAst = checkUnusedVariableAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkUnusedVariable(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkUnusedVariableAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkUnusedVariableAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ exports.checkVariableShadowAst = checkVariableShadowAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkVariableShadow(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkVariableShadowAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkVariableShadowAst(ast, file) {
|
|
@@ -5,7 +5,7 @@ exports.checkWhileConstantAst = checkWhileConstantAst;
|
|
|
5
5
|
const locale_1 = require("../locale");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
7
|
function checkWhileConstant(content, file) {
|
|
8
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
8
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
9
9
|
return checkWhileConstantAst(ast, file);
|
|
10
10
|
}
|
|
11
11
|
function checkWhileConstantAst(ast, file) {
|
package/dist/formatters.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Issue } from './types';
|
|
2
2
|
export declare function formatDefault(issues: Issue[], fileContents?: Map<string, string>): string;
|
|
3
|
-
export declare function formatHtml(issues: Issue[], errorCount: number, warningCount: number): string;
|
|
4
3
|
export declare function formatJson(issues: Issue[], errorCount?: number, warningCount?: number): string;
|
|
5
4
|
//# sourceMappingURL=formatters.d.ts.map
|
package/dist/formatters.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.formatDefault = formatDefault;
|
|
4
|
-
exports.formatHtml = formatHtml;
|
|
5
4
|
exports.formatJson = formatJson;
|
|
6
5
|
const locale_1 = require("./locale");
|
|
7
6
|
const colors = {
|
|
@@ -69,21 +68,6 @@ function formatDefault(issues, fileContents) {
|
|
|
69
68
|
}
|
|
70
69
|
return lines.join('\n');
|
|
71
70
|
}
|
|
72
|
-
function formatHtml(issues, errorCount, warningCount) {
|
|
73
|
-
let html = `<!DOCTYPE html>
|
|
74
|
-
<html lang="en">
|
|
75
|
-
<head><meta charset="UTF-8"><title>@atlisp/lint Report</title></head>
|
|
76
|
-
<body>
|
|
77
|
-
<h1>@atlisp/lint Report</h1>
|
|
78
|
-
<p>${errorCount} error(s), ${warningCount} warning(s)</p>
|
|
79
|
-
<ul>
|
|
80
|
-
`;
|
|
81
|
-
for (const iss of issues) {
|
|
82
|
-
html += ` <li>${iss.severity}: ${iss.file}:${iss.line} — ${iss.message}</li>\n`;
|
|
83
|
-
}
|
|
84
|
-
html += '</ul>\n</body>\n</html>';
|
|
85
|
-
return html;
|
|
86
|
-
}
|
|
87
71
|
function formatJson(issues, errorCount, warningCount) {
|
|
88
72
|
return JSON.stringify({
|
|
89
73
|
issues,
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
;; @lisp SBCL Lint — syntax validation via SBCL
|
|
2
|
+
;; Usage: sbcl --script lint-sbcl.lisp <src-dir> <stub-packages.json> <walk-exclude-json> <defmacro-allow-files-json> <locale>
|
|
3
|
+
|
|
4
|
+
(require :uiop)
|
|
5
|
+
|
|
6
|
+
(defparameter *errors* 0)
|
|
7
|
+
(defparameter *warnings* 0)
|
|
8
|
+
(defparameter *checked* 0)
|
|
9
|
+
(defparameter *locale* "zh")
|
|
10
|
+
|
|
11
|
+
;; ── i18n ──────────────────────────────────────────────────────────────
|
|
12
|
+
(defun i18n (key)
|
|
13
|
+
(cdr (assoc key
|
|
14
|
+
(if (string= *locale* "zh")
|
|
15
|
+
'((:not-found . "not found symbol")
|
|
16
|
+
(:syntax-error . "syntax error")
|
|
17
|
+
(:vec-syntax . "#( vector syntax")
|
|
18
|
+
(:char-syntax . "# backslash char literal")
|
|
19
|
+
(:eval-syntax . "#. read-time eval")
|
|
20
|
+
(:defmacro . "defmacro")
|
|
21
|
+
(:trailing-ws . "trailing whitespace")
|
|
22
|
+
(:ok . "OK")
|
|
23
|
+
(:fail . "FAIL")
|
|
24
|
+
(:header . " @lisp SBCL Lint")
|
|
25
|
+
(:source . " Source")
|
|
26
|
+
(:found . " Found ~d .lsp files")
|
|
27
|
+
(:scanned . " Scanned")
|
|
28
|
+
(:errors . " Errors")
|
|
29
|
+
(:warnings . " Warnings"))
|
|
30
|
+
'((:not-found . "not found symbol")
|
|
31
|
+
(:syntax-error . "syntax error")
|
|
32
|
+
(:vec-syntax . "#( vector syntax")
|
|
33
|
+
(:char-syntax . "# backslash char literal")
|
|
34
|
+
(:eval-syntax . "#. read-time eval")
|
|
35
|
+
(:defmacro . "defmacro")
|
|
36
|
+
(:trailing-ws . "trailing whitespace")
|
|
37
|
+
(:ok . "OK")
|
|
38
|
+
(:fail . "FAIL")
|
|
39
|
+
(:header . " @lisp SBCL Lint")
|
|
40
|
+
(:source . " Source")
|
|
41
|
+
(:found . " Found ~d .lsp files")
|
|
42
|
+
(:scanned . " Scanned")
|
|
43
|
+
(:errors . " Errors")
|
|
44
|
+
(:warnings . " Warnings"))))))
|
|
45
|
+
|
|
46
|
+
;; ── Stub packages from external file ──────────────────────────────────
|
|
47
|
+
(defun load-stub-packages (file-path)
|
|
48
|
+
(flet ((ensure-pkg (name &optional use-list)
|
|
49
|
+
(unless (find-package name)
|
|
50
|
+
(make-package name :use (or use-list '())))))
|
|
51
|
+
(with-open-file (s file-path :direction :input)
|
|
52
|
+
(let ((packages (read s)))
|
|
53
|
+
(dolist (pkg packages)
|
|
54
|
+
(ensure-pkg (first pkg) (second pkg)))))))
|
|
55
|
+
|
|
56
|
+
;; ── File walking ──────────────────────────────────────────────────────
|
|
57
|
+
(defun source-files (dir)
|
|
58
|
+
(sort
|
|
59
|
+
(remove-if-not
|
|
60
|
+
(lambda (p)
|
|
61
|
+
(let ((n (pathname-type p)))
|
|
62
|
+
(and n (string-equal n "lsp"))))
|
|
63
|
+
(uiop:directory-files dir))
|
|
64
|
+
'string< :key #'namestring))
|
|
65
|
+
|
|
66
|
+
(defun walk-tree (dir exclude-dirs)
|
|
67
|
+
(let ((result (source-files dir)))
|
|
68
|
+
(dolist (sub (uiop:subdirectories dir))
|
|
69
|
+
(let ((name (car (last (pathname-directory sub)))))
|
|
70
|
+
(unless (find name exclude-dirs :test 'string=)
|
|
71
|
+
(setf result (append result (walk-tree sub exclude-dirs))))))
|
|
72
|
+
result))
|
|
73
|
+
|
|
74
|
+
;; ── Per-file check ────────────────────────────────────────────────────
|
|
75
|
+
(defun check-file (path src-dir defmacro-allow-files)
|
|
76
|
+
(incf *checked*)
|
|
77
|
+
(let* ((rel (enough-namestring path src-dir))
|
|
78
|
+
(fullname (format nil "~a.~a" (pathname-name path) (pathname-type path))))
|
|
79
|
+
(format t " ~a ... " rel)
|
|
80
|
+
(force-output)
|
|
81
|
+
;; Syntax check via READ
|
|
82
|
+
(handler-case
|
|
83
|
+
(with-open-file (s path :direction :input)
|
|
84
|
+
(loop for form = (read s nil :eof)
|
|
85
|
+
until (eq form :eof)))
|
|
86
|
+
(error (e)
|
|
87
|
+
(let ((msg (princ-to-string e)))
|
|
88
|
+
(cond
|
|
89
|
+
((search "not found" msg)
|
|
90
|
+
(format t "~% [NOTE] ~a: ~a~%" rel (i18n :not-found))
|
|
91
|
+
(incf *warnings*))
|
|
92
|
+
(t
|
|
93
|
+
(format t "~a~% [ERROR] ~a: ~a~%" (i18n :fail) rel (i18n :syntax-error))
|
|
94
|
+
(incf *errors*)
|
|
95
|
+
(return-from check-file))))))
|
|
96
|
+
;; Trailing whitespace
|
|
97
|
+
(with-open-file (s path :direction :input)
|
|
98
|
+
(loop for line = (read-line s nil nil)
|
|
99
|
+
for lineno from 1
|
|
100
|
+
while line
|
|
101
|
+
when (and (> (length line) 0)
|
|
102
|
+
(find (char line (1- (length line))) '(#\Space #\Tab)))
|
|
103
|
+
do (progn
|
|
104
|
+
(format t "~% [WARN] ~a line ~d: ~a~%" rel lineno (i18n :trailing-ws))
|
|
105
|
+
(incf *warnings*))))
|
|
106
|
+
;; CL-ism checks: raw content scan
|
|
107
|
+
(with-open-file (s path :direction :input)
|
|
108
|
+
(let ((content (make-string (file-length s))))
|
|
109
|
+
(file-position s 0)
|
|
110
|
+
(read-sequence content s)
|
|
111
|
+
(when (search "#(" content)
|
|
112
|
+
(format t "~% [WARN] ~a: ~a~%" rel (i18n :vec-syntax))
|
|
113
|
+
(incf *warnings*))
|
|
114
|
+
(when (search "#\\" content)
|
|
115
|
+
(format t "~% [WARN] ~a: ~a~%" rel (i18n :char-syntax))
|
|
116
|
+
(incf *warnings*))
|
|
117
|
+
(when (search "#." content)
|
|
118
|
+
(format t "~% [WARN] ~a: ~a~%" rel (i18n :eval-syntax))
|
|
119
|
+
(incf *warnings*))
|
|
120
|
+
(when (and (search "defmacro" content)
|
|
121
|
+
(not (some (lambda (s) (search s fullname)) defmacro-allow-files)))
|
|
122
|
+
(format t "~% [WARN] ~a: ~a~%" rel (i18n :defmacro))
|
|
123
|
+
(incf *warnings*))))
|
|
124
|
+
(format t "~a~%" (i18n :ok))))
|
|
125
|
+
|
|
126
|
+
;; ── Main ──────────────────────────────────────────────────────────────
|
|
127
|
+
(defun main ()
|
|
128
|
+
(let* ((args (uiop:command-line-arguments))
|
|
129
|
+
(src-dir (first args))
|
|
130
|
+
(stub-file (second args))
|
|
131
|
+
(walk-exclude (if (third args)
|
|
132
|
+
(read-from-string (third args))
|
|
133
|
+
'(".vscode" "test" "experiment" "tools" ".git")))
|
|
134
|
+
(defmacro-allow (if (fourth args)
|
|
135
|
+
(read-from-string (fourth args))
|
|
136
|
+
'("compat-cl")))
|
|
137
|
+
(locale (fifth args)))
|
|
138
|
+
(when locale (setf *locale* locale))
|
|
139
|
+
(load-stub-packages stub-file)
|
|
140
|
+
|
|
141
|
+
(format t "~%==================================================~%")
|
|
142
|
+
(format t "~a~%" (i18n :header))
|
|
143
|
+
(format t "~a: ~a~%" (i18n :source) (namestring (pathname src-dir)))
|
|
144
|
+
(format t "==================================================~%~%")
|
|
145
|
+
(setf *errors* 0 *warnings* 0 *checked* 0)
|
|
146
|
+
|
|
147
|
+
(let ((files (walk-tree src-dir walk-exclude)))
|
|
148
|
+
(format t "~a~%~%" (format nil (i18n :found) (length files)))
|
|
149
|
+
(dolist (f files)
|
|
150
|
+
(check-file f src-dir defmacro-allow)))
|
|
151
|
+
|
|
152
|
+
(format t "~%==================================================~%")
|
|
153
|
+
(format t "~a: ~d~%" (i18n :scanned) *checked*)
|
|
154
|
+
(format t "~a: ~d~%" (i18n :errors) *errors*)
|
|
155
|
+
(format t "~a: ~d~%" (i18n :warnings) *warnings*)
|
|
156
|
+
(format t "==================================================~%~%")
|
|
157
|
+
(if (> *errors* 0)
|
|
158
|
+
(uiop:quit 1)
|
|
159
|
+
(uiop:quit 0))))
|
|
160
|
+
|
|
161
|
+
(main)
|
package/dist/project.js
CHANGED
|
@@ -44,7 +44,7 @@ const duplicate_defun_1 = require("./checks/duplicate-defun");
|
|
|
44
44
|
const locale_1 = require("./locale");
|
|
45
45
|
function collectDefuns(file) {
|
|
46
46
|
const content = fs.readFileSync(file, 'utf-8');
|
|
47
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
47
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
48
48
|
const results = [];
|
|
49
49
|
const defunNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'defun') || (0, parser_1.astIsList)(n, 'defun-q'));
|
|
50
50
|
for (const node of defunNodes) {
|
|
@@ -56,7 +56,7 @@ function collectDefuns(file) {
|
|
|
56
56
|
}
|
|
57
57
|
function collectReferences(file) {
|
|
58
58
|
const content = fs.readFileSync(file, 'utf-8');
|
|
59
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
59
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
60
60
|
const results = [];
|
|
61
61
|
const allCalls = (0, parser_1.astFindAll)(ast, n => {
|
|
62
62
|
if (n.type !== 'list' || !n.children || n.children.length === 0)
|
|
@@ -73,7 +73,7 @@ function collectReferences(file) {
|
|
|
73
73
|
}
|
|
74
74
|
function countFunctionArgs(file) {
|
|
75
75
|
const content = fs.readFileSync(file, 'utf-8');
|
|
76
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
76
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
77
77
|
const result = new Map();
|
|
78
78
|
const defunNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'defun') || (0, parser_1.astIsList)(n, 'defun-q'));
|
|
79
79
|
for (const node of defunNodes) {
|
|
@@ -102,7 +102,7 @@ function countFunctionArgs(file) {
|
|
|
102
102
|
}
|
|
103
103
|
function countCallArgs(file) {
|
|
104
104
|
const content = fs.readFileSync(file, 'utf-8');
|
|
105
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
105
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
106
106
|
const result = new Map();
|
|
107
107
|
const allCalls = (0, parser_1.astFindAll)(ast, n => {
|
|
108
108
|
if (n.type !== 'list' || !n.children || n.children.length === 0)
|
|
@@ -122,7 +122,7 @@ function countCallArgs(file) {
|
|
|
122
122
|
}
|
|
123
123
|
function findModuleDeps(filepath) {
|
|
124
124
|
const content = fs.readFileSync(filepath, 'utf-8');
|
|
125
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
125
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
126
126
|
const imports = [];
|
|
127
127
|
const exports = [];
|
|
128
128
|
const inPackageNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'in-package'));
|
package/dist/runner.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Issue, LintConfig } from './types';
|
|
2
2
|
export declare function runChecks(content: string, file: string, config: LintConfig): Issue[];
|
|
3
|
-
export declare function runChecksWithVisitorWrapper(content: string, file: string, config: LintConfig): Issue[];
|
|
4
3
|
export declare function lintFiles(files: string[], config: LintConfig, rootDir: string): Issue[];
|
|
5
4
|
export declare function lintFilesParallel(files: string[], config: LintConfig, rootDir: string): Promise<Issue[]>;
|
|
6
5
|
export declare const FIXABLE_RULES: Set<string>;
|
package/dist/runner.js
CHANGED
|
@@ -35,7 +35,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.FIXABLE_RULES = void 0;
|
|
37
37
|
exports.runChecks = runChecks;
|
|
38
|
-
exports.runChecksWithVisitorWrapper = runChecksWithVisitorWrapper;
|
|
39
38
|
exports.lintFiles = lintFiles;
|
|
40
39
|
exports.lintFilesParallel = lintFilesParallel;
|
|
41
40
|
exports.applyFixes = applyFixes;
|
|
@@ -100,12 +99,17 @@ function runChecks(content, file, config) {
|
|
|
100
99
|
const severity = checks[rule];
|
|
101
100
|
if (!severity || severity === 'off')
|
|
102
101
|
return;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
try {
|
|
103
|
+
const results = fn();
|
|
104
|
+
for (const r of results) {
|
|
105
|
+
if (!(0, disable_1.isDisabled)(r.rule, r.line, disableMap)) {
|
|
106
|
+
issues.push(r);
|
|
107
|
+
}
|
|
107
108
|
}
|
|
108
109
|
}
|
|
110
|
+
catch {
|
|
111
|
+
// Check failed, continue with remaining checks
|
|
112
|
+
}
|
|
109
113
|
}
|
|
110
114
|
function addIssues(ruleIssues) {
|
|
111
115
|
for (const r of ruleIssues) {
|
|
@@ -173,17 +177,17 @@ function runChecks(content, file, config) {
|
|
|
173
177
|
]);
|
|
174
178
|
const needsAst = Array.from(astRules).some(r => checks[r] !== 'off');
|
|
175
179
|
if (needsAst) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
try {
|
|
181
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
182
|
+
const visitorIssues = (0, visitor_runner_1.runChecksWithVisitor)(ast, file, config, disableMap);
|
|
183
|
+
addIssues(visitorIssues);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// AST-based checks are skipped, text-based checks above still report issues
|
|
187
|
+
}
|
|
179
188
|
}
|
|
180
189
|
return issues;
|
|
181
190
|
}
|
|
182
|
-
function runChecksWithVisitorWrapper(content, file, config) {
|
|
183
|
-
const disableMap = (0, disable_1.parseDisableComments)(content);
|
|
184
|
-
const ast = (0, parser_1.parseAst)(content);
|
|
185
|
-
return (0, visitor_runner_1.runChecksWithVisitor)(ast, file, config, disableMap);
|
|
186
|
-
}
|
|
187
191
|
function lintFiles(files, config, rootDir) {
|
|
188
192
|
(0, locale_1.setLocale)(config.locale || 'zh');
|
|
189
193
|
const allIssues = [];
|
|
@@ -321,6 +325,7 @@ exports.FIXABLE_RULES = new Set([
|
|
|
321
325
|
]);
|
|
322
326
|
function applyFixes(issues, content, filepath) {
|
|
323
327
|
const lines = content.split('\n');
|
|
328
|
+
const fixedLines = new Set();
|
|
324
329
|
const fixesByRule = new Map();
|
|
325
330
|
for (const iss of issues) {
|
|
326
331
|
if (iss.file !== filepath)
|
|
@@ -330,7 +335,6 @@ function applyFixes(issues, content, filepath) {
|
|
|
330
335
|
fixesByRule.set(iss.rule, set);
|
|
331
336
|
}
|
|
332
337
|
const fixableRules = exports.FIXABLE_RULES;
|
|
333
|
-
let result = content;
|
|
334
338
|
for (const [rule, lineSet] of fixesByRule) {
|
|
335
339
|
if (!fixableRules.has(rule))
|
|
336
340
|
continue;
|
|
@@ -339,30 +343,38 @@ function applyFixes(issues, content, filepath) {
|
|
|
339
343
|
const idx = line - 1;
|
|
340
344
|
if (idx < 0 || idx >= lines.length)
|
|
341
345
|
continue;
|
|
342
|
-
const lineContent =
|
|
346
|
+
const lineContent = lines[idx];
|
|
343
347
|
switch (rule) {
|
|
344
348
|
case 'redundant_quotes': {
|
|
345
349
|
const fixed = lineContent.replace(/''(\S)/g, "'$1");
|
|
346
|
-
if (fixed !== lineContent)
|
|
347
|
-
|
|
350
|
+
if (fixed !== lineContent) {
|
|
351
|
+
lines[idx] = fixed;
|
|
352
|
+
fixedLines.add(idx);
|
|
353
|
+
}
|
|
348
354
|
break;
|
|
349
355
|
}
|
|
350
356
|
case 'double_not': {
|
|
351
357
|
const fixed = lineContent.replace(/\(not\s+\(not\s+/g, '(not ');
|
|
352
|
-
if (fixed !== lineContent)
|
|
353
|
-
|
|
358
|
+
if (fixed !== lineContent) {
|
|
359
|
+
lines[idx] = fixed;
|
|
360
|
+
fixedLines.add(idx);
|
|
361
|
+
}
|
|
354
362
|
break;
|
|
355
363
|
}
|
|
356
364
|
case 'redundant_nil_else': {
|
|
357
365
|
const fixed = lineContent.replace(/\s+nil\s*\)$/, ')');
|
|
358
|
-
if (fixed !== lineContent)
|
|
359
|
-
|
|
366
|
+
if (fixed !== lineContent) {
|
|
367
|
+
lines[idx] = fixed;
|
|
368
|
+
fixedLines.add(idx);
|
|
369
|
+
}
|
|
360
370
|
break;
|
|
361
371
|
}
|
|
362
372
|
case 'single_arg_and_or': {
|
|
363
373
|
const m = lineContent.match(/\((and|or)\s+([^)\s]+)\s*\)/);
|
|
364
|
-
if (m)
|
|
365
|
-
|
|
374
|
+
if (m) {
|
|
375
|
+
lines[idx] = m[2];
|
|
376
|
+
fixedLines.add(idx);
|
|
377
|
+
}
|
|
366
378
|
break;
|
|
367
379
|
}
|
|
368
380
|
case 'redundant_progn': {
|
|
@@ -374,16 +386,20 @@ function applyFixes(issues, content, filepath) {
|
|
|
374
386
|
const inner = lineContent.slice(proIdx + 7, close);
|
|
375
387
|
const rest = lineContent.slice(close + 1);
|
|
376
388
|
const fixed = beforePro + inner + rest;
|
|
377
|
-
if (fixed !== lineContent)
|
|
378
|
-
|
|
389
|
+
if (fixed !== lineContent) {
|
|
390
|
+
lines[idx] = fixed;
|
|
391
|
+
fixedLines.add(idx);
|
|
392
|
+
}
|
|
379
393
|
}
|
|
380
394
|
}
|
|
381
395
|
break;
|
|
382
396
|
}
|
|
383
397
|
case 'redundant_let': {
|
|
384
398
|
const fixed = lineContent.replace(/\(let\s+\(\)\s*/, '(progn ');
|
|
385
|
-
if (fixed !== lineContent)
|
|
386
|
-
|
|
399
|
+
if (fixed !== lineContent) {
|
|
400
|
+
lines[idx] = fixed;
|
|
401
|
+
fixedLines.add(idx);
|
|
402
|
+
}
|
|
387
403
|
break;
|
|
388
404
|
}
|
|
389
405
|
case 'quote_style': {
|
|
@@ -395,8 +411,10 @@ function applyFixes(issues, content, filepath) {
|
|
|
395
411
|
const quoted = lineContent.slice(qIdx + 7, close);
|
|
396
412
|
const rest = lineContent.slice(close + 1);
|
|
397
413
|
const fixed = beforeQ + "'" + quoted + rest;
|
|
398
|
-
if (fixed !== lineContent)
|
|
399
|
-
|
|
414
|
+
if (fixed !== lineContent) {
|
|
415
|
+
lines[idx] = fixed;
|
|
416
|
+
fixedLines.add(idx);
|
|
417
|
+
}
|
|
400
418
|
}
|
|
401
419
|
}
|
|
402
420
|
break;
|
|
@@ -410,8 +428,10 @@ function applyFixes(issues, content, filepath) {
|
|
|
410
428
|
const inner = lineContent.slice(pIdx + 7, close);
|
|
411
429
|
const rest = lineContent.slice(close + 1);
|
|
412
430
|
const fixed = beforeP + inner + rest;
|
|
413
|
-
if (fixed !== lineContent)
|
|
414
|
-
|
|
431
|
+
if (fixed !== lineContent) {
|
|
432
|
+
lines[idx] = fixed;
|
|
433
|
+
fixedLines.add(idx);
|
|
434
|
+
}
|
|
415
435
|
}
|
|
416
436
|
}
|
|
417
437
|
break;
|
|
@@ -436,18 +456,18 @@ function applyFixes(issues, content, filepath) {
|
|
|
436
456
|
if (thenForm && elseForm) {
|
|
437
457
|
const rest = lineContent.slice(ifClose + 1);
|
|
438
458
|
const fixed = indent + '(if ' + condVar + ' ' + elseForm + ' ' + thenForm + ')' + rest;
|
|
439
|
-
if (fixed !== lineContent)
|
|
440
|
-
|
|
459
|
+
if (fixed !== lineContent) {
|
|
460
|
+
lines[idx] = fixed;
|
|
461
|
+
fixedLines.add(idx);
|
|
462
|
+
}
|
|
441
463
|
}
|
|
442
464
|
}
|
|
443
465
|
break;
|
|
444
466
|
}
|
|
445
467
|
case 'redundant_setq': {
|
|
446
|
-
const
|
|
447
|
-
const trimmed = allLines[idx].trim();
|
|
468
|
+
const trimmed = lines[idx].trim();
|
|
448
469
|
if (/^\(setq\s+(\S+)\s+\1\s*\)$/.test(trimmed)) {
|
|
449
|
-
|
|
450
|
-
result = allLines.join('\n');
|
|
470
|
+
lines.splice(idx, 1);
|
|
451
471
|
}
|
|
452
472
|
break;
|
|
453
473
|
}
|
|
@@ -456,12 +476,15 @@ function applyFixes(issues, content, filepath) {
|
|
|
456
476
|
if (fixed !== lineContent) {
|
|
457
477
|
const merged = lineContent.replace(/^\(\s*setq\s+/, '(setq ');
|
|
458
478
|
if (merged.includes('setq') && !merged.match(/\)\(/)) {
|
|
459
|
-
|
|
479
|
+
lines[idx] = merged;
|
|
480
|
+
fixedLines.add(idx);
|
|
460
481
|
}
|
|
461
482
|
else {
|
|
462
483
|
const simplified = lineContent.replace(/\)\(setq\s+/g, '\n');
|
|
463
|
-
if (simplified !== lineContent)
|
|
464
|
-
|
|
484
|
+
if (simplified !== lineContent) {
|
|
485
|
+
lines[idx] = simplified;
|
|
486
|
+
fixedLines.add(idx);
|
|
487
|
+
}
|
|
465
488
|
}
|
|
466
489
|
}
|
|
467
490
|
break;
|
|
@@ -475,8 +498,10 @@ function applyFixes(issues, content, filepath) {
|
|
|
475
498
|
const listMatch = inner.match(/^\(list\s+(.*)\)\s*$/);
|
|
476
499
|
if (listMatch) {
|
|
477
500
|
const fixed = lineContent.slice(0, aIdx) + '(cons ' + listMatch[1] + ')' + lineContent.slice(close + 1);
|
|
478
|
-
if (fixed !== lineContent)
|
|
479
|
-
|
|
501
|
+
if (fixed !== lineContent) {
|
|
502
|
+
lines[idx] = fixed;
|
|
503
|
+
fixedLines.add(idx);
|
|
504
|
+
}
|
|
480
505
|
}
|
|
481
506
|
}
|
|
482
507
|
}
|
|
@@ -484,40 +509,48 @@ function applyFixes(issues, content, filepath) {
|
|
|
484
509
|
}
|
|
485
510
|
case 'nth_usage': {
|
|
486
511
|
const fixed = lineContent.replace(/\(nth\s+0\s+/g, '(car ');
|
|
487
|
-
if (fixed !== lineContent)
|
|
488
|
-
|
|
512
|
+
if (fixed !== lineContent) {
|
|
513
|
+
lines[idx] = fixed;
|
|
514
|
+
fixedLines.add(idx);
|
|
515
|
+
}
|
|
489
516
|
break;
|
|
490
517
|
}
|
|
491
518
|
case 'setq_single_arg': {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
allLines.splice(idx, 1);
|
|
495
|
-
result = allLines.join('\n');
|
|
519
|
+
if (/^\s*\(setq\s+\S+\s*\)\s*$/.test(lines[idx])) {
|
|
520
|
+
lines.splice(idx, 1);
|
|
496
521
|
}
|
|
497
522
|
break;
|
|
498
523
|
}
|
|
499
524
|
case 'extra_parens': {
|
|
500
525
|
const fixed = lineContent.replace(/\){2,}/g, ')');
|
|
501
|
-
if (fixed !== lineContent)
|
|
502
|
-
|
|
526
|
+
if (fixed !== lineContent) {
|
|
527
|
+
lines[idx] = fixed;
|
|
528
|
+
fixedLines.add(idx);
|
|
529
|
+
}
|
|
503
530
|
break;
|
|
504
531
|
}
|
|
505
532
|
case 'empty_branch': {
|
|
506
533
|
const fixed = lineContent.replace(/\(if\s+\S+\s*\)/g, m => m.slice(0, -1) + ' nil)');
|
|
507
|
-
if (fixed !== lineContent)
|
|
508
|
-
|
|
534
|
+
if (fixed !== lineContent) {
|
|
535
|
+
lines[idx] = fixed;
|
|
536
|
+
fixedLines.add(idx);
|
|
537
|
+
}
|
|
509
538
|
break;
|
|
510
539
|
}
|
|
511
540
|
case 'comment_style': {
|
|
512
541
|
const fixed = lineContent.replace(/^(\s*;)([^ ;|].*)$/gm, '$1 $2');
|
|
513
|
-
if (fixed !== lineContent)
|
|
514
|
-
|
|
542
|
+
if (fixed !== lineContent) {
|
|
543
|
+
lines[idx] = fixed;
|
|
544
|
+
fixedLines.add(idx);
|
|
545
|
+
}
|
|
515
546
|
break;
|
|
516
547
|
}
|
|
517
548
|
case 'eq_usage': {
|
|
518
549
|
const fixed = lineContent.replace(/\(eq\s+(\S+)\s+(\d+)\s*\)/g, '(= $1 $2)');
|
|
519
|
-
if (fixed !== lineContent)
|
|
520
|
-
|
|
550
|
+
if (fixed !== lineContent) {
|
|
551
|
+
lines[idx] = fixed;
|
|
552
|
+
fixedLines.add(idx);
|
|
553
|
+
}
|
|
521
554
|
break;
|
|
522
555
|
}
|
|
523
556
|
case 'cond_simplify': {
|
|
@@ -538,8 +571,10 @@ function applyFixes(issues, content, filepath) {
|
|
|
538
571
|
const bodyExpr = branchContent.slice(space).trim();
|
|
539
572
|
const rest = lineContent.slice(condEnd + 1);
|
|
540
573
|
const fixed = indent + '(if ' + testExpr + ' ' + bodyExpr + ')' + rest;
|
|
541
|
-
if (fixed !== lineContent)
|
|
542
|
-
|
|
574
|
+
if (fixed !== lineContent) {
|
|
575
|
+
lines[idx] = fixed;
|
|
576
|
+
fixedLines.add(idx);
|
|
577
|
+
}
|
|
543
578
|
}
|
|
544
579
|
}
|
|
545
580
|
}
|
|
@@ -549,34 +584,31 @@ function applyFixes(issues, content, filepath) {
|
|
|
549
584
|
}
|
|
550
585
|
case 'self_compare': {
|
|
551
586
|
const fixed = lineContent.replace(/\((?:[=/<>]+|eq|equal)\s+(\S+)\s+\1\s*\)/g, 'T');
|
|
552
|
-
if (fixed !== lineContent)
|
|
553
|
-
|
|
587
|
+
if (fixed !== lineContent) {
|
|
588
|
+
lines[idx] = fixed;
|
|
589
|
+
fixedLines.add(idx);
|
|
590
|
+
}
|
|
554
591
|
break;
|
|
555
592
|
}
|
|
556
593
|
case 'while_constant': {
|
|
557
|
-
const
|
|
558
|
-
const trimmed = allLines[idx].trim();
|
|
594
|
+
const trimmed = lines[idx].trim();
|
|
559
595
|
const nilMatch = trimmed.match(/^\(\s*while\s+nil\s+(.*)\)\s*$/);
|
|
560
596
|
if (nilMatch) {
|
|
561
|
-
|
|
562
|
-
result = allLines.join('\n');
|
|
597
|
+
lines[idx] = lines[idx].replace(trimmed, 'nil');
|
|
563
598
|
}
|
|
564
599
|
break;
|
|
565
600
|
}
|
|
566
601
|
case 'redundant_list': {
|
|
567
602
|
const fixed = lineContent.replace(/\(\s*list\s*\)/g, 'nil');
|
|
568
|
-
if (fixed !== lineContent)
|
|
569
|
-
|
|
603
|
+
if (fixed !== lineContent) {
|
|
604
|
+
lines[idx] = fixed;
|
|
605
|
+
fixedLines.add(idx);
|
|
606
|
+
}
|
|
570
607
|
break;
|
|
571
608
|
}
|
|
572
609
|
}
|
|
573
610
|
}
|
|
574
611
|
}
|
|
575
|
-
return result;
|
|
576
|
-
}
|
|
577
|
-
function replaceLine(content, lineIdx, newLine) {
|
|
578
|
-
const lines = content.split('\n');
|
|
579
|
-
lines[lineIdx] = newLine;
|
|
580
612
|
return lines.join('\n');
|
|
581
613
|
}
|
|
582
614
|
function parseIgnoreFile(rootDir) {
|
|
@@ -593,23 +625,51 @@ const UTF8_BOM = 0xfeff;
|
|
|
593
625
|
function fixFile(filepath) {
|
|
594
626
|
const fixes = [];
|
|
595
627
|
let content = fs.readFileSync(filepath, 'utf-8');
|
|
596
|
-
|
|
597
|
-
.split('\n')
|
|
598
|
-
.map(l => l.replace(/[ \t]+$/, ''))
|
|
599
|
-
.join('\n');
|
|
600
|
-
if (wsFixed !== content) {
|
|
601
|
-
content = wsFixed;
|
|
602
|
-
fixes.push('trailing_whitespace');
|
|
603
|
-
}
|
|
628
|
+
// 1. Remove BOM
|
|
604
629
|
if (content.charCodeAt(0) === UTF8_BOM) {
|
|
605
630
|
content = content.slice(1);
|
|
606
631
|
fixes.push('encoding (BOM)');
|
|
607
632
|
}
|
|
633
|
+
// 2. Single line pass: trailing whitespace + empty comment lines
|
|
634
|
+
const origLines = content.split('\n');
|
|
635
|
+
let wsChanged = false;
|
|
636
|
+
let ecChanged = false;
|
|
637
|
+
const processed = origLines.map(l => {
|
|
638
|
+
const stripped = l.replace(/[ \t]+$/, '');
|
|
639
|
+
if (stripped !== l)
|
|
640
|
+
wsChanged = true;
|
|
641
|
+
if (/^\s*;\s*$/.test(stripped)) {
|
|
642
|
+
ecChanged = true;
|
|
643
|
+
return '';
|
|
644
|
+
}
|
|
645
|
+
return stripped;
|
|
646
|
+
});
|
|
647
|
+
if (wsChanged)
|
|
648
|
+
fixes.push('trailing_whitespace');
|
|
649
|
+
if (ecChanged)
|
|
650
|
+
fixes.push('empty_comment');
|
|
651
|
+
if (wsChanged || ecChanged)
|
|
652
|
+
content = processed.join('\n');
|
|
653
|
+
// 3. Count parens (string/comment aware), fix trailing excess
|
|
608
654
|
let openCount = 0;
|
|
609
655
|
let closeCount = 0;
|
|
610
|
-
let
|
|
611
|
-
for (let i = 0; i <
|
|
612
|
-
const ch =
|
|
656
|
+
let inStr = false;
|
|
657
|
+
for (let i = 0; i < content.length; i++) {
|
|
658
|
+
const ch = content[i];
|
|
659
|
+
if (ch === '"') {
|
|
660
|
+
inStr = !inStr;
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
if (inStr) {
|
|
664
|
+
if (ch === '\\')
|
|
665
|
+
i++;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (ch === ';') {
|
|
669
|
+
const nl = content.indexOf('\n', i);
|
|
670
|
+
i = nl !== -1 ? nl : content.length;
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
613
673
|
if (ch === '(')
|
|
614
674
|
openCount++;
|
|
615
675
|
else if (ch === ')')
|
|
@@ -617,29 +677,17 @@ function fixFile(filepath) {
|
|
|
617
677
|
}
|
|
618
678
|
if (closeCount > openCount) {
|
|
619
679
|
let excess = closeCount - openCount;
|
|
620
|
-
|
|
621
|
-
|
|
680
|
+
let fixed = content;
|
|
681
|
+
while (excess > 0 && fixed.endsWith(')')) {
|
|
682
|
+
fixed = fixed.slice(0, -1);
|
|
622
683
|
excess--;
|
|
623
684
|
}
|
|
624
|
-
|
|
625
|
-
if (
|
|
626
|
-
content =
|
|
685
|
+
fixed = fixed.replace(/\n[ \t]*$/, '');
|
|
686
|
+
if (fixed !== content) {
|
|
687
|
+
content = fixed;
|
|
627
688
|
fixes.push('trailing_paren');
|
|
628
689
|
}
|
|
629
690
|
}
|
|
630
|
-
const lines = content.split('\n');
|
|
631
|
-
let changed = false;
|
|
632
|
-
const fixedLines = lines.map(l => {
|
|
633
|
-
if (/^\s*;\s*$/.test(l)) {
|
|
634
|
-
changed = true;
|
|
635
|
-
return '';
|
|
636
|
-
}
|
|
637
|
-
return l;
|
|
638
|
-
});
|
|
639
|
-
if (changed) {
|
|
640
|
-
content = fixedLines.join('\n');
|
|
641
|
-
fixes.push('empty_comment');
|
|
642
|
-
}
|
|
643
691
|
if (fixes.length > 0) {
|
|
644
692
|
fs.writeFileSync(filepath, content, 'utf-8');
|
|
645
693
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
((AUTOLISP ())
|
|
2
|
+
(@ (COMMON-LISP AUTOLISP))
|
|
3
|
+
(C ())
|
|
4
|
+
(JSON ())
|
|
5
|
+
(SIDEBAR ())
|
|
6
|
+
(FUN ())
|
|
7
|
+
(DCL ())
|
|
8
|
+
(BASE ())
|
|
9
|
+
(STRING ())
|
|
10
|
+
(BLOCK ())
|
|
11
|
+
(ENTITY ())
|
|
12
|
+
(LAYER ())
|
|
13
|
+
(LAYOUT ())
|
|
14
|
+
(CURVE ())
|
|
15
|
+
(LIST ())
|
|
16
|
+
(M ())
|
|
17
|
+
(P ())
|
|
18
|
+
(UI ())
|
|
19
|
+
(SYS ())
|
|
20
|
+
(EXCEL ())
|
|
21
|
+
(WORD ())
|
|
22
|
+
(RE ())
|
|
23
|
+
(VECTRA ())
|
|
24
|
+
(DOS ())
|
|
25
|
+
(AT-PM ())
|
|
26
|
+
(QRENCODE ())
|
|
27
|
+
(NETWORK ())
|
|
28
|
+
(PKGMAN ())
|
|
29
|
+
(DATETIME ())
|
|
30
|
+
(DICT ())
|
|
31
|
+
(@NLP ())
|
|
32
|
+
(AT-SIDEBAR ())
|
|
33
|
+
(S ())
|
|
34
|
+
(VL ())
|
|
35
|
+
(VLR ())
|
|
36
|
+
(VLAX ())
|
|
37
|
+
(THEME ())
|
|
38
|
+
(AJAX ())
|
|
39
|
+
(ATLISP ())
|
|
40
|
+
(LOG ())
|
|
41
|
+
(ATLISP-CORE ()))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlisp/lint",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"description": "AutoLISP static analysis tool — parens, security, conventions, SBCL syntax validation",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"CAD",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"prepack": "npm run build"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@atlisp/parser": "^0.1.
|
|
40
|
+
"@atlisp/parser": "^0.1.3"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/jest": "^29",
|