@atlisp/lint 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/atlisp-lint.schema.json +32 -2
- package/dist/cache.js +19 -9
- package/dist/checks/builtin-arg-count.d.ts +1 -0
- package/dist/checks/builtin-arg-count.js +5 -0
- package/dist/checks/dangerous-calls.js +1 -0
- package/dist/checks/dangling-defun.d.ts +7 -0
- package/dist/checks/dangling-defun.js +19 -0
- package/dist/checks/global-function-naming.js +159 -35
- package/dist/checks/missing-princ.js +2 -2
- package/dist/checks/nth-usage.d.ts +1 -0
- package/dist/checks/nth-usage.js +16 -1
- package/dist/checks/unused-package-dep.d.ts +1 -0
- package/dist/checks/unused-package-dep.js +53 -0
- package/dist/cli/collector.d.ts +7 -0
- package/dist/cli/collector.js +154 -0
- package/dist/cli/parser.d.ts +30 -0
- package/dist/cli/parser.js +119 -0
- package/dist/config.js +32 -6
- package/dist/disable.js +12 -0
- package/dist/fixes/index.d.ts +8 -0
- package/dist/fixes/index.js +200 -0
- package/dist/index.js +56 -236
- package/dist/locale.js +30 -2
- package/dist/presets.js +24 -0
- package/dist/project.js +182 -134
- package/dist/rules.d.ts +1 -0
- package/dist/rules.js +85 -0
- package/dist/runner.d.ts +2 -3
- package/dist/runner.js +46 -371
- package/dist/types.d.ts +6 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +43 -0
- package/dist/validate.js +12 -0
- package/dist/visitor-runner.d.ts +28 -0
- package/dist/visitor-runner.js +115 -1
- package/dist/visitors/equal-nil.d.ts +8 -0
- package/dist/visitors/equal-nil.js +26 -0
- package/dist/visitors/index.d.ts +5 -0
- package/dist/visitors/index.js +14 -0
- package/dist/visitors/progn-in-cond.d.ts +8 -0
- package/dist/visitors/progn-in-cond.js +33 -0
- package/dist/visitors/redundant-and-or.d.ts +8 -0
- package/dist/visitors/redundant-and-or.js +27 -0
- package/dist/visitors/redundant-car-cdr.d.ts +8 -0
- package/dist/visitors/redundant-car-cdr.js +35 -0
- package/dist/visitors/types.d.ts +13 -0
- package/dist/visitors/types.js +72 -0
- package/package.json +1 -1
package/dist/project.js
CHANGED
|
@@ -41,10 +41,11 @@ const dangling_defun_1 = require("./checks/dangling-defun");
|
|
|
41
41
|
const missing_export_1 = require("./checks/missing-export");
|
|
42
42
|
const unused_package_dep_1 = require("./checks/unused-package-dep");
|
|
43
43
|
const duplicate_defun_1 = require("./checks/duplicate-defun");
|
|
44
|
+
const builtin_arg_count_1 = require("./checks/builtin-arg-count");
|
|
45
|
+
const config_1 = require("./config");
|
|
44
46
|
const locale_1 = require("./locale");
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
47
|
+
const locale_2 = require("./locale");
|
|
48
|
+
function collectDefunsFromAst(ast) {
|
|
48
49
|
const results = [];
|
|
49
50
|
const defunNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'defun') || (0, parser_1.astIsList)(n, 'defun-q'));
|
|
50
51
|
for (const node of defunNodes) {
|
|
@@ -54,9 +55,7 @@ function collectDefuns(file) {
|
|
|
54
55
|
}
|
|
55
56
|
return results;
|
|
56
57
|
}
|
|
57
|
-
function
|
|
58
|
-
const content = fs.readFileSync(file, 'utf-8');
|
|
59
|
-
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
58
|
+
function collectReferencesFromAst(ast) {
|
|
60
59
|
const results = [];
|
|
61
60
|
const allCalls = (0, parser_1.astFindAll)(ast, n => {
|
|
62
61
|
if (n.type !== 'list' || !n.children || n.children.length === 0)
|
|
@@ -71,9 +70,7 @@ function collectReferences(file) {
|
|
|
71
70
|
}
|
|
72
71
|
return results;
|
|
73
72
|
}
|
|
74
|
-
function
|
|
75
|
-
const content = fs.readFileSync(file, 'utf-8');
|
|
76
|
-
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
73
|
+
function countFunctionArgsFromAst(ast) {
|
|
77
74
|
const result = new Map();
|
|
78
75
|
const defunNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'defun') || (0, parser_1.astIsList)(n, 'defun-q'));
|
|
79
76
|
for (const node of defunNodes) {
|
|
@@ -100,9 +97,7 @@ function countFunctionArgs(file) {
|
|
|
100
97
|
}
|
|
101
98
|
return result;
|
|
102
99
|
}
|
|
103
|
-
function
|
|
104
|
-
const content = fs.readFileSync(file, 'utf-8');
|
|
105
|
-
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
100
|
+
function countCallArgsFromAst(ast) {
|
|
106
101
|
const result = new Map();
|
|
107
102
|
const allCalls = (0, parser_1.astFindAll)(ast, n => {
|
|
108
103
|
if (n.type !== 'list' || !n.children || n.children.length === 0)
|
|
@@ -120,9 +115,7 @@ function countCallArgs(file) {
|
|
|
120
115
|
}
|
|
121
116
|
return result;
|
|
122
117
|
}
|
|
123
|
-
function
|
|
124
|
-
const content = fs.readFileSync(filepath, 'utf-8');
|
|
125
|
-
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
118
|
+
function findModuleDepsFromAst(ast) {
|
|
126
119
|
const imports = [];
|
|
127
120
|
const exports = [];
|
|
128
121
|
const inPackageNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'in-package'));
|
|
@@ -154,56 +147,167 @@ function findModuleDeps(filepath) {
|
|
|
154
147
|
}
|
|
155
148
|
return { imports, exports };
|
|
156
149
|
}
|
|
157
|
-
function
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
150
|
+
function checkArgCountProject(filepath, defunArgs, callArgCounts, symbols, relPath) {
|
|
151
|
+
const issues = [];
|
|
152
|
+
const fileArgs = defunArgs.get(filepath);
|
|
153
|
+
if (!fileArgs)
|
|
154
|
+
return issues;
|
|
155
|
+
const callArgs = callArgCounts.get(filepath);
|
|
156
|
+
if (!callArgs)
|
|
157
|
+
return issues;
|
|
158
|
+
for (const [fnName, calls] of callArgs) {
|
|
159
|
+
const defs = symbols.defuns.get(fnName);
|
|
160
|
+
if (!defs)
|
|
161
|
+
continue;
|
|
162
|
+
const defArgCount = fileArgs.get(fnName);
|
|
163
|
+
if (defArgCount === undefined)
|
|
164
|
+
continue;
|
|
165
|
+
let allMatch = true;
|
|
166
|
+
for (const def of defs) {
|
|
167
|
+
const otherFileArgs = defunArgs.get(def.file);
|
|
168
|
+
if (otherFileArgs && otherFileArgs.get(fnName) !== defArgCount) {
|
|
169
|
+
allMatch = false;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!allMatch)
|
|
174
|
+
continue;
|
|
175
|
+
for (const call of calls) {
|
|
176
|
+
if (call.count !== defArgCount) {
|
|
177
|
+
issues.push({
|
|
178
|
+
file: relPath,
|
|
179
|
+
line: call.line,
|
|
180
|
+
severity: 'warn',
|
|
181
|
+
rule: 'arg_count_project',
|
|
182
|
+
message: (0, locale_2.t)('arg_count_project', fnName, String(call.count), String(defArgCount)),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return issues;
|
|
188
|
+
}
|
|
189
|
+
function checkModuleCycle(moduleDeps, rootDir, allIssues) {
|
|
190
|
+
const visited = new Set();
|
|
191
|
+
const stack = new Set();
|
|
192
|
+
const cyclePath = [];
|
|
193
|
+
const exportIndex = new Map();
|
|
194
|
+
for (const [f, deps] of moduleDeps) {
|
|
195
|
+
for (const exp of deps.exports) {
|
|
196
|
+
const list = exportIndex.get(exp) || [];
|
|
197
|
+
list.push(f);
|
|
198
|
+
exportIndex.set(exp, list);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const dfs = (current) => {
|
|
202
|
+
if (stack.has(current)) {
|
|
203
|
+
const idx = cyclePath.indexOf(current);
|
|
204
|
+
const cycle = cyclePath.slice(idx).concat(current);
|
|
205
|
+
for (const f of cycle) {
|
|
206
|
+
const override = (0, config_1.findOverrides)(f);
|
|
207
|
+
const checks = override?.checks || {};
|
|
208
|
+
if (checks['module_cycle'] === 'off')
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
const displayCycle = cycle.map(c => path.relative(rootDir, c)).join(' → ');
|
|
212
|
+
allIssues.push({
|
|
213
|
+
file: path.relative(rootDir, current),
|
|
214
|
+
line: 1,
|
|
215
|
+
severity: 'warn',
|
|
216
|
+
rule: 'module_cycle',
|
|
217
|
+
message: `Module dependency cycle detected: ${displayCycle}`,
|
|
218
|
+
});
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
if (visited.has(current))
|
|
222
|
+
return false;
|
|
223
|
+
visited.add(current);
|
|
224
|
+
stack.add(current);
|
|
225
|
+
cyclePath.push(current);
|
|
226
|
+
try {
|
|
227
|
+
const deps = moduleDeps.get(current);
|
|
228
|
+
if (deps) {
|
|
229
|
+
for (const imp of deps.imports) {
|
|
230
|
+
const providerFiles = exportIndex.get(imp);
|
|
231
|
+
if (providerFiles) {
|
|
232
|
+
for (const otherFile of providerFiles) {
|
|
233
|
+
if (dfs(otherFile))
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
cyclePath.pop();
|
|
243
|
+
stack.delete(current);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
for (const f of moduleDeps.keys()) {
|
|
247
|
+
if (!visited.has(f)) {
|
|
248
|
+
dfs(f);
|
|
249
|
+
}
|
|
161
250
|
}
|
|
162
|
-
return deps;
|
|
163
251
|
}
|
|
164
252
|
function lintProject(files, config, rootDir) {
|
|
165
253
|
(0, locale_1.setLocale)(config.locale || 'zh');
|
|
166
254
|
const allIssues = [];
|
|
167
255
|
const symbols = { defuns: new Map(), references: new Map() };
|
|
256
|
+
const maxFiles = config.project_analysis?.maxFiles ?? 500;
|
|
257
|
+
if (files.length > maxFiles) {
|
|
258
|
+
const msg = `${files.length} files exceeds project_analysis.maxFiles (${maxFiles}). Skipping cross-file analysis. To increase, set project_analysis.maxFiles in config.`;
|
|
259
|
+
console.warn(`[atlisp-lint] ${msg}`);
|
|
260
|
+
return [];
|
|
261
|
+
}
|
|
168
262
|
const fileContents = new Map();
|
|
263
|
+
const defunArgs = new Map();
|
|
264
|
+
const callArgCounts = new Map();
|
|
265
|
+
const moduleDeps = new Map();
|
|
266
|
+
// Phase A: Streaming collection — one file at a time, keep only metadata
|
|
169
267
|
for (const filepath of files) {
|
|
268
|
+
let content;
|
|
170
269
|
try {
|
|
171
|
-
|
|
172
|
-
fileContents.set(filepath, content);
|
|
270
|
+
content = fs.readFileSync(filepath, 'utf-8');
|
|
173
271
|
}
|
|
174
272
|
catch {
|
|
175
273
|
continue;
|
|
176
274
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const defunList = collectDefuns(filepath);
|
|
275
|
+
const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
|
|
276
|
+
// Extract all metadata from a single AST parse
|
|
277
|
+
const defunList = collectDefunsFromAst(ast);
|
|
181
278
|
for (const d of defunList) {
|
|
182
279
|
const list = symbols.defuns.get(d.name) || [];
|
|
183
280
|
list.push({ file: filepath, line: d.line });
|
|
184
281
|
symbols.defuns.set(d.name, list);
|
|
185
282
|
}
|
|
186
|
-
const refList =
|
|
283
|
+
const refList = collectReferencesFromAst(ast);
|
|
187
284
|
for (const r of refList) {
|
|
188
285
|
const list = symbols.references.get(r.name) || [];
|
|
189
286
|
list.push({ file: filepath, line: r.line });
|
|
190
287
|
symbols.references.set(r.name, list);
|
|
191
288
|
}
|
|
289
|
+
defunArgs.set(filepath, countFunctionArgsFromAst(ast));
|
|
290
|
+
callArgCounts.set(filepath, countCallArgsFromAst(ast));
|
|
291
|
+
moduleDeps.set(filepath, findModuleDepsFromAst(ast));
|
|
292
|
+
// Cache file content for checks that still need it
|
|
293
|
+
fileContents.set(filepath, content);
|
|
294
|
+
// AST goes out of scope here → GC reclaims it
|
|
192
295
|
}
|
|
193
|
-
//
|
|
194
|
-
const moduleDeps = collectAllModuleDeps(Array.from(fileContents.keys()));
|
|
195
|
-
// New: collect function arg counts per file
|
|
196
|
-
const defunArgs = new Map();
|
|
197
|
-
for (const [filepath] of fileContents) {
|
|
198
|
-
defunArgs.set(filepath, countFunctionArgs(filepath));
|
|
199
|
-
}
|
|
296
|
+
// Phase B: Cross-file checks using only symbol tables
|
|
200
297
|
for (const [filepath] of fileContents) {
|
|
201
298
|
const relPath = path.relative(rootDir, filepath);
|
|
202
|
-
const override =
|
|
299
|
+
const override = (0, config_1.findOverrides)(filepath);
|
|
203
300
|
const checks = override?.checks || config.checks;
|
|
204
|
-
// Existing checks
|
|
205
301
|
if (checks['dangling_defun'] !== 'off') {
|
|
206
|
-
const
|
|
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
|
+
}
|
|
310
|
+
const danglingIssues = (0, dangling_defun_1.checkDanglingDefunFromDefs)(defunList, symbols.references, filepath);
|
|
207
311
|
for (const iss of danglingIssues) {
|
|
208
312
|
iss.file = relPath;
|
|
209
313
|
allIssues.push(iss);
|
|
@@ -220,10 +324,13 @@ function lintProject(files, config, rootDir) {
|
|
|
220
324
|
}
|
|
221
325
|
}
|
|
222
326
|
if (checks['unused_package_dep'] !== 'off') {
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
327
|
+
const content = fileContents.get(filepath);
|
|
328
|
+
if (content) {
|
|
329
|
+
const depIssues = (0, unused_package_dep_1.checkUnusedPackageDepFromContent)(filepath, content);
|
|
330
|
+
for (const iss of depIssues) {
|
|
331
|
+
iss.file = relPath;
|
|
332
|
+
allIssues.push(iss);
|
|
333
|
+
}
|
|
227
334
|
}
|
|
228
335
|
}
|
|
229
336
|
if (checks['duplicate_defun'] !== 'off') {
|
|
@@ -233,108 +340,49 @@ function lintProject(files, config, rootDir) {
|
|
|
233
340
|
allIssues.push(iss);
|
|
234
341
|
}
|
|
235
342
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
allIssues.push({
|
|
247
|
-
file: relPath,
|
|
248
|
-
line: 1,
|
|
249
|
-
severity: 'warn',
|
|
250
|
-
rule: 'module_cycle',
|
|
251
|
-
message: `Module dependency cycle detected: ${displayCycle}`,
|
|
252
|
-
});
|
|
253
|
-
return true;
|
|
254
|
-
}
|
|
255
|
-
if (visited.has(current))
|
|
256
|
-
return false;
|
|
257
|
-
visited.add(current);
|
|
258
|
-
stack.add(current);
|
|
259
|
-
cyclePath.push(current);
|
|
260
|
-
const deps = moduleDeps.get(current);
|
|
261
|
-
if (deps) {
|
|
262
|
-
for (const imp of deps.imports) {
|
|
263
|
-
for (const [otherFile, otherDeps] of moduleDeps) {
|
|
264
|
-
if (otherDeps.exports.includes(imp)) {
|
|
265
|
-
if (dfs(otherFile))
|
|
266
|
-
return true;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
cyclePath.pop();
|
|
272
|
-
stack.delete(current);
|
|
273
|
-
return false;
|
|
274
|
-
};
|
|
275
|
-
for (const f of moduleDeps.keys()) {
|
|
276
|
-
if (!visited.has(f)) {
|
|
277
|
-
dfs(f);
|
|
343
|
+
if (checks['arg_count_project'] !== 'off') {
|
|
344
|
+
const argIssues = checkArgCountProject(filepath, defunArgs, callArgCounts, symbols, relPath);
|
|
345
|
+
allIssues.push(...argIssues);
|
|
346
|
+
}
|
|
347
|
+
if (checks['function_reference'] !== 'off') {
|
|
348
|
+
const definedHere = new Set();
|
|
349
|
+
for (const [name, locations] of symbols.defuns) {
|
|
350
|
+
for (const loc of locations) {
|
|
351
|
+
if (loc.file === filepath)
|
|
352
|
+
definedHere.add(name);
|
|
278
353
|
}
|
|
279
354
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const otherFileArgs = defunArgs.get(def.file);
|
|
297
|
-
if (otherFileArgs && otherFileArgs.get(fnName) !== defArgCount) {
|
|
298
|
-
allMatch = false;
|
|
299
|
-
break;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
if (!allMatch)
|
|
303
|
-
continue;
|
|
304
|
-
for (const call of calls) {
|
|
305
|
-
if (call.count !== defArgCount) {
|
|
306
|
-
allIssues.push({
|
|
307
|
-
file: relPath,
|
|
308
|
-
line: call.line,
|
|
309
|
-
severity: 'warn',
|
|
310
|
-
rule: 'arg_count_project',
|
|
311
|
-
message: `Function '${fnName}' called with ${call.count} arguments but defined with ${defArgCount}`,
|
|
312
|
-
});
|
|
313
|
-
}
|
|
355
|
+
for (const [name, refs] of symbols.references) {
|
|
356
|
+
if (builtin_arg_count_1.BUILTIN_FUNCTIONS.has(name))
|
|
357
|
+
continue;
|
|
358
|
+
if (definedHere.has(name))
|
|
359
|
+
continue;
|
|
360
|
+
if (symbols.defuns.has(name))
|
|
361
|
+
continue;
|
|
362
|
+
for (const ref of refs) {
|
|
363
|
+
if (ref.file === filepath) {
|
|
364
|
+
allIssues.push({
|
|
365
|
+
file: relPath,
|
|
366
|
+
line: ref.line,
|
|
367
|
+
severity: 'warn',
|
|
368
|
+
rule: 'function_reference',
|
|
369
|
+
message: (0, locale_2.t)('function_reference', name),
|
|
370
|
+
});
|
|
314
371
|
}
|
|
315
372
|
}
|
|
316
373
|
}
|
|
317
374
|
}
|
|
318
375
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
return JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
|
328
|
-
}
|
|
329
|
-
catch {
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
376
|
+
// Module cycle check: run DFS once (not per file)
|
|
377
|
+
const cycleCheckFile = fileContents.keys().next().value;
|
|
378
|
+
if (cycleCheckFile) {
|
|
379
|
+
const override = (0, config_1.findOverrides)(cycleCheckFile);
|
|
380
|
+
const checks = override?.checks || config.checks;
|
|
381
|
+
if (checks['module_cycle'] !== 'off') {
|
|
382
|
+
checkModuleCycle(moduleDeps, rootDir, allIssues);
|
|
332
383
|
}
|
|
333
|
-
const parent = path.dirname(dir);
|
|
334
|
-
if (parent === dir)
|
|
335
|
-
return null;
|
|
336
|
-
dir = parent;
|
|
337
384
|
}
|
|
385
|
+
return allIssues;
|
|
338
386
|
}
|
|
339
387
|
function findPackageDir(filepath) {
|
|
340
388
|
let dir = path.dirname(filepath);
|
package/dist/rules.d.ts
CHANGED
|
@@ -7,4 +7,5 @@ export interface RuleDoc {
|
|
|
7
7
|
export declare const RULES: RuleDoc[];
|
|
8
8
|
export declare function generateRulesMarkdown(): string;
|
|
9
9
|
export declare function generateSchemaHint(): string;
|
|
10
|
+
export declare function generateRuleDetail(ruleName: string): string;
|
|
10
11
|
//# sourceMappingURL=rules.d.ts.map
|
package/dist/rules.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RULES = void 0;
|
|
4
4
|
exports.generateRulesMarkdown = generateRulesMarkdown;
|
|
5
5
|
exports.generateSchemaHint = generateSchemaHint;
|
|
6
|
+
exports.generateRuleDetail = generateRuleDetail;
|
|
6
7
|
exports.RULES = [
|
|
7
8
|
{
|
|
8
9
|
name: 'append_single',
|
|
@@ -281,6 +282,78 @@ exports.RULES = [
|
|
|
281
282
|
description: '检测 defun 函数名缺少包名前缀',
|
|
282
283
|
category: '风格',
|
|
283
284
|
},
|
|
285
|
+
{
|
|
286
|
+
name: 'command_s',
|
|
287
|
+
defaultSeverity: 'warn',
|
|
288
|
+
description: '检测 (command ...) 调用,推荐 (command-s ...)',
|
|
289
|
+
category: '最佳实践',
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: 'zerop_usage',
|
|
293
|
+
defaultSeverity: 'info',
|
|
294
|
+
description: '检测 (= x 0),推荐 (zerop x)',
|
|
295
|
+
category: '风格',
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: 'progn_in_body',
|
|
299
|
+
defaultSeverity: 'info',
|
|
300
|
+
description: '检测 while/repeat/foreach 体中冗余 progn',
|
|
301
|
+
category: '风格',
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: 'if_progn_both',
|
|
305
|
+
defaultSeverity: 'info',
|
|
306
|
+
description: '检测 if 两个分支都使用 progn,推荐 cond',
|
|
307
|
+
category: '风格',
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: 'redundant_cond',
|
|
311
|
+
defaultSeverity: 'info',
|
|
312
|
+
description: '检测单子句 cond,推荐 if',
|
|
313
|
+
category: '风格',
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
name: 'selection_set_leak',
|
|
317
|
+
defaultSeverity: 'warn',
|
|
318
|
+
description: '检测 ssget/ssset 次数不匹配',
|
|
319
|
+
category: '正确性',
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: 'empty_branch_cond',
|
|
323
|
+
defaultSeverity: 'warn',
|
|
324
|
+
description: '检测 cond 子句没有表达式',
|
|
325
|
+
category: '正确性',
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
name: 'function_reference',
|
|
329
|
+
defaultSeverity: 'warn',
|
|
330
|
+
description: '检测函数被引用但从未定义(跨文件)',
|
|
331
|
+
category: '正确性',
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
name: 'progn_in_cond',
|
|
335
|
+
defaultSeverity: 'info',
|
|
336
|
+
description: '检测 cond 子句中冗余 progn',
|
|
337
|
+
category: '风格',
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: 'redundant_car_cdr',
|
|
341
|
+
defaultSeverity: 'info',
|
|
342
|
+
description: '检测 (car (car x)) 等可简化为 (caar x)',
|
|
343
|
+
category: '风格',
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: 'equal_nil',
|
|
347
|
+
defaultSeverity: 'info',
|
|
348
|
+
description: '检测 (equal x nil) 建议使用 (null x)',
|
|
349
|
+
category: '风格',
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: 'redundant_and_or',
|
|
353
|
+
defaultSeverity: 'info',
|
|
354
|
+
description: '检测无参数的 (and) 或 (or)——可能是残留代码',
|
|
355
|
+
category: '风格',
|
|
356
|
+
},
|
|
284
357
|
];
|
|
285
358
|
function generateRulesMarkdown() {
|
|
286
359
|
const lines = [
|
|
@@ -300,4 +373,16 @@ function generateSchemaHint() {
|
|
|
300
373
|
|
|
301
374
|
Total rules: ${exports.RULES.length}`;
|
|
302
375
|
}
|
|
376
|
+
function generateRuleDetail(ruleName) {
|
|
377
|
+
const rule = exports.RULES.find(r => r.name === ruleName);
|
|
378
|
+
if (!rule)
|
|
379
|
+
return `Unknown rule: ${ruleName}. Use --list-rules to see all rules.`;
|
|
380
|
+
const lines = [
|
|
381
|
+
`Rule: ${rule.name}`,
|
|
382
|
+
`Severity: ${rule.defaultSeverity}`,
|
|
383
|
+
`Category: ${rule.category}`,
|
|
384
|
+
`Description: ${rule.description}`,
|
|
385
|
+
];
|
|
386
|
+
return lines.join('\n');
|
|
387
|
+
}
|
|
303
388
|
//# sourceMappingURL=rules.js.map
|
package/dist/runner.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { Issue, LintConfig } from './types';
|
|
2
2
|
export declare function runChecks(content: string, file: string, config: LintConfig): Issue[];
|
|
3
|
-
export declare function lintFiles(files: string[], config: LintConfig, rootDir: string): Issue[];
|
|
3
|
+
export declare function lintFiles(files: string[], config: LintConfig, rootDir: string, contentCache?: Map<string, string>): Issue[];
|
|
4
4
|
export declare function lintFilesParallel(files: string[], config: LintConfig, rootDir: string): Promise<Issue[]>;
|
|
5
|
-
export
|
|
6
|
-
export declare function applyFixes(issues: Issue[], content: string, filepath: string): string;
|
|
5
|
+
export { applyFixesFromProviders as applyFixes, FIXABLE_RULES } from './fixes/index';
|
|
7
6
|
export declare function parseIgnoreFile(rootDir: string): string[];
|
|
8
7
|
export declare function fixFile(filepath: string): string[];
|
|
9
8
|
//# sourceMappingURL=runner.d.ts.map
|