@atlisp/lint 0.2.3 → 0.2.5
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/README.md +14 -2
- package/dist/checks/builtin-arg-count.js +3 -3
- package/dist/checks/empty-catch.js +3 -2
- package/dist/checks/entget-in-loop.js +0 -14
- package/dist/checks/if-without-else.js +4 -1
- package/dist/checks/promise-handler.js +21 -8
- package/dist/checks/string-concat-loop.js +0 -17
- package/dist/formatters-html.js +8 -4
- package/dist/formatters.js +5 -1
- package/dist/index.js +5 -1
- package/dist/locale.js +1 -1
- package/dist/project.js +8 -5
- package/dist/rules.js +24 -4
- package/dist/runner.js +50 -17
- package/dist/validate.js +1 -0
- package/dist/visitor-runner.js +118 -50
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -50,8 +50,11 @@ npx @atlisp/lint --format-check
|
|
|
50
50
|
--clear-cache 清除缓存目录
|
|
51
51
|
--parallel 开启多线程并行检查(使用 Worker Threads)
|
|
52
52
|
--project 启用跨文件分析(Phase 1b)
|
|
53
|
+
--sbcl 启用 SBCL 语法校验(Phase 2,需安装 SBCL)
|
|
53
54
|
--watch 监听模式,文件变动自动重新检查
|
|
54
55
|
--hook-args <args> 自定义 pre-commit hook 参数(配合 --install-hook)
|
|
56
|
+
--diff 仅检查自上次提交后变更的文件
|
|
57
|
+
--changed <branch> 仅检查相对于某分支有变更的文件(默认:main)
|
|
55
58
|
--help 显示帮助信息
|
|
56
59
|
--version 显示版本号
|
|
57
60
|
--format-code 自动格式化代码缩进(基于括号深度 + AST 对齐)
|
|
@@ -60,9 +63,9 @@ npx @atlisp/lint --format-check
|
|
|
60
63
|
|
|
61
64
|
## 架构:三相检测
|
|
62
65
|
|
|
63
|
-
### Phase 1a — 单文件静态检查(
|
|
66
|
+
### Phase 1a — 单文件静态检查(84 条规则)
|
|
64
67
|
|
|
65
|
-
所有
|
|
68
|
+
所有 84 条规则在单文件范围内运行,支持 `--parallel` 多线程并行。
|
|
66
69
|
|
|
67
70
|
| 规则 | 默认 | 类别 | 说明 |
|
|
68
71
|
|------|------|------|------|
|
|
@@ -136,6 +139,13 @@ npx @atlisp/lint --format-check
|
|
|
136
139
|
| `namespace_header` | off | 架构 | 缺少 (in-package ...) 头 |
|
|
137
140
|
| `loop_optimization` | off | 最佳实践 | 优化 while/assoc 循环 |
|
|
138
141
|
| `type_check` | off | 最佳实践 | getvar 结果未类型检查 |
|
|
142
|
+
| `builtin_arg_count` | error | 正确性 | 内置函数调用参数个数不匹配 |
|
|
143
|
+
| `unused_catch_result` | info | 正确性 | vl-catch-all-apply 结果被当布尔值用 |
|
|
144
|
+
| `string_concat_loop` | info | 性能 | 循环内字符串拼接 |
|
|
145
|
+
| `if_without_else` | info | 风格 | if 无 else 分支 |
|
|
146
|
+
| `redundant_list` | info | 风格 | (list) 等价于 nil |
|
|
147
|
+
| `entget_in_loop` | warn | 性能 | 循环内 entget |
|
|
148
|
+
| `promise_handler` | info | 最佳实践 | vlax-invoke 缺少回调处理器 |
|
|
139
149
|
|
|
140
150
|
### Phase 1b — 跨文件分析(--project)
|
|
141
151
|
|
|
@@ -146,6 +156,8 @@ npx @atlisp/lint --format-check
|
|
|
146
156
|
| `dangling_defun` | warn | 函数定义但从未被任何文件调用 |
|
|
147
157
|
| `missing_export` | warn | 函数未注册到 @::\*modules\* |
|
|
148
158
|
| `unused_package_dep` | warn | 导入的包从未被引用 |
|
|
159
|
+
| `module_cycle` | warn | 模块循环依赖 |
|
|
160
|
+
| `arg_count_project` | warn | 跨文件检查函数参数数量 |
|
|
149
161
|
|
|
150
162
|
同时检测 `duplicate_defun`(函数名在多个文件中重复定义)。
|
|
151
163
|
|
|
@@ -115,7 +115,7 @@ const BUILTIN_ARITY = {
|
|
|
115
115
|
'read-char': { min: 0, max: 1 },
|
|
116
116
|
'write-line': { min: 1, max: 2 },
|
|
117
117
|
'write-char': { min: 1, max: 2 },
|
|
118
|
-
|
|
118
|
+
read: { min: 0, max: 1 },
|
|
119
119
|
princ: { min: 0, max: 2 },
|
|
120
120
|
prin1: { min: 0, max: 2 },
|
|
121
121
|
print: { min: 0, max: 2 },
|
|
@@ -131,7 +131,7 @@ const BUILTIN_ARITY = {
|
|
|
131
131
|
getorient: { min: 0, max: 3 },
|
|
132
132
|
getkword: { min: 0, max: 2 },
|
|
133
133
|
initget: { min: 1, max: 2 },
|
|
134
|
-
|
|
134
|
+
load: { min: 1, max: 2 },
|
|
135
135
|
autoload: { min: 2, max: 2 },
|
|
136
136
|
// === System ===
|
|
137
137
|
getvar: { min: 1, max: 1 },
|
|
@@ -156,7 +156,7 @@ const BUILTIN_ARITY = {
|
|
|
156
156
|
entnext: { min: 0, max: 1 },
|
|
157
157
|
handent: { min: 1, max: 1 },
|
|
158
158
|
entupd: { min: 1, max: 1 },
|
|
159
|
-
|
|
159
|
+
ssget: { min: 0, max: -1 },
|
|
160
160
|
ssadd: { min: 0, max: 2 },
|
|
161
161
|
ssdel: { min: 2, max: 2 },
|
|
162
162
|
sslength: { min: 1, max: 1 },
|
|
@@ -66,7 +66,7 @@ function getSetqVarName(node) {
|
|
|
66
66
|
return null;
|
|
67
67
|
}
|
|
68
68
|
function hasErrorCheckInSiblings(node, varName) {
|
|
69
|
-
|
|
69
|
+
const parent = node.parent;
|
|
70
70
|
if (!parent || !parent.children)
|
|
71
71
|
return false;
|
|
72
72
|
const idx = parent.children.indexOf(node);
|
|
@@ -88,7 +88,8 @@ function hasErrorCheckInSiblings(node, varName) {
|
|
|
88
88
|
}
|
|
89
89
|
function containsErrorCheck(node, varName) {
|
|
90
90
|
const matches = (0, parser_1.astFindAll)(node, n => (0, parser_1.astIsList)(n, 'vl-catch-all-error-p') &&
|
|
91
|
-
n.children &&
|
|
91
|
+
n.children &&
|
|
92
|
+
n.children.length >= 2 &&
|
|
92
93
|
(0, parser_1.astIsSymbol)(n.children[1], varName));
|
|
93
94
|
return matches.length > 0;
|
|
94
95
|
}
|
|
@@ -2,20 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.checkEntgetInLoop = checkEntgetInLoop;
|
|
4
4
|
const locale_1 = require("../locale");
|
|
5
|
-
function isInsideLoop(ast, targetNode) {
|
|
6
|
-
let node = targetNode.parent;
|
|
7
|
-
while (node) {
|
|
8
|
-
if (node.type === 'list' && node.children && node.children.length > 0 &&
|
|
9
|
-
node.children[0].type === 'symbol') {
|
|
10
|
-
const name = node.children[0].name;
|
|
11
|
-
if (name === 'while' || name === 'repeat' || name === 'foreach') {
|
|
12
|
-
return true;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
node = node.parent;
|
|
16
|
-
}
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
5
|
function checkEntgetInLoop(content, file) {
|
|
20
6
|
const issues = [];
|
|
21
7
|
const lines = content.split('\n');
|
|
@@ -11,7 +11,10 @@ function checkIfWithoutElse(ast, file) {
|
|
|
11
11
|
continue;
|
|
12
12
|
if (node.children.length === 3) {
|
|
13
13
|
const parent = node.parent;
|
|
14
|
-
if (parent &&
|
|
14
|
+
if (parent &&
|
|
15
|
+
parent.type === 'list' &&
|
|
16
|
+
parent.children &&
|
|
17
|
+
parent.children.length > 0 &&
|
|
15
18
|
parent.children[0].type === 'symbol') {
|
|
16
19
|
const parentName = parent.children[0].name;
|
|
17
20
|
if (parentName === 'cond' || parentName === 'if')
|
|
@@ -5,21 +5,31 @@ const locale_1 = require("../locale");
|
|
|
5
5
|
const parser_1 = require("@atlisp/parser");
|
|
6
6
|
function checkPromiseHandler(ast, file) {
|
|
7
7
|
const issues = [];
|
|
8
|
-
const vlaxNodes = (0, parser_1.astFindAll)(ast, (n) => !!(n.type === 'list' &&
|
|
9
|
-
n.children
|
|
10
|
-
|
|
8
|
+
const vlaxNodes = (0, parser_1.astFindAll)(ast, (n) => !!(n.type === 'list' &&
|
|
9
|
+
n.children &&
|
|
10
|
+
n.children.length > 0 &&
|
|
11
|
+
n.children[0].type === 'symbol' &&
|
|
12
|
+
typeof n.children[0].name === 'string' &&
|
|
13
|
+
(n.children[0].name.startsWith('vlax-invoke') ||
|
|
14
|
+
n.children[0].name.startsWith('vlax-invoke-method'))));
|
|
11
15
|
for (const node of vlaxNodes) {
|
|
12
16
|
if (isInQuote(node))
|
|
13
17
|
continue;
|
|
14
18
|
const parent = node.parent;
|
|
15
|
-
if (parent &&
|
|
16
|
-
parent.
|
|
19
|
+
if (parent &&
|
|
20
|
+
parent.type === 'list' &&
|
|
21
|
+
parent.children &&
|
|
22
|
+
parent.children.length > 0 &&
|
|
23
|
+
parent.children[0].type === 'symbol' &&
|
|
24
|
+
parent.children[0].name === 'vl-catch-all-apply') {
|
|
17
25
|
continue;
|
|
18
26
|
}
|
|
19
27
|
let hasCallback = false;
|
|
20
28
|
if (node.children) {
|
|
21
29
|
for (const child of node.children) {
|
|
22
|
-
if (child.type === 'list' &&
|
|
30
|
+
if (child.type === 'list' &&
|
|
31
|
+
child.children &&
|
|
32
|
+
child.children.length > 0 &&
|
|
23
33
|
child.children[0].type === 'symbol' &&
|
|
24
34
|
(child.children[0].name === 'lambda' || child.children[0].name === 'function')) {
|
|
25
35
|
hasCallback = true;
|
|
@@ -42,8 +52,11 @@ function checkPromiseHandler(ast, file) {
|
|
|
42
52
|
function isInQuote(node) {
|
|
43
53
|
let p = node.parent;
|
|
44
54
|
while (p) {
|
|
45
|
-
if (p.type === 'list' &&
|
|
46
|
-
p.children
|
|
55
|
+
if (p.type === 'list' &&
|
|
56
|
+
p.children &&
|
|
57
|
+
p.children.length > 0 &&
|
|
58
|
+
p.children[0].type === 'symbol' &&
|
|
59
|
+
p.children[0].name === 'quote') {
|
|
47
60
|
return true;
|
|
48
61
|
}
|
|
49
62
|
p = p.parent;
|
|
@@ -2,21 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.checkStringConcatInLoop = checkStringConcatInLoop;
|
|
4
4
|
const locale_1 = require("../locale");
|
|
5
|
-
const parser_1 = require("@atlisp/parser");
|
|
6
|
-
function hasStrcatInBody(body) {
|
|
7
|
-
for (const node of body) {
|
|
8
|
-
for (const n of (0, parser_1.astWalk)(node)) {
|
|
9
|
-
if (n.type === 'list' && n.children && n.children.length > 0 &&
|
|
10
|
-
n.children[0].type === 'symbol' && n.children[0].name === 'strcat') {
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
function isLoopForm(name) {
|
|
18
|
-
return name === 'while' || name === 'repeat' || name === 'foreach';
|
|
19
|
-
}
|
|
20
5
|
function checkStringConcatInLoop(content, file) {
|
|
21
6
|
const issues = [];
|
|
22
7
|
const lines = content.split('\n');
|
|
@@ -27,8 +12,6 @@ function checkStringConcatInLoop(content, file) {
|
|
|
27
12
|
const name = match[1];
|
|
28
13
|
let depth = 0;
|
|
29
14
|
let started = false;
|
|
30
|
-
let startLine = i;
|
|
31
|
-
let foundStrcat = false;
|
|
32
15
|
for (let j = i; j < lines.length; j++) {
|
|
33
16
|
for (let k = 0; k < lines[j].length; k++) {
|
|
34
17
|
const ch = lines[j][k];
|
package/dist/formatters-html.js
CHANGED
|
@@ -55,11 +55,13 @@ function formatHtml(issues, errorCount, warningCount) {
|
|
|
55
55
|
<p class="text-muted">${issues.length} total issues across ${ruleGroups.size} rules</p>
|
|
56
56
|
</div>
|
|
57
57
|
|
|
58
|
-
${sortedRules
|
|
58
|
+
${sortedRules
|
|
59
|
+
.map(([rule, ruleIssues]) => `
|
|
59
60
|
<div class="summary-card">
|
|
60
61
|
<h5 class="card-title">${escHtml(rule)} <span class="badge bg-secondary">${ruleIssues.length}</span></h5>
|
|
61
62
|
<ul class="list-group list-group-flush">
|
|
62
|
-
${ruleIssues
|
|
63
|
+
${ruleIssues
|
|
64
|
+
.map(iss => `
|
|
63
65
|
<li class="list-group-item d-flex justify-content-between align-items-start">
|
|
64
66
|
<div>
|
|
65
67
|
<span class="fw-bold">${escHtml(iss.file)}:${iss.line || '-'}</span>
|
|
@@ -67,10 +69,12 @@ function formatHtml(issues, errorCount, warningCount) {
|
|
|
67
69
|
</div>
|
|
68
70
|
${iss.severity === 'error' ? '<span class="badge bg-danger rounded-pill">ERROR</span>' : iss.severity === 'warn' ? '<span class="badge bg-warning text-dark rounded-pill">WARN</span>' : '<span class="badge bg-info text-dark rounded-pill">INFO</span>'}
|
|
69
71
|
</li>
|
|
70
|
-
`)
|
|
72
|
+
`)
|
|
73
|
+
.join('')}
|
|
71
74
|
</ul>
|
|
72
75
|
</div>
|
|
73
|
-
`)
|
|
76
|
+
`)
|
|
77
|
+
.join('')}
|
|
74
78
|
|
|
75
79
|
<div class="summary-card">
|
|
76
80
|
<h5>All Issues</h5>
|
package/dist/formatters.js
CHANGED
|
@@ -23,7 +23,11 @@ function formatDefault(issues, fileContents) {
|
|
|
23
23
|
else if (iss.severity === 'warn')
|
|
24
24
|
warnings++;
|
|
25
25
|
const tagColor = iss.severity === 'error' ? colors.red : iss.severity === 'warn' ? colors.yellow : colors.cyan;
|
|
26
|
-
const tag = iss.severity === 'error'
|
|
26
|
+
const tag = iss.severity === 'error'
|
|
27
|
+
? (0, locale_1.t)('summary.tag_fail')
|
|
28
|
+
: iss.severity === 'warn'
|
|
29
|
+
? (0, locale_1.t)('summary.tag_warn')
|
|
30
|
+
: (0, locale_1.t)('summary.tag_info');
|
|
27
31
|
const loc = iss.line > 0 ? `${(0, locale_1.t)('summary.line')} ${iss.line}` : '';
|
|
28
32
|
const prefix = useColor ? `${tagColor}${tag}${colors.reset}` : tag;
|
|
29
33
|
lines.push(`${prefix}: ${iss.file} — ${loc}${loc ? ': ' : ''}${iss.message}`.trim());
|
package/dist/index.js
CHANGED
|
@@ -363,7 +363,11 @@ async function main() {
|
|
|
363
363
|
}
|
|
364
364
|
// --watch mode
|
|
365
365
|
if (opts.watch) {
|
|
366
|
-
(0, watch_1.watchFiles)(files, {
|
|
366
|
+
(0, watch_1.watchFiles)(files, {
|
|
367
|
+
rootDir,
|
|
368
|
+
format: (opts.format === 'sarif' || opts.format === 'html' ? 'default' : opts.format),
|
|
369
|
+
config,
|
|
370
|
+
});
|
|
367
371
|
return;
|
|
368
372
|
}
|
|
369
373
|
// --fix mode: auto-fix before linting
|
package/dist/locale.js
CHANGED
|
@@ -36,7 +36,7 @@ const messages = {
|
|
|
36
36
|
unused_parameter: "Parameter '{0}' in '{1}' is never used",
|
|
37
37
|
constant_condition: "Constant condition '{0}' in {1} — possibly leftover debug code",
|
|
38
38
|
redundant_progn: 'Redundant progn in {0}: only one form inside',
|
|
39
|
-
redundant_if:
|
|
39
|
+
redundant_if: 'Redundant progn in when/unless body — when/unless already allows multiple forms',
|
|
40
40
|
empty_branch: "Empty branch in '{0}' — no body forms",
|
|
41
41
|
unused_let_binding: "Let binding '{0}' is never used",
|
|
42
42
|
recursive_call: "Function '{0}' calls itself — possible infinite recursion",
|
package/dist/project.js
CHANGED
|
@@ -139,9 +139,12 @@ function findModuleDeps(filepath) {
|
|
|
139
139
|
}
|
|
140
140
|
const setqNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'setq'));
|
|
141
141
|
for (const node of setqNodes) {
|
|
142
|
-
if (node.children &&
|
|
143
|
-
node.children
|
|
144
|
-
node.children[
|
|
142
|
+
if (node.children &&
|
|
143
|
+
node.children.length >= 3 &&
|
|
144
|
+
node.children[1].type === 'symbol' &&
|
|
145
|
+
node.children[1].name === '@::*modules*' &&
|
|
146
|
+
node.children[2].type === 'list' &&
|
|
147
|
+
node.children[2].children) {
|
|
145
148
|
for (const exp of node.children[2].children) {
|
|
146
149
|
if (exp.type === 'string' && typeof exp.value === 'string') {
|
|
147
150
|
exports.push(exp.value);
|
|
@@ -235,7 +238,7 @@ function lintProject(files, config, rootDir) {
|
|
|
235
238
|
const visited = new Set();
|
|
236
239
|
const stack = new Set();
|
|
237
240
|
const cyclePath = [];
|
|
238
|
-
|
|
241
|
+
const dfs = (current) => {
|
|
239
242
|
if (stack.has(current)) {
|
|
240
243
|
const idx = cyclePath.indexOf(current);
|
|
241
244
|
const cycle = cyclePath.slice(idx).concat(current);
|
|
@@ -268,7 +271,7 @@ function lintProject(files, config, rootDir) {
|
|
|
268
271
|
cyclePath.pop();
|
|
269
272
|
stack.delete(current);
|
|
270
273
|
return false;
|
|
271
|
-
}
|
|
274
|
+
};
|
|
272
275
|
for (const f of moduleDeps.keys()) {
|
|
273
276
|
if (!visited.has(f)) {
|
|
274
277
|
dfs(f);
|
package/dist/rules.js
CHANGED
|
@@ -156,7 +156,12 @@ exports.RULES = [
|
|
|
156
156
|
description: '检测函数调用自身(可能无限递归)',
|
|
157
157
|
category: '正确性',
|
|
158
158
|
},
|
|
159
|
-
{
|
|
159
|
+
{
|
|
160
|
+
name: 'redundant_if',
|
|
161
|
+
defaultSeverity: 'info',
|
|
162
|
+
description: '检测 when/unless 中冗余 progn(when/unless 已隐含 progn)',
|
|
163
|
+
category: '风格',
|
|
164
|
+
},
|
|
160
165
|
{ name: 'redundant_let', defaultSeverity: 'info', description: '检测无绑定的 let 建议改用 progn', category: '风格' },
|
|
161
166
|
{ name: 'redundant_nil_else', defaultSeverity: 'info', description: '检测 if 中冗余的 nil 分支', category: '风格' },
|
|
162
167
|
{ name: 'redundant_progn', defaultSeverity: 'info', description: '检测分支中多余的 progn', category: '风格' },
|
|
@@ -228,15 +233,30 @@ exports.RULES = [
|
|
|
228
233
|
description: '检测 vlax-* 使用前未调用 vl-load-com',
|
|
229
234
|
category: '最佳实践',
|
|
230
235
|
},
|
|
231
|
-
{
|
|
236
|
+
{
|
|
237
|
+
name: 'unused_catch_result',
|
|
238
|
+
defaultSeverity: 'info',
|
|
239
|
+
description: '检测 vl-catch-all-apply 结果被当布尔值用',
|
|
240
|
+
category: '正确性',
|
|
241
|
+
},
|
|
232
242
|
{ name: 'string_concat_loop', defaultSeverity: 'info', description: '检测循环内字符串拼接', category: '性能' },
|
|
233
243
|
{ name: 'if_without_else', defaultSeverity: 'info', description: '检测 if 无 else 分支', category: '风格' },
|
|
234
244
|
{ name: 'redundant_list', defaultSeverity: 'info', description: '检测 (list) 等价于 nil', category: '风格' },
|
|
235
245
|
{ name: 'entget_in_loop', defaultSeverity: 'warn', description: '检测循环内 entget', category: '性能' },
|
|
236
|
-
{
|
|
246
|
+
{
|
|
247
|
+
name: 'promise_handler',
|
|
248
|
+
defaultSeverity: 'info',
|
|
249
|
+
description: '检测 vlax-invoke 缺少回调处理器',
|
|
250
|
+
category: '最佳实践',
|
|
251
|
+
},
|
|
237
252
|
{ name: 'module_cycle', defaultSeverity: 'warn', description: '检测模块循环依赖', category: '架构' },
|
|
238
253
|
{ name: 'arg_count_project', defaultSeverity: 'warn', description: '跨文件检查函数参数数量', category: '正确性' },
|
|
239
|
-
{
|
|
254
|
+
{
|
|
255
|
+
name: 'builtin_arg_count',
|
|
256
|
+
defaultSeverity: 'error',
|
|
257
|
+
description: '检查 AutoLISP 内置函数调用参数个数是否匹配',
|
|
258
|
+
category: '正确性',
|
|
259
|
+
},
|
|
240
260
|
];
|
|
241
261
|
function generateRulesMarkdown() {
|
|
242
262
|
const lines = [
|
package/dist/runner.js
CHANGED
|
@@ -166,15 +166,33 @@ function runChecks(content, file, config) {
|
|
|
166
166
|
addIfEnabled('string_concat_loop', () => (0, string_concat_loop_1.checkStringConcatInLoop)(content, file));
|
|
167
167
|
// AST-based checks via single-pass visitor
|
|
168
168
|
const astRules = new Set([
|
|
169
|
-
'quit_exit',
|
|
170
|
-
'
|
|
171
|
-
'
|
|
172
|
-
'
|
|
173
|
-
'
|
|
174
|
-
'
|
|
175
|
-
'
|
|
176
|
-
'
|
|
177
|
-
'
|
|
169
|
+
'quit_exit',
|
|
170
|
+
'open_without_close',
|
|
171
|
+
'function_complexity',
|
|
172
|
+
'parameter_naming',
|
|
173
|
+
'unused_variable',
|
|
174
|
+
'unused_parameter',
|
|
175
|
+
'constant_condition',
|
|
176
|
+
'redundant_progn',
|
|
177
|
+
'empty_branch',
|
|
178
|
+
'unused_let_binding',
|
|
179
|
+
'recursive_call',
|
|
180
|
+
'variable_shadow',
|
|
181
|
+
'unused_local_fun',
|
|
182
|
+
'multiple_setq',
|
|
183
|
+
'redundant_quotes',
|
|
184
|
+
'redundant_setq',
|
|
185
|
+
'redundant_nil_else',
|
|
186
|
+
'single_arg_and_or',
|
|
187
|
+
'redundant_let',
|
|
188
|
+
'self_compare',
|
|
189
|
+
'misplaced_else',
|
|
190
|
+
'quote_vs_function',
|
|
191
|
+
'double_not',
|
|
192
|
+
'setq_single_arg',
|
|
193
|
+
'assoc_without_cdr',
|
|
194
|
+
'identical_branches',
|
|
195
|
+
'while_constant',
|
|
178
196
|
'cond_duplicate',
|
|
179
197
|
]);
|
|
180
198
|
const needsAst = Array.from(astRules).some(r => checks[r] !== 'off');
|
|
@@ -239,7 +257,7 @@ function lintFilesParallel(files, config, rootDir) {
|
|
|
239
257
|
if (completed >= files.length)
|
|
240
258
|
resolve(allIssues);
|
|
241
259
|
});
|
|
242
|
-
worker.on('error', err => {
|
|
260
|
+
worker.on('error', (err) => {
|
|
243
261
|
allIssues.push({
|
|
244
262
|
file: relPath,
|
|
245
263
|
line: 1,
|
|
@@ -317,13 +335,28 @@ function extractForm(s, start) {
|
|
|
317
335
|
}
|
|
318
336
|
// ===== FIX APPLICATION =====
|
|
319
337
|
exports.FIXABLE_RULES = new Set([
|
|
320
|
-
'redundant_quotes',
|
|
321
|
-
'
|
|
322
|
-
'
|
|
323
|
-
'
|
|
324
|
-
'
|
|
325
|
-
'
|
|
326
|
-
'
|
|
338
|
+
'redundant_quotes',
|
|
339
|
+
'double_not',
|
|
340
|
+
'redundant_nil_else',
|
|
341
|
+
'single_arg_and_or',
|
|
342
|
+
'redundant_setq',
|
|
343
|
+
'redundant_progn',
|
|
344
|
+
'redundant_let',
|
|
345
|
+
'quote_style',
|
|
346
|
+
'redundant_if',
|
|
347
|
+
'misplaced_else',
|
|
348
|
+
'setq_multiple',
|
|
349
|
+
'append_single',
|
|
350
|
+
'nth_usage',
|
|
351
|
+
'setq_single_arg',
|
|
352
|
+
'extra_parens',
|
|
353
|
+
'empty_branch',
|
|
354
|
+
'comment_style',
|
|
355
|
+
'eq_usage',
|
|
356
|
+
'cond_simplify',
|
|
357
|
+
'self_compare',
|
|
358
|
+
'while_constant',
|
|
359
|
+
'redundant_list',
|
|
327
360
|
]);
|
|
328
361
|
function applyFixes(issues, content, filepath) {
|
|
329
362
|
const lines = content.split('\n');
|
package/dist/validate.js
CHANGED
package/dist/visitor-runner.js
CHANGED
|
@@ -4,11 +4,6 @@ exports.runChecksWithVisitor = runChecksWithVisitor;
|
|
|
4
4
|
const locale_1 = require("./locale");
|
|
5
5
|
const disable_1 = require("./disable");
|
|
6
6
|
const parser_1 = require("@atlisp/parser");
|
|
7
|
-
function emit(issues, rule, line, severity, message, file) {
|
|
8
|
-
if (severity === 'off')
|
|
9
|
-
return;
|
|
10
|
-
issues.push({ file, line, severity: severity, rule, message });
|
|
11
|
-
}
|
|
12
7
|
function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
13
8
|
const issues = [];
|
|
14
9
|
const checks = config.checks;
|
|
@@ -34,7 +29,11 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
34
29
|
function isInQuote(node) {
|
|
35
30
|
let p = node.parent;
|
|
36
31
|
while (p) {
|
|
37
|
-
if (p.type === 'list' &&
|
|
32
|
+
if (p.type === 'list' &&
|
|
33
|
+
p.children &&
|
|
34
|
+
p.children.length > 0 &&
|
|
35
|
+
p.children[0].type === 'symbol' &&
|
|
36
|
+
(p.children[0].name === 'quote' || p.children[0].name === 'function')) {
|
|
38
37
|
return true;
|
|
39
38
|
}
|
|
40
39
|
p = p.parent;
|
|
@@ -82,7 +81,11 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
82
81
|
}
|
|
83
82
|
function hasSelfCall(body, fnName) {
|
|
84
83
|
for (const form of body) {
|
|
85
|
-
if (form.type === 'list' &&
|
|
84
|
+
if (form.type === 'list' &&
|
|
85
|
+
form.children &&
|
|
86
|
+
form.children.length > 0 &&
|
|
87
|
+
form.children[0].type === 'symbol' &&
|
|
88
|
+
form.children[0].name === fnName) {
|
|
86
89
|
return true;
|
|
87
90
|
}
|
|
88
91
|
}
|
|
@@ -143,8 +146,16 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
148
|
const higherOrderFuncs = new Set([
|
|
146
|
-
'mapcar',
|
|
147
|
-
'
|
|
149
|
+
'mapcar',
|
|
150
|
+
'apply',
|
|
151
|
+
'lambda',
|
|
152
|
+
'vl-sort',
|
|
153
|
+
'vl-sort-i',
|
|
154
|
+
'vl-remove-if',
|
|
155
|
+
'vl-remove-if-not',
|
|
156
|
+
'vl-member-if',
|
|
157
|
+
'vl-some',
|
|
158
|
+
'vl-every',
|
|
148
159
|
]);
|
|
149
160
|
// redundant_progn
|
|
150
161
|
if (checks['redundant_progn'] !== 'off') {
|
|
@@ -220,9 +231,13 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
220
231
|
// double_not
|
|
221
232
|
if (checks['double_not'] !== 'off') {
|
|
222
233
|
visitor.on('not', node => {
|
|
223
|
-
if (node.children &&
|
|
224
|
-
node.children
|
|
225
|
-
node.children[1].
|
|
234
|
+
if (node.children &&
|
|
235
|
+
node.children.length >= 2 &&
|
|
236
|
+
node.children[1].type === 'list' &&
|
|
237
|
+
node.children[1].children &&
|
|
238
|
+
node.children[1].children.length > 0 &&
|
|
239
|
+
node.children[1].children[0].type === 'symbol' &&
|
|
240
|
+
node.children[1].children[0].name === 'not') {
|
|
226
241
|
addIssue('double_not', node.pos.line, (0, locale_1.t)('double_not'));
|
|
227
242
|
}
|
|
228
243
|
});
|
|
@@ -249,9 +264,12 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
249
264
|
visitor.on('if', node => {
|
|
250
265
|
if (node.children && node.children.length === 4) {
|
|
251
266
|
const cond = node.children[1];
|
|
252
|
-
if (cond.type === 'list' &&
|
|
253
|
-
cond.children
|
|
254
|
-
|
|
267
|
+
if (cond.type === 'list' &&
|
|
268
|
+
cond.children &&
|
|
269
|
+
cond.children.length >= 2 &&
|
|
270
|
+
cond.children[0].type === 'symbol' &&
|
|
271
|
+
cond.children[0].name === 'not') {
|
|
272
|
+
const innerName = cond.children[1].type === 'symbol' ? cond.children[1].name || '' : '';
|
|
255
273
|
addIssue('misplaced_else', node.pos.line, (0, locale_1.t)('misplaced_else', innerName));
|
|
256
274
|
}
|
|
257
275
|
}
|
|
@@ -263,7 +281,7 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
263
281
|
if (node.children && node.children.length >= 2) {
|
|
264
282
|
const cond = node.children[1];
|
|
265
283
|
if (isLiteral(cond)) {
|
|
266
|
-
const name = cond.type === 'symbol' ?
|
|
284
|
+
const name = cond.type === 'symbol' ? cond.name || '' : String(cond.value ?? '');
|
|
267
285
|
addIssue('while_constant', node.pos.line, (0, locale_1.t)('while_constant', name));
|
|
268
286
|
}
|
|
269
287
|
}
|
|
@@ -275,7 +293,7 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
275
293
|
if (node.children && node.children.length >= 2) {
|
|
276
294
|
const cond = node.children[1];
|
|
277
295
|
if (isLiteral(cond)) {
|
|
278
|
-
const name = cond.type === 'symbol' ?
|
|
296
|
+
const name = cond.type === 'symbol' ? cond.name || '' : String(cond.value ?? '');
|
|
279
297
|
addIssue('constant_condition', node.pos.line, (0, locale_1.t)('constant_condition', name, formName));
|
|
280
298
|
}
|
|
281
299
|
}
|
|
@@ -288,7 +306,7 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
288
306
|
if (clause.type === 'list' && clause.children && clause.children.length >= 2) {
|
|
289
307
|
const test = clause.children[1];
|
|
290
308
|
if (isLiteral(test)) {
|
|
291
|
-
const name = test.type === 'symbol' ?
|
|
309
|
+
const name = test.type === 'symbol' ? test.name || '' : String(test.value ?? '');
|
|
292
310
|
addIssue('constant_condition', node.pos.line, (0, locale_1.t)('constant_condition', name, 'cond'));
|
|
293
311
|
}
|
|
294
312
|
}
|
|
@@ -298,9 +316,13 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
298
316
|
// quote_vs_function
|
|
299
317
|
if (checks['quote_vs_function'] !== 'off') {
|
|
300
318
|
visitor.on('quote', node => {
|
|
301
|
-
if (node.children &&
|
|
302
|
-
node.children
|
|
303
|
-
node.children[1].
|
|
319
|
+
if (node.children &&
|
|
320
|
+
node.children.length >= 2 &&
|
|
321
|
+
node.children[1].type === 'list' &&
|
|
322
|
+
node.children[1].children &&
|
|
323
|
+
node.children[1].children.length > 0 &&
|
|
324
|
+
node.children[1].children[0].type === 'symbol' &&
|
|
325
|
+
node.children[1].children[0].name === 'lambda') {
|
|
304
326
|
const parent = node.parent;
|
|
305
327
|
if (parent && parent.children && parent.children.length > 0) {
|
|
306
328
|
const pName = (parent.children[0].type === 'symbol' && parent.children[0].name) || '';
|
|
@@ -314,9 +336,13 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
314
336
|
// redundant_quotes
|
|
315
337
|
if (checks['redundant_quotes'] !== 'off') {
|
|
316
338
|
visitor.on('quote', node => {
|
|
317
|
-
if (node.children &&
|
|
318
|
-
node.children
|
|
319
|
-
node.children[1].
|
|
339
|
+
if (node.children &&
|
|
340
|
+
node.children.length >= 2 &&
|
|
341
|
+
node.children[1].type === 'list' &&
|
|
342
|
+
node.children[1].children &&
|
|
343
|
+
node.children[1].children.length > 0 &&
|
|
344
|
+
node.children[1].children[0].type === 'symbol' &&
|
|
345
|
+
node.children[1].children[0].name === 'quote') {
|
|
320
346
|
addIssue('redundant_quotes', node.pos.line, (0, locale_1.t)('redundant_quotes'));
|
|
321
347
|
}
|
|
322
348
|
});
|
|
@@ -347,8 +373,11 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
347
373
|
addIssue('assoc_without_cdr', node.pos.line, (0, locale_1.t)('assoc_without_cdr'));
|
|
348
374
|
return;
|
|
349
375
|
}
|
|
350
|
-
if (parent.type === 'list' &&
|
|
351
|
-
parent.children
|
|
376
|
+
if (parent.type === 'list' &&
|
|
377
|
+
parent.children &&
|
|
378
|
+
parent.children.length > 0 &&
|
|
379
|
+
parent.children[0].type === 'symbol' &&
|
|
380
|
+
(parent.children[0].name === 'cdr' || parent.children[0].name === 'cadr')) {
|
|
352
381
|
return;
|
|
353
382
|
}
|
|
354
383
|
addIssue('assoc_without_cdr', node.pos.line, (0, locale_1.t)('assoc_without_cdr'));
|
|
@@ -368,8 +397,12 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
368
397
|
}
|
|
369
398
|
// open_without_close (data collection)
|
|
370
399
|
if (checks['open_without_close'] !== 'off') {
|
|
371
|
-
visitor.on('open', () => {
|
|
372
|
-
|
|
400
|
+
visitor.on('open', () => {
|
|
401
|
+
state.openCount++;
|
|
402
|
+
});
|
|
403
|
+
visitor.on('close', () => {
|
|
404
|
+
state.closeCount++;
|
|
405
|
+
});
|
|
373
406
|
}
|
|
374
407
|
// cond_duplicate (data collection)
|
|
375
408
|
if (checks['cond_duplicate'] !== 'off') {
|
|
@@ -404,8 +437,12 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
404
437
|
dangerNames.add('vl-registry-write');
|
|
405
438
|
if (dangerNames.size > 0) {
|
|
406
439
|
visitor.on('*', node => {
|
|
407
|
-
if (node.type === 'list' &&
|
|
408
|
-
node.children
|
|
440
|
+
if (node.type === 'list' &&
|
|
441
|
+
node.children &&
|
|
442
|
+
node.children.length > 0 &&
|
|
443
|
+
node.children[0].type === 'symbol' &&
|
|
444
|
+
node.children[0].name &&
|
|
445
|
+
dangerNames.has(node.children[0].name)) {
|
|
409
446
|
if (!isInQuote(node)) {
|
|
410
447
|
state.dangerousCalls.push({ name: node.children[0].name, node });
|
|
411
448
|
}
|
|
@@ -419,8 +456,11 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
419
456
|
if (node.type === 'list' && node.children) {
|
|
420
457
|
let prevSetq = false;
|
|
421
458
|
for (const child of node.children) {
|
|
422
|
-
if (child.type === 'list' &&
|
|
423
|
-
child.children
|
|
459
|
+
if (child.type === 'list' &&
|
|
460
|
+
child.children &&
|
|
461
|
+
child.children.length > 0 &&
|
|
462
|
+
child.children[0].type === 'symbol' &&
|
|
463
|
+
child.children[0].name === 'setq') {
|
|
424
464
|
if (prevSetq) {
|
|
425
465
|
addIssue('multiple_setq', child.pos.line, (0, locale_1.t)('multiple_setq'));
|
|
426
466
|
prevSetq = false;
|
|
@@ -460,15 +500,18 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
460
500
|
return;
|
|
461
501
|
const letVars = new Set();
|
|
462
502
|
for (const b of bindings.children) {
|
|
463
|
-
if (b.type === 'list' &&
|
|
503
|
+
if (b.type === 'list' &&
|
|
504
|
+
b.children &&
|
|
505
|
+
b.children.length > 0 &&
|
|
506
|
+
b.children[0].type === 'symbol' &&
|
|
507
|
+
b.children[0].name) {
|
|
464
508
|
letVars.add(b.children[0].name);
|
|
465
509
|
}
|
|
466
510
|
}
|
|
467
511
|
// Walk body for setq of let-bound vars (shallow, don't descend into nested lets)
|
|
468
512
|
const walkBodyForSetq = (forms) => {
|
|
469
513
|
for (const form of forms) {
|
|
470
|
-
if (form.type === 'list' && form.children && form.children.length > 0 &&
|
|
471
|
-
form.children[0].type === 'symbol') {
|
|
514
|
+
if (form.type === 'list' && form.children && form.children.length > 0 && form.children[0].type === 'symbol') {
|
|
472
515
|
if (form.children[0].name === 'setq' && form.children.length > 1) {
|
|
473
516
|
for (let i = 1; i < form.children.length; i += 2) {
|
|
474
517
|
const child = form.children[i];
|
|
@@ -501,7 +544,11 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
501
544
|
return;
|
|
502
545
|
const letVars = [];
|
|
503
546
|
for (const b of bindings.children) {
|
|
504
|
-
if (b.type === 'list' &&
|
|
547
|
+
if (b.type === 'list' &&
|
|
548
|
+
b.children &&
|
|
549
|
+
b.children.length > 0 &&
|
|
550
|
+
b.children[0].type === 'symbol' &&
|
|
551
|
+
b.children[0].name) {
|
|
505
552
|
letVars.push({ name: b.children[0].name, line: b.children[0].pos.line });
|
|
506
553
|
}
|
|
507
554
|
}
|
|
@@ -524,7 +571,7 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
524
571
|
if (!node.children || node.children.length < 4)
|
|
525
572
|
return;
|
|
526
573
|
const fnName = node.children[1].type === 'symbol' ? node.children[1].name : '';
|
|
527
|
-
if (!fnName ||
|
|
574
|
+
if (!fnName || fnName.startsWith('C:') || fnName.startsWith('c:'))
|
|
528
575
|
return;
|
|
529
576
|
const body = node.children.slice(3);
|
|
530
577
|
if (checks['unused_parameter'] !== 'off') {
|
|
@@ -564,7 +611,13 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
564
611
|
return;
|
|
565
612
|
for (let i = 1; i < node.children.length; i += 2) {
|
|
566
613
|
const varNode = node.children[i];
|
|
567
|
-
if (varNode.type === 'symbol' &&
|
|
614
|
+
if (varNode.type === 'symbol' &&
|
|
615
|
+
varNode.name &&
|
|
616
|
+
!varNode.name.startsWith('*') &&
|
|
617
|
+
!varNode.name.includes(':*') &&
|
|
618
|
+
varNode.name !== 'T' &&
|
|
619
|
+
varNode.name !== 'nil' &&
|
|
620
|
+
!varNode.name.startsWith('/')) {
|
|
568
621
|
state.setqDefs.set(varNode.name, { line: varNode.pos.line, file });
|
|
569
622
|
}
|
|
570
623
|
}
|
|
@@ -598,7 +651,10 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
598
651
|
visitor.on('if', node => {
|
|
599
652
|
if (node.children && node.children.length === 3) {
|
|
600
653
|
const parent = node.parent;
|
|
601
|
-
if (parent &&
|
|
654
|
+
if (parent &&
|
|
655
|
+
parent.type === 'list' &&
|
|
656
|
+
parent.children &&
|
|
657
|
+
parent.children.length > 0 &&
|
|
602
658
|
parent.children[0].type === 'symbol') {
|
|
603
659
|
const parentName = parent.children[0].name;
|
|
604
660
|
if (parentName === 'cond' || parentName === 'if')
|
|
@@ -619,19 +675,28 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
619
675
|
// promise_handler
|
|
620
676
|
if (checks['promise_handler'] !== 'off') {
|
|
621
677
|
visitor.on('*', node => {
|
|
622
|
-
if (node.type === 'list' &&
|
|
623
|
-
node.children
|
|
678
|
+
if (node.type === 'list' &&
|
|
679
|
+
node.children &&
|
|
680
|
+
node.children.length > 0 &&
|
|
681
|
+
node.children[0].type === 'symbol' &&
|
|
682
|
+
node.children[0].name &&
|
|
624
683
|
(node.children[0].name.startsWith('vlax-invoke') || node.children[0].name.startsWith('vlax-invoke-method'))) {
|
|
625
684
|
if (isInQuote(node))
|
|
626
685
|
return;
|
|
627
686
|
const parent = node.parent;
|
|
628
|
-
if (parent &&
|
|
629
|
-
parent.
|
|
687
|
+
if (parent &&
|
|
688
|
+
parent.type === 'list' &&
|
|
689
|
+
parent.children &&
|
|
690
|
+
parent.children.length > 0 &&
|
|
691
|
+
parent.children[0].type === 'symbol' &&
|
|
692
|
+
parent.children[0].name === 'vl-catch-all-apply') {
|
|
630
693
|
return;
|
|
631
694
|
}
|
|
632
695
|
let hasCallback = false;
|
|
633
696
|
for (const child of (node.children || []).slice(1)) {
|
|
634
|
-
if (child.type === 'list' &&
|
|
697
|
+
if (child.type === 'list' &&
|
|
698
|
+
child.children &&
|
|
699
|
+
child.children.length > 0 &&
|
|
635
700
|
child.children[0].type === 'symbol' &&
|
|
636
701
|
(child.children[0].name === 'lambda' || child.children[0].name === 'function')) {
|
|
637
702
|
hasCallback = true;
|
|
@@ -654,15 +719,19 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
654
719
|
}
|
|
655
720
|
// dangerous_calls
|
|
656
721
|
if (checks['quit_exit'] !== 'off') {
|
|
657
|
-
const dc = config.dangerous_calls;
|
|
658
722
|
for (const call of state.dangerousCalls) {
|
|
659
723
|
const sev = config.dangerous_calls[call.name === 'vl-registry-write' ? 'vl_registry_write' : call.name] || 'off';
|
|
660
724
|
if (sev === 'off')
|
|
661
725
|
continue;
|
|
662
|
-
const msgKey = call.name === 'quit'
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
726
|
+
const msgKey = call.name === 'quit'
|
|
727
|
+
? 'dangerous.quit'
|
|
728
|
+
: call.name === 'exit'
|
|
729
|
+
? 'dangerous.exit'
|
|
730
|
+
: call.name === 'startapp'
|
|
731
|
+
? 'dangerous.startapp'
|
|
732
|
+
: call.name === 'vl-registry-write'
|
|
733
|
+
? 'dangerous.vl_registry_write'
|
|
734
|
+
: '';
|
|
666
735
|
if (msgKey) {
|
|
667
736
|
addIssue(call.name === 'vl-registry-write' ? 'vl_registry_write' : call.name, call.node.pos.line, (0, locale_1.t)(msgKey));
|
|
668
737
|
}
|
|
@@ -682,7 +751,6 @@ function runChecksWithVisitor(ast, file, config, disableMap) {
|
|
|
682
751
|
// function_complexity
|
|
683
752
|
if (checks['function_complexity'] !== 'off') {
|
|
684
753
|
const maxLines = config.function_complexity.max_lines;
|
|
685
|
-
const maxNesting = config.function_complexity.max_nesting;
|
|
686
754
|
for (const scope of state.defunScopes) {
|
|
687
755
|
let maxLine = scope.line;
|
|
688
756
|
const computeEnd = (nodes) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlisp/lint",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "AutoLISP static analysis tool — parens, security, conventions, SBCL syntax validation",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"CAD",
|
|
@@ -37,19 +37,19 @@
|
|
|
37
37
|
"prepack": "npm run build"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@atlisp/parser": "^0.1.
|
|
40
|
+
"@atlisp/parser": "^0.1.5"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@types/jest": "^
|
|
44
|
-
"@types/node": "^
|
|
45
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
46
|
-
"@typescript-eslint/parser": "^
|
|
47
|
-
"eslint": "^
|
|
48
|
-
"jest": "^
|
|
43
|
+
"@types/jest": "^30",
|
|
44
|
+
"@types/node": "^26.1.0",
|
|
45
|
+
"@typescript-eslint/eslint-plugin": "^8",
|
|
46
|
+
"@typescript-eslint/parser": "^8",
|
|
47
|
+
"eslint": "^10.6.0",
|
|
48
|
+
"jest": "^30",
|
|
49
49
|
"prettier": "^3",
|
|
50
50
|
"ts-jest": "^29",
|
|
51
51
|
"ts-node": "^10.9.2",
|
|
52
|
-
"typescript": "^
|
|
52
|
+
"typescript": "^6.0.3"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": ">=18"
|