@atlisp/lint 0.2.16 → 0.2.17
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 +1 -1
- package/atlisp-lint.default.json +1 -1
- package/dist/checks/arg-count.d.ts +2 -1
- package/dist/checks/arg-count.js +37 -19
- package/dist/checks/builtin-arg-count.d.ts +2 -1
- package/dist/checks/builtin-arg-count.js +3 -2
- package/dist/checks/cond-duplicate.js +5 -6
- package/dist/checks/constant-condition.js +9 -9
- package/dist/checks/dangerous-calls.js +1 -1
- package/dist/checks/dangling-defun.d.ts +0 -7
- package/dist/checks/dangling-defun.js +0 -63
- package/dist/checks/double-not.js +1 -1
- package/dist/checks/empty-branch.js +1 -1
- package/dist/checks/empty-catch.d.ts +2 -1
- package/dist/checks/empty-catch.js +8 -7
- package/dist/checks/function-complexity.js +5 -18
- package/dist/checks/global-naming.js +1 -1
- package/dist/checks/identical-branches.js +3 -3
- package/dist/checks/if-without-else.js +1 -1
- package/dist/checks/misplaced-else.js +2 -2
- package/dist/checks/missing-export.js +3 -3
- package/dist/checks/missing-princ.js +1 -1
- package/dist/checks/multiple-setq.js +1 -1
- package/dist/checks/no-return.d.ts +2 -1
- package/dist/checks/no-return.js +4 -3
- package/dist/checks/nth-usage.js +1 -1
- package/dist/checks/parameter-naming.js +2 -2
- 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-setq.js +1 -1
- package/dist/checks/self-compare.js +1 -1
- package/dist/checks/setq-single-arg.js +3 -2
- package/dist/checks/single-arg-and-or.js +1 -1
- package/dist/checks/undeclared-setq.d.ts +2 -1
- package/dist/checks/undeclared-setq.js +6 -5
- package/dist/checks/unused-let.js +3 -3
- package/dist/checks/unused-local-fun.js +2 -2
- package/dist/checks/unused-package-dep.d.ts +0 -1
- package/dist/checks/unused-package-dep.js +6 -93
- package/dist/checks/unused-param.js +2 -2
- package/dist/checks/unused-variable.js +2 -2
- package/dist/checks/variable-shadow.js +3 -3
- package/dist/checks/while-constant.js +1 -1
- package/dist/cli/collector.js +26 -19
- package/dist/config.d.ts +1 -1
- package/dist/config.js +18 -9
- package/dist/fixes/index.d.ts +4 -0
- package/dist/fixes/index.js +843 -140
- package/dist/formatter.d.ts +0 -1
- package/dist/formatter.js +0 -6
- package/dist/index.js +59 -51
- package/dist/project.d.ts +1 -1
- package/dist/project.js +98 -112
- package/dist/runner.d.ts +1 -1
- package/dist/runner.js +17 -8
- package/dist/utils.js +21 -30
- package/dist/validate.js +3 -3
- package/dist/visitor-runner.d.ts +0 -28
- package/dist/visitor-runner.js +3 -809
- package/dist/visitors/equal-nil.d.ts +2 -2
- package/dist/visitors/equal-nil.js +5 -4
- package/dist/visitors/index.d.ts +1 -1
- package/dist/visitors/progn-in-cond.d.ts +2 -2
- package/dist/visitors/progn-in-cond.js +5 -4
- package/dist/visitors/redundant-and-or.d.ts +2 -2
- package/dist/visitors/redundant-and-or.js +3 -3
- package/dist/visitors/redundant-car-cdr.d.ts +2 -2
- package/dist/visitors/redundant-car-cdr.js +3 -3
- package/dist/visitors/register.d.ts +6 -0
- package/dist/visitors/register.js +625 -0
- package/dist/visitors/shared.d.ts +40 -0
- package/dist/visitors/shared.js +129 -0
- package/dist/visitors/types.d.ts +2 -7
- package/dist/visitors/types.js +0 -69
- package/dist/watch.js +33 -12
- package/dist/worker.js +1 -1
- package/package.json +2 -2
package/dist/formatter.d.ts
CHANGED
package/dist/formatter.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.formatCode = formatCode;
|
|
4
|
-
exports.formatCodeStable = formatCodeStable;
|
|
5
4
|
const utils_1 = require("./utils");
|
|
6
5
|
function isCommentLine(line) {
|
|
7
6
|
const trimmed = line.trim();
|
|
@@ -56,9 +55,4 @@ function formatCode(content, indentSize = 2) {
|
|
|
56
55
|
}
|
|
57
56
|
return output;
|
|
58
57
|
}
|
|
59
|
-
function formatCodeStable(content, indentSize = 2) {
|
|
60
|
-
const first = formatCode(content, indentSize);
|
|
61
|
-
const second = formatCode(first, indentSize);
|
|
62
|
-
return second;
|
|
63
|
-
}
|
|
64
58
|
//# sourceMappingURL=formatter.js.map
|
package/dist/index.js
CHANGED
|
@@ -215,7 +215,7 @@ async function main() {
|
|
|
215
215
|
if (!opts.staged)
|
|
216
216
|
return;
|
|
217
217
|
}
|
|
218
|
-
// Filter cached files (pre-read content to avoid double I/O)
|
|
218
|
+
// Filter cached files (pre-read content to avoid double I/O for linting and display)
|
|
219
219
|
const contentCache = new Map();
|
|
220
220
|
const filesToLint = opts.cache
|
|
221
221
|
? files.filter(f => {
|
|
@@ -234,70 +234,67 @@ async function main() {
|
|
|
234
234
|
}
|
|
235
235
|
return !cached;
|
|
236
236
|
})
|
|
237
|
-
: files
|
|
237
|
+
: files.filter(f => {
|
|
238
|
+
try {
|
|
239
|
+
const content = fs.readFileSync(f, 'utf-8');
|
|
240
|
+
contentCache.set(f, content);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
/* ignore */
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
});
|
|
238
247
|
if (filesToLint.length === 0) {
|
|
239
248
|
console.log((0, locale_1.t)('index.all_cached'));
|
|
240
249
|
return;
|
|
241
250
|
}
|
|
242
251
|
// Phase 1: Static checks (parallel or sequential)
|
|
243
|
-
const contentArg =
|
|
252
|
+
const contentArg = contentCache.size > 0 ? contentCache : undefined;
|
|
244
253
|
const issues = opts.parallel
|
|
245
|
-
? await (0, runner_1.lintFilesParallel)(filesToLint, config, rootDir)
|
|
254
|
+
? await (0, runner_1.lintFilesParallel)(filesToLint, config, rootDir, contentArg)
|
|
246
255
|
: (0, runner_1.lintFiles)(filesToLint, config, rootDir, contentArg);
|
|
247
256
|
// Phase 1b: Project-level cross-file checks
|
|
248
257
|
if (opts.project) {
|
|
249
|
-
const projectIssues = (0, project_1.lintProject)(filesToLint, config, rootDir);
|
|
258
|
+
const projectIssues = await (0, project_1.lintProject)(filesToLint, config, rootDir);
|
|
250
259
|
issues.push(...projectIssues);
|
|
251
260
|
}
|
|
252
|
-
// Apply rule-based fixes (
|
|
261
|
+
// Apply rule-based fixes + format_indent (merged single pass, when --fix is set)
|
|
253
262
|
if (opts.fix) {
|
|
263
|
+
const indentSize = config.line_length.tab_width;
|
|
264
|
+
const formatEnabled = config.checks['format_indent'] !== 'off';
|
|
254
265
|
for (const f of filesToLint) {
|
|
255
266
|
const relPath = path.relative(rootDir, f);
|
|
256
267
|
const fileIssues = issues.filter(i => i.file === relPath);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
else {
|
|
266
|
-
fs.writeFileSync(f, fixed, 'utf-8');
|
|
267
|
-
console.log(`${(0, locale_1.t)('summary.tag_fail')}: ${relPath} — ${(0, locale_1.t)('index.fixed')}`);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
catch (e) {
|
|
272
|
-
console.warn(`[lint] applyFixes failed for ${relPath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
268
|
+
const hasFormatIssue = formatEnabled && issues.some(i => i.file === relPath && i.rule === 'format_indent');
|
|
269
|
+
if (fileIssues.length === 0 && !hasFormatIssue)
|
|
270
|
+
continue;
|
|
271
|
+
try {
|
|
272
|
+
const content = fs.readFileSync(f, 'utf-8');
|
|
273
|
+
let changed = content;
|
|
274
|
+
if (fileIssues.length > 0) {
|
|
275
|
+
changed = (0, runner_1.applyFixes)(fileIssues, changed, relPath);
|
|
273
276
|
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
// format_indent fix: apply formatCode to files with format_indent issues
|
|
277
|
-
if (config.checks['format_indent'] !== 'off') {
|
|
278
|
-
const indentSize = config.line_length.tab_width;
|
|
279
|
-
for (const f of filesToLint) {
|
|
280
|
-
const relPath = path.relative(rootDir, f);
|
|
281
|
-
const hasFormatIssue = issues.some(i => i.file === relPath && i.rule === 'format_indent');
|
|
282
277
|
if (hasFormatIssue) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
}
|
|
278
|
+
changed = (0, formatter_1.formatCode)(changed, indentSize);
|
|
279
|
+
}
|
|
280
|
+
if (changed !== content) {
|
|
281
|
+
if (opts.dryRun) {
|
|
282
|
+
const parts = [];
|
|
283
|
+
if (fileIssues.length > 0)
|
|
284
|
+
parts.push(`would apply ${fileIssues.length} fix(es)`);
|
|
285
|
+
if (hasFormatIssue)
|
|
286
|
+
parts.push('would fix format_indent');
|
|
287
|
+
console.log(`${(0, locale_1.t)('summary.tag_info')}: ${relPath} — ${parts.join('; ')}`);
|
|
295
288
|
}
|
|
296
|
-
|
|
297
|
-
|
|
289
|
+
else {
|
|
290
|
+
fs.writeFileSync(f, changed, 'utf-8');
|
|
291
|
+
console.log(`${(0, locale_1.t)('summary.tag_fail')}: ${relPath} — ${(0, locale_1.t)('index.fixed')}`);
|
|
298
292
|
}
|
|
299
293
|
}
|
|
300
294
|
}
|
|
295
|
+
catch (e) {
|
|
296
|
+
console.warn(`[lint] fix failed for ${relPath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
297
|
+
}
|
|
301
298
|
}
|
|
302
299
|
}
|
|
303
300
|
// Mark as cached
|
|
@@ -324,22 +321,33 @@ async function main() {
|
|
|
324
321
|
});
|
|
325
322
|
}
|
|
326
323
|
}
|
|
327
|
-
// Build file content map for code line display
|
|
324
|
+
// Build file content map for code line display (reuse pre-read content)
|
|
328
325
|
const fileContents = new Map();
|
|
329
326
|
for (const iss of issues) {
|
|
330
327
|
if (!fileContents.has(iss.file)) {
|
|
331
328
|
const fp = path.resolve(rootDir, iss.file);
|
|
332
|
-
|
|
333
|
-
|
|
329
|
+
const cached = contentCache.get(fp);
|
|
330
|
+
if (cached !== undefined) {
|
|
331
|
+
fileContents.set(iss.file, cached);
|
|
334
332
|
}
|
|
335
|
-
|
|
336
|
-
|
|
333
|
+
else {
|
|
334
|
+
try {
|
|
335
|
+
fileContents.set(iss.file, fs.readFileSync(fp, 'utf-8'));
|
|
336
|
+
}
|
|
337
|
+
catch (e) {
|
|
338
|
+
console.warn(`[lint] failed to read ${fp} for display: ${e instanceof Error ? e.message : String(e)}`);
|
|
339
|
+
}
|
|
337
340
|
}
|
|
338
341
|
}
|
|
339
342
|
}
|
|
340
343
|
// Format & output
|
|
341
|
-
|
|
342
|
-
|
|
344
|
+
let errorCount = 0, warningCount = 0;
|
|
345
|
+
for (const i of issues) {
|
|
346
|
+
if (i.severity === 'error')
|
|
347
|
+
errorCount++;
|
|
348
|
+
else if (i.severity === 'warn')
|
|
349
|
+
warningCount++;
|
|
350
|
+
}
|
|
343
351
|
if (opts.format === 'json') {
|
|
344
352
|
console.log((0, formatters_1.formatJson)(issues, errorCount, warningCount));
|
|
345
353
|
}
|
package/dist/project.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { Issue, LintConfig } from './types';
|
|
2
|
-
export declare function lintProject(files: string[], config: LintConfig, rootDir: string): Issue[]
|
|
2
|
+
export declare function lintProject(files: string[], config: LintConfig, rootDir: string): Promise<Issue[]>;
|
|
3
3
|
//# sourceMappingURL=project.d.ts.map
|
package/dist/project.js
CHANGED
|
@@ -37,6 +37,7 @@ exports.lintProject = lintProject;
|
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const parser_1 = require("@atlisp/parser");
|
|
40
|
+
const fsp = __importStar(require("fs/promises"));
|
|
40
41
|
const dangling_defun_1 = require("./checks/dangling-defun");
|
|
41
42
|
const missing_export_1 = require("./checks/missing-export");
|
|
42
43
|
const unused_package_dep_1 = require("./checks/unused-package-dep");
|
|
@@ -45,72 +46,52 @@ const builtin_arg_count_1 = require("./checks/builtin-arg-count");
|
|
|
45
46
|
const config_1 = require("./config");
|
|
46
47
|
const locale_1 = require("./locale");
|
|
47
48
|
const locale_2 = require("./locale");
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
const shared_1 = require("./visitors/shared");
|
|
50
|
+
function collectProjectMetadata(ast) {
|
|
51
|
+
const defuns = [];
|
|
52
|
+
const refs = [];
|
|
53
|
+
const allLists = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n));
|
|
54
|
+
for (const node of allLists) {
|
|
55
|
+
if (!(0, parser_1.astIsList)(node) || node.children.length === 0)
|
|
56
|
+
continue;
|
|
57
|
+
const car = node.children[0];
|
|
58
|
+
if (!(0, parser_1.astIsSymbol)(car))
|
|
59
|
+
continue;
|
|
60
|
+
const name = car.name;
|
|
61
|
+
if ((name === 'defun' || name === 'defun-q') && node.children.length >= 2 && (0, parser_1.astIsSymbol)(node.children[1])) {
|
|
62
|
+
defuns.push({ name: node.children[1].name, line: node.pos.line });
|
|
54
63
|
}
|
|
55
|
-
}
|
|
56
|
-
return results;
|
|
57
|
-
}
|
|
58
|
-
function collectReferencesFromAst(ast) {
|
|
59
|
-
const results = [];
|
|
60
|
-
const allCalls = (0, parser_1.astFindAll)(ast, n => {
|
|
61
|
-
if (n.type !== 'list' || !n.children || n.children.length === 0)
|
|
62
|
-
return false;
|
|
63
|
-
return (0, parser_1.astIsSymbol)(n.children[0]);
|
|
64
|
-
});
|
|
65
|
-
for (const call of allCalls) {
|
|
66
|
-
const name = call.children[0].name;
|
|
67
64
|
if (name !== 'defun' && name !== 'defun-q' && !name.startsWith('c:') && !name.includes(':')) {
|
|
68
|
-
|
|
65
|
+
refs.push({ name, line: node.pos.line });
|
|
69
66
|
}
|
|
70
67
|
}
|
|
71
|
-
return
|
|
68
|
+
return { defuns, refs };
|
|
72
69
|
}
|
|
73
70
|
function countFunctionArgsFromAst(ast) {
|
|
74
71
|
const result = new Map();
|
|
75
72
|
const defunNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'defun') || (0, parser_1.astIsList)(n, 'defun-q'));
|
|
76
73
|
for (const node of defunNodes) {
|
|
77
|
-
if (!node
|
|
74
|
+
if (!(0, parser_1.astIsList)(node) || node.children.length < 3)
|
|
78
75
|
continue;
|
|
79
76
|
const name = (0, parser_1.astIsSymbol)(node.children[1]) ? node.children[1].name : '';
|
|
80
77
|
if (!name)
|
|
81
78
|
continue;
|
|
82
|
-
|
|
83
|
-
if (paramList.type !== 'list' || !paramList.children) {
|
|
84
|
-
result.set(name, 0);
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
let count = 0;
|
|
88
|
-
for (const child of paramList.children) {
|
|
89
|
-
if (child.type === 'symbol' && child.name && child.name !== '/') {
|
|
90
|
-
count++;
|
|
91
|
-
}
|
|
92
|
-
else if (child.type === 'symbol' && child.name === '/') {
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
result.set(name, count);
|
|
79
|
+
result.set(name, (0, shared_1.defunParams)(node).length);
|
|
97
80
|
}
|
|
98
81
|
return result;
|
|
99
82
|
}
|
|
100
83
|
function countCallArgsFromAst(ast) {
|
|
101
84
|
const result = new Map();
|
|
102
|
-
const allCalls = (0, parser_1.astFindAll)(ast, n =>
|
|
103
|
-
if (n.type !== 'list' || !n.children || n.children.length === 0)
|
|
104
|
-
return false;
|
|
105
|
-
return (0, parser_1.astIsSymbol)(n.children[0]);
|
|
106
|
-
});
|
|
85
|
+
const allCalls = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n) && n.children.length > 0 && (0, parser_1.astIsSymbol)(n.children[0]));
|
|
107
86
|
for (const call of allCalls) {
|
|
87
|
+
if (!(0, parser_1.astIsList)(call) || !(0, parser_1.astIsSymbol)(call.children[0]))
|
|
88
|
+
continue;
|
|
108
89
|
const name = call.children[0].name;
|
|
109
90
|
if (name === 'defun' || name === 'defun-q' || name.startsWith('c:') || name.includes(':'))
|
|
110
91
|
continue;
|
|
111
92
|
if (!result.has(name))
|
|
112
93
|
result.set(name, []);
|
|
113
|
-
const argCount = call.children
|
|
94
|
+
const argCount = call.children.length - 1;
|
|
114
95
|
result.get(name).push({ line: call.pos.line, count: argCount });
|
|
115
96
|
}
|
|
116
97
|
return result;
|
|
@@ -120,26 +101,25 @@ function findModuleDepsFromAst(ast) {
|
|
|
120
101
|
const exports = [];
|
|
121
102
|
const inPackageNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'in-package'));
|
|
122
103
|
for (const node of inPackageNodes) {
|
|
123
|
-
if (node
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
104
|
+
if (!(0, parser_1.astIsList)(node) || node.children.length < 3)
|
|
105
|
+
continue;
|
|
106
|
+
const useList = node.children[2];
|
|
107
|
+
if ((0, parser_1.astIsList)(useList) && useList.children) {
|
|
108
|
+
for (const use of useList.children) {
|
|
109
|
+
if ((0, parser_1.astIsSymbol)(use) && use.name)
|
|
110
|
+
imports.push(use.name);
|
|
130
111
|
}
|
|
131
112
|
}
|
|
132
113
|
}
|
|
133
114
|
const setqNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'setq'));
|
|
134
115
|
for (const node of setqNodes) {
|
|
135
|
-
if (node
|
|
116
|
+
if ((0, parser_1.astIsList)(node) &&
|
|
136
117
|
node.children.length >= 3 &&
|
|
137
|
-
node.children[1]
|
|
118
|
+
(0, parser_1.astIsSymbol)(node.children[1]) &&
|
|
138
119
|
node.children[1].name === '@::*modules*' &&
|
|
139
|
-
node.children[2]
|
|
140
|
-
node.children[2].children) {
|
|
120
|
+
(0, parser_1.astIsList)(node.children[2])) {
|
|
141
121
|
for (const exp of node.children[2].children) {
|
|
142
|
-
if (
|
|
122
|
+
if (typeof exp.value === 'string') {
|
|
143
123
|
exports.push(exp.value);
|
|
144
124
|
}
|
|
145
125
|
}
|
|
@@ -249,38 +229,52 @@ function checkModuleCycle(moduleDeps, rootDir, allIssues) {
|
|
|
249
229
|
}
|
|
250
230
|
}
|
|
251
231
|
}
|
|
252
|
-
function lintProject(files, config, rootDir) {
|
|
232
|
+
async function lintProject(files, config, rootDir) {
|
|
253
233
|
(0, locale_1.setLocale)(config.locale || 'zh');
|
|
254
234
|
const allIssues = [];
|
|
255
|
-
const symbols = {
|
|
235
|
+
const symbols = {
|
|
236
|
+
defuns: new Map(),
|
|
237
|
+
references: new Map(),
|
|
238
|
+
fileDefuns: new Map(),
|
|
239
|
+
fileRefs: new Map(),
|
|
240
|
+
};
|
|
256
241
|
const maxFiles = config.project_analysis?.maxFiles ?? 500;
|
|
257
242
|
if (files.length > maxFiles) {
|
|
258
243
|
const msg = `${files.length} files exceeds project_analysis.maxFiles (${maxFiles}). Skipping cross-file analysis. To increase, set project_analysis.maxFiles in config.`;
|
|
259
244
|
console.warn(`[atlisp-lint] ${msg}`);
|
|
260
245
|
return [];
|
|
261
246
|
}
|
|
262
|
-
const fileContents = new Map();
|
|
263
247
|
const defunArgs = new Map();
|
|
264
248
|
const callArgCounts = new Map();
|
|
265
249
|
const moduleDeps = new Map();
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
250
|
+
const checks = config.checks;
|
|
251
|
+
const needsContent = checks['unused_package_dep'] !== 'off';
|
|
252
|
+
// Phase A: Read all files in parallel, then process metadata sequentially
|
|
253
|
+
const processedFiles = [];
|
|
254
|
+
const contentStore = needsContent ? new Map() : undefined;
|
|
255
|
+
const readResults = await Promise.all(files.map(async (filepath) => {
|
|
269
256
|
try {
|
|
270
|
-
content =
|
|
257
|
+
const content = await fsp.readFile(filepath, 'utf-8');
|
|
258
|
+
return { filepath, content, ok: true };
|
|
271
259
|
}
|
|
272
260
|
catch {
|
|
273
|
-
|
|
261
|
+
return { filepath, ok: false };
|
|
274
262
|
}
|
|
263
|
+
}));
|
|
264
|
+
for (const result of readResults) {
|
|
265
|
+
if (!result.ok)
|
|
266
|
+
continue;
|
|
267
|
+
const { filepath, content } = result;
|
|
275
268
|
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
276
269
|
// Extract all metadata from a single AST parse
|
|
277
|
-
const defunList =
|
|
270
|
+
const { defuns: defunList, refs: refList } = collectProjectMetadata(ast);
|
|
271
|
+
symbols.fileDefuns.set(filepath, defunList);
|
|
278
272
|
for (const d of defunList) {
|
|
279
273
|
const list = symbols.defuns.get(d.name) || [];
|
|
280
274
|
list.push({ file: filepath, line: d.line });
|
|
281
275
|
symbols.defuns.set(d.name, list);
|
|
282
276
|
}
|
|
283
|
-
|
|
277
|
+
symbols.fileRefs.set(filepath, refList);
|
|
284
278
|
for (const r of refList) {
|
|
285
279
|
const list = symbols.references.get(r.name) || [];
|
|
286
280
|
list.push({ file: filepath, line: r.line });
|
|
@@ -289,31 +283,24 @@ function lintProject(files, config, rootDir) {
|
|
|
289
283
|
defunArgs.set(filepath, countFunctionArgsFromAst(ast));
|
|
290
284
|
callArgCounts.set(filepath, countCallArgsFromAst(ast));
|
|
291
285
|
moduleDeps.set(filepath, findModuleDepsFromAst(ast));
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
286
|
+
processedFiles.push(filepath);
|
|
287
|
+
if (contentStore)
|
|
288
|
+
contentStore.set(filepath, content);
|
|
295
289
|
}
|
|
296
290
|
// Phase B: Cross-file checks using only symbol tables
|
|
297
|
-
for (const
|
|
291
|
+
for (const filepath of processedFiles) {
|
|
298
292
|
const relPath = path.relative(rootDir, filepath);
|
|
299
293
|
const override = (0, config_1.findOverrides)(filepath);
|
|
300
|
-
const
|
|
301
|
-
if (
|
|
302
|
-
const defunList = [];
|
|
303
|
-
for (const [name, locations] of symbols.defuns) {
|
|
304
|
-
for (const loc of locations) {
|
|
305
|
-
if (loc.file === filepath) {
|
|
306
|
-
defunList.push({ name, line: loc.line });
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
294
|
+
const fileChecks = override?.checks || checks;
|
|
295
|
+
if (fileChecks['dangling_defun'] !== 'off') {
|
|
296
|
+
const defunList = symbols.fileDefuns.get(filepath) || [];
|
|
310
297
|
const danglingIssues = (0, dangling_defun_1.checkDanglingDefunFromDefs)(defunList, symbols.references, filepath);
|
|
311
298
|
for (const iss of danglingIssues) {
|
|
312
299
|
iss.file = relPath;
|
|
313
300
|
allIssues.push(iss);
|
|
314
301
|
}
|
|
315
302
|
}
|
|
316
|
-
if (
|
|
303
|
+
if (fileChecks['missing_export'] !== 'off') {
|
|
317
304
|
const pkgDir = findPackageDir(filepath);
|
|
318
305
|
if (pkgDir) {
|
|
319
306
|
const missingIssues = (0, missing_export_1.checkMissingExport)(filepath, pkgDir, symbols.defuns);
|
|
@@ -323,8 +310,8 @@ function lintProject(files, config, rootDir) {
|
|
|
323
310
|
}
|
|
324
311
|
}
|
|
325
312
|
}
|
|
326
|
-
if (
|
|
327
|
-
const content =
|
|
313
|
+
if (fileChecks['unused_package_dep'] !== 'off') {
|
|
314
|
+
const content = contentStore?.get(filepath);
|
|
328
315
|
if (content) {
|
|
329
316
|
const depIssues = (0, unused_package_dep_1.checkUnusedPackageDepFromContent)(filepath, content);
|
|
330
317
|
for (const iss of depIssues) {
|
|
@@ -333,65 +320,64 @@ function lintProject(files, config, rootDir) {
|
|
|
333
320
|
}
|
|
334
321
|
}
|
|
335
322
|
}
|
|
336
|
-
if (
|
|
323
|
+
if (fileChecks['duplicate_defun'] !== 'off') {
|
|
337
324
|
const dupIssues = (0, duplicate_defun_1.checkDuplicateDefun)(filepath, symbols.defuns);
|
|
338
325
|
for (const iss of dupIssues) {
|
|
339
326
|
iss.file = relPath;
|
|
340
327
|
allIssues.push(iss);
|
|
341
328
|
}
|
|
342
329
|
}
|
|
343
|
-
if (
|
|
330
|
+
if (fileChecks['arg_count_project'] !== 'off') {
|
|
344
331
|
const argIssues = checkArgCountProject(filepath, defunArgs, callArgCounts, symbols, relPath);
|
|
345
332
|
allIssues.push(...argIssues);
|
|
346
333
|
}
|
|
347
|
-
if (
|
|
348
|
-
const definedHere = new Set();
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
definedHere.add(name);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
for (const [name, refs] of symbols.references) {
|
|
356
|
-
if (builtin_arg_count_1.BUILTIN_FUNCTIONS.has(name))
|
|
334
|
+
if (fileChecks['function_reference'] !== 'off') {
|
|
335
|
+
const definedHere = new Set((symbols.fileDefuns.get(filepath) || []).map(d => d.name));
|
|
336
|
+
const fileRefs = symbols.fileRefs.get(filepath) || [];
|
|
337
|
+
for (const ref of fileRefs) {
|
|
338
|
+
if (builtin_arg_count_1.BUILTIN_FUNCTIONS.has(ref.name))
|
|
357
339
|
continue;
|
|
358
|
-
if (definedHere.has(name))
|
|
340
|
+
if (definedHere.has(ref.name))
|
|
359
341
|
continue;
|
|
360
|
-
if (symbols.defuns.has(name))
|
|
342
|
+
if (symbols.defuns.has(ref.name))
|
|
361
343
|
continue;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
message: (0, locale_2.t)('function_reference', name),
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
}
|
|
344
|
+
allIssues.push({
|
|
345
|
+
file: relPath,
|
|
346
|
+
line: ref.line,
|
|
347
|
+
severity: 'warn',
|
|
348
|
+
rule: 'function_reference',
|
|
349
|
+
message: (0, locale_2.t)('function_reference', ref.name),
|
|
350
|
+
});
|
|
373
351
|
}
|
|
374
352
|
}
|
|
375
353
|
}
|
|
376
354
|
// Module cycle check: run DFS once (not per file)
|
|
377
|
-
|
|
378
|
-
|
|
355
|
+
if (processedFiles.length > 0) {
|
|
356
|
+
const cycleCheckFile = processedFiles[0];
|
|
379
357
|
const override = (0, config_1.findOverrides)(cycleCheckFile);
|
|
380
|
-
const
|
|
381
|
-
if (
|
|
358
|
+
const fileChecks = override?.checks || checks;
|
|
359
|
+
if (fileChecks['module_cycle'] !== 'off') {
|
|
382
360
|
checkModuleCycle(moduleDeps, rootDir, allIssues);
|
|
383
361
|
}
|
|
384
362
|
}
|
|
385
363
|
return allIssues;
|
|
386
364
|
}
|
|
365
|
+
const packageDirCache = new Map();
|
|
387
366
|
function findPackageDir(filepath) {
|
|
367
|
+
const cached = packageDirCache.get(filepath);
|
|
368
|
+
if (cached !== undefined)
|
|
369
|
+
return cached;
|
|
388
370
|
let dir = path.dirname(filepath);
|
|
389
371
|
for (;;) {
|
|
390
|
-
if (fs.existsSync(path.join(dir, 'pkg.lsp')))
|
|
372
|
+
if (fs.existsSync(path.join(dir, 'pkg.lsp'))) {
|
|
373
|
+
packageDirCache.set(filepath, dir);
|
|
391
374
|
return dir;
|
|
375
|
+
}
|
|
392
376
|
const parent = path.dirname(dir);
|
|
393
|
-
if (parent === dir)
|
|
377
|
+
if (parent === dir) {
|
|
378
|
+
packageDirCache.set(filepath, null);
|
|
394
379
|
return null;
|
|
380
|
+
}
|
|
395
381
|
dir = parent;
|
|
396
382
|
}
|
|
397
383
|
}
|
package/dist/runner.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Issue, LintConfig } from './types';
|
|
2
2
|
export declare function runChecks(content: string, file: string, config: LintConfig): Issue[];
|
|
3
3
|
export declare function lintFiles(files: string[], config: LintConfig, rootDir: string, contentCache?: Map<string, string>): Issue[];
|
|
4
|
-
export declare function lintFilesParallel(files: string[], config: LintConfig, rootDir: string): Promise<Issue[]>;
|
|
4
|
+
export declare function lintFilesParallel(files: string[], config: LintConfig, rootDir: string, contentCache?: Map<string, string>): Promise<Issue[]>;
|
|
5
5
|
export { applyFixesFromProviders as applyFixes, FIXABLE_RULES } from './fixes/index';
|
|
6
6
|
export declare function parseIgnoreFile(rootDir: string): string[];
|
|
7
7
|
export declare function fixFile(filepath: string): string[];
|
package/dist/runner.js
CHANGED
|
@@ -138,15 +138,21 @@ function runChecks(content, file, config) {
|
|
|
138
138
|
addIfEnabled('error_handling', () => (0, error_handling_1.checkErrorHandling)(content, file));
|
|
139
139
|
addIfEnabled('global_naming', () => (0, global_naming_1.checkGlobalNaming)(content, file));
|
|
140
140
|
addIfEnabled('extra_parens', () => (0, extra_parens_1.checkExtraParens)(content, file));
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
// Early AST parse for checks that need AST but aren't visitor-based
|
|
142
|
+
let ast;
|
|
143
|
+
const needsEarlyAst = ['arg_count', 'builtin_arg_count', 'empty_catch', 'no_return', 'undeclared_setq'].some(r => checks[r] !== 'off');
|
|
144
|
+
if (needsEarlyAst) {
|
|
145
|
+
ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
146
|
+
}
|
|
147
|
+
addIfEnabled('arg_count', () => (0, arg_count_1.checkArgCount)(content, file, ast));
|
|
148
|
+
addIfEnabled('builtin_arg_count', () => (0, builtin_arg_count_1.checkBuiltinArgCount)(content, file, ast));
|
|
143
149
|
addIfEnabled('strcat_usage', () => (0, strcat_usage_1.checkStrcatUsage)(content, file));
|
|
144
150
|
addIfEnabled('cond_simplify', () => (0, cond_simplify_1.checkCondSimplify)(content, file));
|
|
145
151
|
addIfEnabled('quote_style', () => (0, quote_style_1.checkQuoteStyle)(content, file));
|
|
146
152
|
addIfEnabled('eq_usage', () => (0, eq_usage_1.checkEqUsage)(content, file));
|
|
147
153
|
addIfEnabled('lambda_syntax', () => (0, lambda_syntax_1.checkLambdaSyntax)(content, file));
|
|
148
154
|
addIfEnabled('comment_style', () => (0, comment_style_1.checkCommentStyle)(content, file));
|
|
149
|
-
addIfEnabled('empty_catch', () => (0, empty_catch_1.checkEmptyCatch)(content, file));
|
|
155
|
+
addIfEnabled('empty_catch', () => (0, empty_catch_1.checkEmptyCatch)(content, file, ast));
|
|
150
156
|
addIfEnabled('nth_usage', () => (0, nth_usage_1.checkNthUsage)(content, file));
|
|
151
157
|
addIfEnabled('append_single', () => (0, append_single_1.checkAppendSingle)(content, file));
|
|
152
158
|
addIfEnabled('setq_multiple', () => (0, setq_multiple_1.checkSetqMultiple)(content, file));
|
|
@@ -154,12 +160,12 @@ function runChecks(content, file, config) {
|
|
|
154
160
|
addIfEnabled('magic_number', () => (0, magic_number_1.checkMagicNumber)(content, file));
|
|
155
161
|
addIfEnabled('mixed_indent', () => (0, mixed_indent_1.checkMixedIndent)(content, file));
|
|
156
162
|
addIfEnabled('long_function_call', () => (0, long_function_call_1.checkLongFunctionCall)(content, file, 6));
|
|
157
|
-
addIfEnabled('no_return', () => (0, no_return_1.checkNoReturn)(content, file));
|
|
163
|
+
addIfEnabled('no_return', () => (0, no_return_1.checkNoReturn)(content, file, ast));
|
|
158
164
|
addIfEnabled('shadow_builtin', () => (0, shadow_builtin_1.checkShadowBuiltin)(content, file));
|
|
159
165
|
addIfEnabled('dynamic_doc', () => (0, dynamic_doc_1.checkDynamicDoc)(content, file));
|
|
160
166
|
addIfEnabled('loop_optimization', () => (0, loop_optimization_1.checkLoopOptimization)(content, file));
|
|
161
167
|
addIfEnabled('redundant_if', () => (0, redundant_if_1.checkRedundantIf)(content, file));
|
|
162
|
-
addIfEnabled('undeclared_setq', () => (0, undeclared_setq_1.checkUndeclaredSetq)(content, file));
|
|
168
|
+
addIfEnabled('undeclared_setq', () => (0, undeclared_setq_1.checkUndeclaredSetq)(content, file, ast));
|
|
163
169
|
addIfEnabled('format_indent', () => (0, format_indent_1.checkFormatIndent)(content, file, config.line_length.tab_width));
|
|
164
170
|
// New checks
|
|
165
171
|
addIfEnabled('unused_catch_result', () => (0, unused_catch_result_1.checkUnusedCatchResult)(content, file));
|
|
@@ -212,7 +218,9 @@ function runChecks(content, file, config) {
|
|
|
212
218
|
const needsAst = Array.from(astRules).some(r => checks[r] !== 'off');
|
|
213
219
|
if (needsAst) {
|
|
214
220
|
try {
|
|
215
|
-
|
|
221
|
+
if (!ast) {
|
|
222
|
+
ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
223
|
+
}
|
|
216
224
|
const visitorIssues = (0, visitor_runner_1.runChecksWithVisitor)(ast, file, config, disableMap);
|
|
217
225
|
addIssues(visitorIssues);
|
|
218
226
|
}
|
|
@@ -261,7 +269,7 @@ function lintFiles(files, config, rootDir, contentCache) {
|
|
|
261
269
|
}
|
|
262
270
|
return allIssues;
|
|
263
271
|
}
|
|
264
|
-
function lintFilesParallel(files, config, rootDir) {
|
|
272
|
+
function lintFilesParallel(files, config, rootDir, contentCache) {
|
|
265
273
|
(0, locale_1.setLocale)(config.locale || 'zh');
|
|
266
274
|
return new Promise(resolve => {
|
|
267
275
|
const allIssues = [];
|
|
@@ -274,8 +282,9 @@ function lintFilesParallel(files, config, rootDir) {
|
|
|
274
282
|
const idx = nextIndex++;
|
|
275
283
|
const filepath = files[idx];
|
|
276
284
|
const relPath = path.relative(rootDir, filepath);
|
|
285
|
+
const content = contentCache?.get(filepath);
|
|
277
286
|
const worker = new worker_threads_1.Worker(path.join(__dirname, 'worker.js'), {
|
|
278
|
-
workerData: { filepath, relPath, config },
|
|
287
|
+
workerData: { filepath, relPath, config, content },
|
|
279
288
|
});
|
|
280
289
|
worker.on('message', (msg) => {
|
|
281
290
|
if (completed.has(idx))
|