@atlisp/lint 0.2.12 → 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 +27 -4
- 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/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/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 +28 -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 +64 -34
- 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 +1 -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/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
|
package/dist/runner.js
CHANGED
|
@@ -33,11 +33,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.FIXABLE_RULES = void 0;
|
|
36
|
+
exports.FIXABLE_RULES = exports.applyFixes = void 0;
|
|
37
37
|
exports.runChecks = runChecks;
|
|
38
38
|
exports.lintFiles = lintFiles;
|
|
39
39
|
exports.lintFilesParallel = lintFilesParallel;
|
|
40
|
-
exports.applyFixes = applyFixes;
|
|
41
40
|
exports.parseIgnoreFile = parseIgnoreFile;
|
|
42
41
|
exports.fixFile = fixFile;
|
|
43
42
|
const fs = __importStar(require("fs"));
|
|
@@ -110,8 +109,8 @@ function runChecks(content, file, config) {
|
|
|
110
109
|
}
|
|
111
110
|
}
|
|
112
111
|
}
|
|
113
|
-
catch {
|
|
114
|
-
|
|
112
|
+
catch (e) {
|
|
113
|
+
console.warn(`[lint] check "${rule}" failed for ${file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
115
114
|
}
|
|
116
115
|
}
|
|
117
116
|
function addIssues(ruleIssues) {
|
|
@@ -198,6 +197,17 @@ function runChecks(content, file, config) {
|
|
|
198
197
|
'identical_branches',
|
|
199
198
|
'while_constant',
|
|
200
199
|
'cond_duplicate',
|
|
200
|
+
'command_s',
|
|
201
|
+
'zerop_usage',
|
|
202
|
+
'progn_in_body',
|
|
203
|
+
'if_progn_both',
|
|
204
|
+
'redundant_cond',
|
|
205
|
+
'selection_set_leak',
|
|
206
|
+
'empty_branch_cond',
|
|
207
|
+
'progn_in_cond',
|
|
208
|
+
'redundant_car_cdr',
|
|
209
|
+
'equal_nil',
|
|
210
|
+
'redundant_and_or',
|
|
201
211
|
]);
|
|
202
212
|
const needsAst = Array.from(astRules).some(r => checks[r] !== 'off');
|
|
203
213
|
if (needsAst) {
|
|
@@ -206,35 +216,49 @@ function runChecks(content, file, config) {
|
|
|
206
216
|
const visitorIssues = (0, visitor_runner_1.runChecksWithVisitor)(ast, file, config, disableMap);
|
|
207
217
|
addIssues(visitorIssues);
|
|
208
218
|
}
|
|
209
|
-
catch {
|
|
210
|
-
|
|
219
|
+
catch (e) {
|
|
220
|
+
console.warn(`[lint] AST parse/visitor failed for ${file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
211
221
|
}
|
|
212
222
|
}
|
|
213
223
|
return issues;
|
|
214
224
|
}
|
|
215
|
-
function lintFiles(files, config, rootDir) {
|
|
225
|
+
function lintFiles(files, config, rootDir, contentCache) {
|
|
216
226
|
(0, locale_1.setLocale)(config.locale || 'zh');
|
|
217
227
|
const allIssues = [];
|
|
218
|
-
|
|
228
|
+
const total = files.length;
|
|
229
|
+
const showProgress = process.stderr.isTTY && total > 5;
|
|
230
|
+
for (let fi = 0; fi < total; fi++) {
|
|
231
|
+
const filepath = files[fi];
|
|
219
232
|
const relPath = path.relative(rootDir, filepath);
|
|
233
|
+
if (showProgress) {
|
|
234
|
+
process.stderr.write(`\r[${fi + 1}/${total}] ${relPath} `);
|
|
235
|
+
}
|
|
220
236
|
let content;
|
|
221
|
-
|
|
222
|
-
content =
|
|
237
|
+
if (contentCache && contentCache.has(filepath)) {
|
|
238
|
+
content = contentCache.get(filepath);
|
|
223
239
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
240
|
+
else {
|
|
241
|
+
try {
|
|
242
|
+
content = fs.readFileSync(filepath, 'utf-8');
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
allIssues.push({
|
|
246
|
+
file: relPath,
|
|
247
|
+
line: 1,
|
|
248
|
+
severity: 'error',
|
|
249
|
+
rule: 'read',
|
|
250
|
+
message: (0, locale_1.t)('runner.read_error'),
|
|
251
|
+
});
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
233
254
|
}
|
|
234
255
|
const override = (0, config_1.findOverrides)(filepath);
|
|
235
256
|
const effectiveConfig = override ? (0, config_1.mergeOverrides)(config, override) : config;
|
|
236
257
|
allIssues.push(...runChecks(content, relPath, effectiveConfig));
|
|
237
258
|
}
|
|
259
|
+
if (showProgress) {
|
|
260
|
+
process.stderr.write('\r' + ' '.repeat(60) + '\r');
|
|
261
|
+
}
|
|
238
262
|
return allIssues;
|
|
239
263
|
}
|
|
240
264
|
function lintFilesParallel(files, config, rootDir) {
|
|
@@ -302,358 +326,9 @@ function lintFilesParallel(files, config, rootDir) {
|
|
|
302
326
|
resolve([]);
|
|
303
327
|
});
|
|
304
328
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
let depth = 0;
|
|
309
|
-
for (let i = start; i < s.length; i++) {
|
|
310
|
-
if (s[i] === '"') {
|
|
311
|
-
i++;
|
|
312
|
-
while (i < s.length && s[i] !== '"') {
|
|
313
|
-
if (s[i] === '\\')
|
|
314
|
-
i++;
|
|
315
|
-
i++;
|
|
316
|
-
}
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
if (s[i] === '(')
|
|
320
|
-
depth++;
|
|
321
|
-
else if (s[i] === ')') {
|
|
322
|
-
depth--;
|
|
323
|
-
if (depth === 0)
|
|
324
|
-
return i;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return s.length - 1;
|
|
328
|
-
}
|
|
329
|
-
function extractForm(s, start) {
|
|
330
|
-
let i = start;
|
|
331
|
-
while (i < s.length && s[i] === ' ')
|
|
332
|
-
i++;
|
|
333
|
-
if (i >= s.length)
|
|
334
|
-
return ['', i];
|
|
335
|
-
if (s[i] !== '(') {
|
|
336
|
-
const m = s.slice(i).match(/^\S+/);
|
|
337
|
-
if (m)
|
|
338
|
-
return [m[0], i + m[0].length];
|
|
339
|
-
return ['', i];
|
|
340
|
-
}
|
|
341
|
-
const end = findMatchingParen(s, i);
|
|
342
|
-
return [s.slice(i, end + 1), end + 1];
|
|
343
|
-
}
|
|
344
|
-
// ===== FIX APPLICATION =====
|
|
345
|
-
exports.FIXABLE_RULES = new Set([
|
|
346
|
-
'redundant_quotes',
|
|
347
|
-
'double_not',
|
|
348
|
-
'redundant_nil_else',
|
|
349
|
-
'single_arg_and_or',
|
|
350
|
-
'redundant_setq',
|
|
351
|
-
'redundant_progn',
|
|
352
|
-
'redundant_let',
|
|
353
|
-
'quote_style',
|
|
354
|
-
'redundant_if',
|
|
355
|
-
'misplaced_else',
|
|
356
|
-
'setq_multiple',
|
|
357
|
-
'append_single',
|
|
358
|
-
'nth_usage',
|
|
359
|
-
'setq_single_arg',
|
|
360
|
-
'extra_parens',
|
|
361
|
-
'empty_branch',
|
|
362
|
-
'comment_style',
|
|
363
|
-
'eq_usage',
|
|
364
|
-
'cond_simplify',
|
|
365
|
-
'self_compare',
|
|
366
|
-
'while_constant',
|
|
367
|
-
'redundant_list',
|
|
368
|
-
]);
|
|
369
|
-
function applyFixes(issues, content, filepath) {
|
|
370
|
-
const lines = content.split('\n');
|
|
371
|
-
const fixedLines = new Set();
|
|
372
|
-
const fixesByRule = new Map();
|
|
373
|
-
for (const iss of issues) {
|
|
374
|
-
if (iss.file !== filepath)
|
|
375
|
-
continue;
|
|
376
|
-
const set = fixesByRule.get(iss.rule) || new Set();
|
|
377
|
-
set.add(iss.line);
|
|
378
|
-
fixesByRule.set(iss.rule, set);
|
|
379
|
-
}
|
|
380
|
-
const fixableRules = exports.FIXABLE_RULES;
|
|
381
|
-
for (const [rule, lineSet] of fixesByRule) {
|
|
382
|
-
if (!fixableRules.has(rule))
|
|
383
|
-
continue;
|
|
384
|
-
const sortedLines = Array.from(lineSet).sort((a, b) => b - a);
|
|
385
|
-
for (const line of sortedLines) {
|
|
386
|
-
const idx = line - 1;
|
|
387
|
-
if (idx < 0 || idx >= lines.length)
|
|
388
|
-
continue;
|
|
389
|
-
const lineContent = lines[idx];
|
|
390
|
-
switch (rule) {
|
|
391
|
-
case 'redundant_quotes': {
|
|
392
|
-
const fixed = lineContent.replace(/''(\S)/g, "'$1");
|
|
393
|
-
if (fixed !== lineContent) {
|
|
394
|
-
lines[idx] = fixed;
|
|
395
|
-
fixedLines.add(idx);
|
|
396
|
-
}
|
|
397
|
-
break;
|
|
398
|
-
}
|
|
399
|
-
case 'double_not': {
|
|
400
|
-
const fixed = lineContent.replace(/\(not\s+\(not\s+/g, '(not ');
|
|
401
|
-
if (fixed !== lineContent) {
|
|
402
|
-
lines[idx] = fixed;
|
|
403
|
-
fixedLines.add(idx);
|
|
404
|
-
}
|
|
405
|
-
break;
|
|
406
|
-
}
|
|
407
|
-
case 'redundant_nil_else': {
|
|
408
|
-
const fixed = lineContent.replace(/\s+nil\s*\)$/, ')');
|
|
409
|
-
if (fixed !== lineContent) {
|
|
410
|
-
lines[idx] = fixed;
|
|
411
|
-
fixedLines.add(idx);
|
|
412
|
-
}
|
|
413
|
-
break;
|
|
414
|
-
}
|
|
415
|
-
case 'single_arg_and_or': {
|
|
416
|
-
const m = lineContent.match(/\((and|or)\s+([^)\s]+)\s*\)/);
|
|
417
|
-
if (m) {
|
|
418
|
-
lines[idx] = m[2];
|
|
419
|
-
fixedLines.add(idx);
|
|
420
|
-
}
|
|
421
|
-
break;
|
|
422
|
-
}
|
|
423
|
-
case 'redundant_progn': {
|
|
424
|
-
const proIdx = lineContent.indexOf('(progn ');
|
|
425
|
-
if (proIdx !== -1) {
|
|
426
|
-
const beforePro = lineContent.slice(0, proIdx);
|
|
427
|
-
const close = findMatchingParen(lineContent, proIdx);
|
|
428
|
-
if (close > proIdx) {
|
|
429
|
-
const inner = lineContent.slice(proIdx + 7, close);
|
|
430
|
-
const rest = lineContent.slice(close + 1);
|
|
431
|
-
const fixed = beforePro + inner + rest;
|
|
432
|
-
if (fixed !== lineContent) {
|
|
433
|
-
lines[idx] = fixed;
|
|
434
|
-
fixedLines.add(idx);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
break;
|
|
439
|
-
}
|
|
440
|
-
case 'redundant_let': {
|
|
441
|
-
const fixed = lineContent.replace(/\(let\s+\(\)\s*/, '(progn ');
|
|
442
|
-
if (fixed !== lineContent) {
|
|
443
|
-
lines[idx] = fixed;
|
|
444
|
-
fixedLines.add(idx);
|
|
445
|
-
}
|
|
446
|
-
break;
|
|
447
|
-
}
|
|
448
|
-
case 'quote_style': {
|
|
449
|
-
const qIdx = lineContent.indexOf('(quote ');
|
|
450
|
-
if (qIdx !== -1) {
|
|
451
|
-
const beforeQ = lineContent.slice(0, qIdx);
|
|
452
|
-
const close = findMatchingParen(lineContent, qIdx);
|
|
453
|
-
if (close > qIdx) {
|
|
454
|
-
const quoted = lineContent.slice(qIdx + 7, close);
|
|
455
|
-
const rest = lineContent.slice(close + 1);
|
|
456
|
-
const fixed = beforeQ + "'" + quoted + rest;
|
|
457
|
-
if (fixed !== lineContent) {
|
|
458
|
-
lines[idx] = fixed;
|
|
459
|
-
fixedLines.add(idx);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
break;
|
|
464
|
-
}
|
|
465
|
-
case 'redundant_if': {
|
|
466
|
-
const pIdx = lineContent.indexOf('(progn ');
|
|
467
|
-
if (pIdx !== -1) {
|
|
468
|
-
const beforeP = lineContent.slice(0, pIdx);
|
|
469
|
-
const close = findMatchingParen(lineContent, pIdx);
|
|
470
|
-
if (close > pIdx) {
|
|
471
|
-
const inner = lineContent.slice(pIdx + 7, close);
|
|
472
|
-
const rest = lineContent.slice(close + 1);
|
|
473
|
-
const fixed = beforeP + inner + rest;
|
|
474
|
-
if (fixed !== lineContent) {
|
|
475
|
-
lines[idx] = fixed;
|
|
476
|
-
fixedLines.add(idx);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
break;
|
|
481
|
-
}
|
|
482
|
-
case 'misplaced_else': {
|
|
483
|
-
const ifStart = lineContent.indexOf('(if (not ');
|
|
484
|
-
if (ifStart !== -1) {
|
|
485
|
-
const notIdx = lineContent.indexOf('(not ', ifStart);
|
|
486
|
-
if (notIdx === -1)
|
|
487
|
-
break;
|
|
488
|
-
const notClose = findMatchingParen(lineContent, notIdx);
|
|
489
|
-
if (notClose <= notIdx)
|
|
490
|
-
break;
|
|
491
|
-
const ifClose = findMatchingParen(lineContent, ifStart);
|
|
492
|
-
if (ifClose <= ifStart)
|
|
493
|
-
break;
|
|
494
|
-
const indent = lineContent.slice(0, ifStart);
|
|
495
|
-
const condVar = lineContent.slice(notIdx + 5, notClose);
|
|
496
|
-
const branchContent = lineContent.slice(notClose + 1, ifClose).trim();
|
|
497
|
-
const [thenForm, thenEnd] = extractForm(branchContent, 0);
|
|
498
|
-
const elseForm = branchContent.slice(thenEnd).trim();
|
|
499
|
-
if (thenForm && elseForm) {
|
|
500
|
-
const rest = lineContent.slice(ifClose + 1);
|
|
501
|
-
const fixed = indent + '(if ' + condVar + ' ' + elseForm + ' ' + thenForm + ')' + rest;
|
|
502
|
-
if (fixed !== lineContent) {
|
|
503
|
-
lines[idx] = fixed;
|
|
504
|
-
fixedLines.add(idx);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
break;
|
|
509
|
-
}
|
|
510
|
-
case 'redundant_setq': {
|
|
511
|
-
const trimmed = lines[idx].trim();
|
|
512
|
-
if (/^\(setq\s+(\S+)\s+\1\s*\)$/.test(trimmed)) {
|
|
513
|
-
lines.splice(idx, 1);
|
|
514
|
-
}
|
|
515
|
-
break;
|
|
516
|
-
}
|
|
517
|
-
case 'setq_multiple': {
|
|
518
|
-
const fixed = lineContent.replace(/\)\(setq\s+/g, ' ');
|
|
519
|
-
if (fixed !== lineContent) {
|
|
520
|
-
const merged = lineContent.replace(/^\(\s*setq\s+/, '(setq ');
|
|
521
|
-
if (merged.includes('setq') && !merged.match(/\)\(/)) {
|
|
522
|
-
lines[idx] = merged;
|
|
523
|
-
fixedLines.add(idx);
|
|
524
|
-
}
|
|
525
|
-
else {
|
|
526
|
-
const simplified = lineContent.replace(/\)\(setq\s+/g, '\n');
|
|
527
|
-
if (simplified !== lineContent) {
|
|
528
|
-
lines[idx] = simplified;
|
|
529
|
-
fixedLines.add(idx);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
break;
|
|
534
|
-
}
|
|
535
|
-
case 'append_single': {
|
|
536
|
-
const aIdx = lineContent.indexOf('(append ');
|
|
537
|
-
if (aIdx !== -1) {
|
|
538
|
-
const close = findMatchingParen(lineContent, aIdx);
|
|
539
|
-
if (close > aIdx) {
|
|
540
|
-
const inner = lineContent.slice(aIdx + 8, close);
|
|
541
|
-
const listMatch = inner.match(/^\(list\s+(.*)\)\s*$/);
|
|
542
|
-
if (listMatch) {
|
|
543
|
-
const fixed = lineContent.slice(0, aIdx) + '(cons ' + listMatch[1] + ')' + lineContent.slice(close + 1);
|
|
544
|
-
if (fixed !== lineContent) {
|
|
545
|
-
lines[idx] = fixed;
|
|
546
|
-
fixedLines.add(idx);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
break;
|
|
552
|
-
}
|
|
553
|
-
case 'nth_usage': {
|
|
554
|
-
const fixed = lineContent.replace(/\(nth\s+0\s+/g, '(car ');
|
|
555
|
-
if (fixed !== lineContent) {
|
|
556
|
-
lines[idx] = fixed;
|
|
557
|
-
fixedLines.add(idx);
|
|
558
|
-
}
|
|
559
|
-
break;
|
|
560
|
-
}
|
|
561
|
-
case 'setq_single_arg': {
|
|
562
|
-
if (/^\s*\(setq\s+\S+\s*\)\s*$/.test(lines[idx])) {
|
|
563
|
-
lines.splice(idx, 1);
|
|
564
|
-
}
|
|
565
|
-
break;
|
|
566
|
-
}
|
|
567
|
-
case 'extra_parens': {
|
|
568
|
-
const fixed = lineContent.replace(/\){2,}/g, ')');
|
|
569
|
-
if (fixed !== lineContent) {
|
|
570
|
-
lines[idx] = fixed;
|
|
571
|
-
fixedLines.add(idx);
|
|
572
|
-
}
|
|
573
|
-
break;
|
|
574
|
-
}
|
|
575
|
-
case 'empty_branch': {
|
|
576
|
-
const fixed = lineContent.replace(/\(if\s+\S+\s*\)/g, m => m.slice(0, -1) + ' nil)');
|
|
577
|
-
if (fixed !== lineContent) {
|
|
578
|
-
lines[idx] = fixed;
|
|
579
|
-
fixedLines.add(idx);
|
|
580
|
-
}
|
|
581
|
-
break;
|
|
582
|
-
}
|
|
583
|
-
case 'comment_style': {
|
|
584
|
-
const fixed = lineContent.replace(/^(\s*;)([^ ;|].*)$/gm, '$1 $2');
|
|
585
|
-
if (fixed !== lineContent) {
|
|
586
|
-
lines[idx] = fixed;
|
|
587
|
-
fixedLines.add(idx);
|
|
588
|
-
}
|
|
589
|
-
break;
|
|
590
|
-
}
|
|
591
|
-
case 'eq_usage': {
|
|
592
|
-
const fixed = lineContent.replace(/\(eq\s+(\S+)\s+(\d+)\s*\)/g, '(= $1 $2)');
|
|
593
|
-
if (fixed !== lineContent) {
|
|
594
|
-
lines[idx] = fixed;
|
|
595
|
-
fixedLines.add(idx);
|
|
596
|
-
}
|
|
597
|
-
break;
|
|
598
|
-
}
|
|
599
|
-
case 'cond_simplify': {
|
|
600
|
-
const condIdx = lineContent.indexOf('(cond ');
|
|
601
|
-
if (condIdx !== -1) {
|
|
602
|
-
const condEnd = findMatchingParen(lineContent, condIdx);
|
|
603
|
-
if (condEnd > condIdx) {
|
|
604
|
-
const inner = lineContent.slice(condIdx + 6, condEnd).trim();
|
|
605
|
-
const branchOpen = inner.indexOf('(');
|
|
606
|
-
if (branchOpen !== -1) {
|
|
607
|
-
const branchEnd = findMatchingParen(inner, branchOpen);
|
|
608
|
-
if (branchEnd > branchOpen) {
|
|
609
|
-
const indent = lineContent.slice(0, condIdx);
|
|
610
|
-
const branchContent = inner.slice(branchOpen + 1, branchEnd).trim();
|
|
611
|
-
const space = branchContent.search(/\s+/);
|
|
612
|
-
if (space !== -1) {
|
|
613
|
-
const testExpr = branchContent.slice(0, space);
|
|
614
|
-
const bodyExpr = branchContent.slice(space).trim();
|
|
615
|
-
const rest = lineContent.slice(condEnd + 1);
|
|
616
|
-
const fixed = indent + '(if ' + testExpr + ' ' + bodyExpr + ')' + rest;
|
|
617
|
-
if (fixed !== lineContent) {
|
|
618
|
-
lines[idx] = fixed;
|
|
619
|
-
fixedLines.add(idx);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
break;
|
|
627
|
-
}
|
|
628
|
-
case 'self_compare': {
|
|
629
|
-
const fixed = lineContent.replace(/\((?:[=/<>]+|eq|equal)\s+(\S+)\s+\1\s*\)/g, 'T');
|
|
630
|
-
if (fixed !== lineContent) {
|
|
631
|
-
lines[idx] = fixed;
|
|
632
|
-
fixedLines.add(idx);
|
|
633
|
-
}
|
|
634
|
-
break;
|
|
635
|
-
}
|
|
636
|
-
case 'while_constant': {
|
|
637
|
-
const trimmed = lines[idx].trim();
|
|
638
|
-
const nilMatch = trimmed.match(/^\(\s*while\s+nil\s+(.*)\)\s*$/);
|
|
639
|
-
if (nilMatch) {
|
|
640
|
-
lines[idx] = lines[idx].replace(trimmed, 'nil');
|
|
641
|
-
}
|
|
642
|
-
break;
|
|
643
|
-
}
|
|
644
|
-
case 'redundant_list': {
|
|
645
|
-
const fixed = lineContent.replace(/\(\s*list\s*\)/g, 'nil');
|
|
646
|
-
if (fixed !== lineContent) {
|
|
647
|
-
lines[idx] = fixed;
|
|
648
|
-
fixedLines.add(idx);
|
|
649
|
-
}
|
|
650
|
-
break;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
return lines.join('\n');
|
|
656
|
-
}
|
|
329
|
+
var index_1 = require("./fixes/index");
|
|
330
|
+
Object.defineProperty(exports, "applyFixes", { enumerable: true, get: function () { return index_1.applyFixesFromProviders; } });
|
|
331
|
+
Object.defineProperty(exports, "FIXABLE_RULES", { enumerable: true, get: function () { return index_1.FIXABLE_RULES; } });
|
|
657
332
|
function parseIgnoreFile(rootDir) {
|
|
658
333
|
const ignoreFile = path.join(rootDir, '.atlisp-lint-ignore');
|
|
659
334
|
if (!fs.existsSync(ignoreFile))
|
package/dist/types.d.ts
CHANGED
package/dist/utils.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export declare function codeLines(content: string): Generator<{
|
|
|
7
7
|
text: string;
|
|
8
8
|
stripped: string;
|
|
9
9
|
}>;
|
|
10
|
+
/** Find matching closing paren (string/comment aware) */
|
|
11
|
+
export declare function findMatchingParen(s: string, start: number): number;
|
|
12
|
+
/** Extract the next s-expression form starting at position */
|
|
13
|
+
export declare function extractForm(s: string, start: number): [string, number];
|
|
10
14
|
/** Extract string literals from a line */
|
|
11
15
|
export declare function extractStrings(line: string): string[];
|
|
12
16
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.js
CHANGED
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.stripLine = stripLine;
|
|
4
4
|
exports.stripLineRaw = stripLineRaw;
|
|
5
5
|
exports.codeLines = codeLines;
|
|
6
|
+
exports.findMatchingParen = findMatchingParen;
|
|
7
|
+
exports.extractForm = extractForm;
|
|
6
8
|
exports.extractStrings = extractStrings;
|
|
7
9
|
/** Strip string contents and comments from a line of AutoLISP code */
|
|
8
10
|
function stripLine(line) {
|
|
@@ -67,6 +69,47 @@ function* codeLines(content) {
|
|
|
67
69
|
};
|
|
68
70
|
}
|
|
69
71
|
}
|
|
72
|
+
/** Find matching closing paren (string/comment aware) */
|
|
73
|
+
function findMatchingParen(s, start) {
|
|
74
|
+
if (s[start] !== '(')
|
|
75
|
+
return start;
|
|
76
|
+
let depth = 0;
|
|
77
|
+
for (let i = start; i < s.length; i++) {
|
|
78
|
+
if (s[i] === '"') {
|
|
79
|
+
i++;
|
|
80
|
+
while (i < s.length && s[i] !== '"') {
|
|
81
|
+
if (s[i] === '\\')
|
|
82
|
+
i++;
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (s[i] === '(')
|
|
88
|
+
depth++;
|
|
89
|
+
else if (s[i] === ')') {
|
|
90
|
+
depth--;
|
|
91
|
+
if (depth === 0)
|
|
92
|
+
return i;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return s.length - 1;
|
|
96
|
+
}
|
|
97
|
+
/** Extract the next s-expression form starting at position */
|
|
98
|
+
function extractForm(s, start) {
|
|
99
|
+
let i = start;
|
|
100
|
+
while (i < s.length && s[i] === ' ')
|
|
101
|
+
i++;
|
|
102
|
+
if (i >= s.length)
|
|
103
|
+
return ['', i];
|
|
104
|
+
if (s[i] !== '(') {
|
|
105
|
+
const m = s.slice(i).match(/^\S+/);
|
|
106
|
+
if (m)
|
|
107
|
+
return [m[0], i + m[0].length];
|
|
108
|
+
return ['', i];
|
|
109
|
+
}
|
|
110
|
+
const end = findMatchingParen(s, i);
|
|
111
|
+
return [s.slice(i, end + 1), end + 1];
|
|
112
|
+
}
|
|
70
113
|
/** Extract string literals from a line */
|
|
71
114
|
function extractStrings(line) {
|
|
72
115
|
const strings = [];
|
package/dist/validate.js
CHANGED
|
@@ -89,6 +89,18 @@ const VALID_RULES = [
|
|
|
89
89
|
'redundant_entmake',
|
|
90
90
|
'missing_princ',
|
|
91
91
|
'global_function_naming',
|
|
92
|
+
'command_s',
|
|
93
|
+
'zerop_usage',
|
|
94
|
+
'progn_in_body',
|
|
95
|
+
'if_progn_both',
|
|
96
|
+
'redundant_cond',
|
|
97
|
+
'selection_set_leak',
|
|
98
|
+
'empty_branch_cond',
|
|
99
|
+
'function_reference',
|
|
100
|
+
'progn_in_cond',
|
|
101
|
+
'redundant_car_cdr',
|
|
102
|
+
'equal_nil',
|
|
103
|
+
'redundant_and_or',
|
|
92
104
|
];
|
|
93
105
|
const VALID_SEVERITIES = ['off', 'info', 'warn', 'error'];
|
|
94
106
|
function validateConfig(config) {
|