@atlisp/lint 0.1.5 → 0.1.8
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/LICENSE +21 -0
- package/README.md +219 -58
- package/atlisp-lint.default.json +32 -1
- package/dist/atlisp-lint.default.json +90 -0
- package/dist/cache.d.ts +2 -2
- package/dist/cache.js +6 -6
- package/dist/checks/append-single.d.ts +3 -0
- package/dist/checks/append-single.js +17 -0
- package/dist/checks/arg-count.d.ts +3 -0
- package/dist/checks/arg-count.js +123 -0
- package/dist/checks/assoc-without-cdr.d.ts +5 -0
- package/dist/checks/assoc-without-cdr.js +32 -0
- package/dist/checks/comment-style.d.ts +3 -0
- package/dist/checks/comment-style.js +24 -0
- package/dist/checks/cond-duplicate.d.ts +5 -0
- package/dist/checks/cond-duplicate.js +52 -0
- package/dist/checks/cond-simplify.d.ts +3 -0
- package/dist/checks/cond-simplify.js +45 -0
- package/dist/checks/constant-condition.js +4 -4
- package/dist/checks/dangerous-calls.js +2 -2
- package/dist/checks/dangling-defun.d.ts +3 -0
- package/dist/checks/dangling-defun.js +10 -28
- package/dist/checks/double-not.js +1 -1
- package/dist/checks/duplicate-defun.d.ts +6 -0
- package/dist/checks/duplicate-defun.js +50 -0
- package/dist/checks/dynamic-doc.d.ts +3 -0
- package/dist/checks/dynamic-doc.js +21 -0
- package/dist/checks/empty-branch.js +1 -1
- package/dist/checks/empty-catch.d.ts +3 -0
- package/dist/checks/empty-catch.js +34 -0
- package/dist/checks/eq-usage.d.ts +3 -0
- package/dist/checks/eq-usage.js +25 -0
- package/dist/checks/error-handling.d.ts +3 -0
- package/dist/checks/error-handling.js +56 -0
- package/dist/checks/extra-parens.d.ts +3 -0
- package/dist/checks/extra-parens.js +45 -0
- package/dist/checks/format-indent.d.ts +3 -0
- package/dist/checks/format-indent.js +29 -0
- package/dist/checks/function-complexity.js +1 -1
- package/dist/checks/function-order.d.ts +3 -0
- package/dist/checks/function-order.js +33 -0
- package/dist/checks/global-naming.d.ts +3 -0
- package/dist/checks/global-naming.js +62 -0
- package/dist/checks/identical-branches.d.ts +5 -0
- package/dist/checks/identical-branches.js +54 -0
- package/dist/checks/index.d.ts +3 -0
- package/dist/checks/index.js +117 -0
- package/dist/checks/lambda-syntax.d.ts +3 -0
- package/dist/checks/lambda-syntax.js +25 -0
- package/dist/checks/long-function-call.d.ts +3 -0
- package/dist/checks/long-function-call.js +54 -0
- package/dist/checks/loop-optimization.d.ts +3 -0
- package/dist/checks/loop-optimization.js +17 -0
- package/dist/checks/magic-number.d.ts +3 -0
- package/dist/checks/magic-number.js +21 -0
- package/dist/checks/misplaced-else.js +1 -1
- package/dist/checks/missing-export.js +2 -2
- package/dist/checks/mixed-indent.d.ts +3 -0
- package/dist/checks/mixed-indent.js +19 -0
- package/dist/checks/module-reg.js +1 -1
- package/dist/checks/multiple-setq.js +1 -1
- package/dist/checks/no-return.d.ts +3 -0
- package/dist/checks/no-return.js +51 -0
- package/dist/checks/nth-usage.d.ts +3 -0
- package/dist/checks/nth-usage.js +17 -0
- package/dist/checks/quote-style.d.ts +3 -0
- package/dist/checks/quote-style.js +25 -0
- package/dist/checks/quote-vs-function.js +1 -1
- package/dist/checks/recursive-call.js +1 -1
- package/dist/checks/redundant-if.d.ts +3 -0
- package/dist/checks/redundant-if.js +17 -0
- package/dist/checks/redundant-let.js +1 -1
- package/dist/checks/redundant-nil-else.js +1 -1
- package/dist/checks/redundant-progn.js +1 -1
- package/dist/checks/redundant-quotes.js +1 -1
- package/dist/checks/redundant-setq.js +1 -1
- package/dist/checks/self-compare.js +1 -1
- package/dist/checks/setq-multiple.d.ts +3 -0
- package/dist/checks/setq-multiple.js +17 -0
- package/dist/checks/setq-single-arg.d.ts +5 -0
- package/dist/checks/setq-single-arg.js +30 -0
- package/dist/checks/shadow-builtin.d.ts +3 -0
- package/dist/checks/shadow-builtin.js +77 -0
- package/dist/checks/single-arg-and-or.js +1 -1
- package/dist/checks/strcat-usage.d.ts +3 -0
- package/dist/checks/strcat-usage.js +25 -0
- package/dist/checks/type-check.d.ts +3 -0
- package/dist/checks/type-check.js +26 -0
- package/dist/checks/unused-let.js +1 -1
- package/dist/checks/unused-package-dep.js +3 -3
- package/dist/checks/variable-shadow.js +1 -1
- package/dist/checks/while-constant.d.ts +5 -0
- package/dist/checks/while-constant.js +40 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +71 -2
- package/dist/disable.js +1 -1
- package/dist/formatter.d.ts +2 -0
- package/dist/formatter.js +51 -0
- package/dist/formatters.d.ts +1 -0
- package/dist/formatters.js +18 -2
- package/dist/index.js +172 -32
- package/dist/lib/lint-sbcl.lisp +161 -0
- package/dist/locale.js +76 -0
- package/dist/presets.d.ts +4 -0
- package/dist/presets.js +159 -0
- package/dist/project.js +37 -6
- package/dist/rules.d.ts +9 -0
- package/dist/rules.js +239 -0
- package/dist/runner.d.ts +2 -0
- package/dist/runner.js +329 -12
- package/dist/sbcl.js +1 -1
- package/dist/stub-packages.json +41 -0
- package/dist/types.d.ts +6 -0
- package/dist/validate.d.ts +8 -0
- package/dist/validate.js +126 -0
- package/dist/watch.d.ts +9 -0
- package/dist/watch.js +113 -0
- package/package.json +1 -1
package/dist/runner.js
CHANGED
|
@@ -37,6 +37,8 @@ exports.runChecks = runChecks;
|
|
|
37
37
|
exports.runChecksWithVisitor = runChecksWithVisitor;
|
|
38
38
|
exports.lintFiles = lintFiles;
|
|
39
39
|
exports.lintFilesParallel = lintFilesParallel;
|
|
40
|
+
exports.applyFixes = applyFixes;
|
|
41
|
+
exports.parseIgnoreFile = parseIgnoreFile;
|
|
40
42
|
exports.fixFile = fixFile;
|
|
41
43
|
const fs = __importStar(require("fs"));
|
|
42
44
|
const path = __importStar(require("path"));
|
|
@@ -80,6 +82,36 @@ const misplaced_else_1 = require("./checks/misplaced-else");
|
|
|
80
82
|
const quote_vs_function_1 = require("./checks/quote-vs-function");
|
|
81
83
|
const commented_code_1 = require("./checks/commented-code");
|
|
82
84
|
const double_not_1 = require("./checks/double-not");
|
|
85
|
+
const setq_single_arg_1 = require("./checks/setq-single-arg");
|
|
86
|
+
const assoc_without_cdr_1 = require("./checks/assoc-without-cdr");
|
|
87
|
+
const identical_branches_1 = require("./checks/identical-branches");
|
|
88
|
+
const while_constant_1 = require("./checks/while-constant");
|
|
89
|
+
const cond_duplicate_1 = require("./checks/cond-duplicate");
|
|
90
|
+
const error_handling_1 = require("./checks/error-handling");
|
|
91
|
+
const global_naming_1 = require("./checks/global-naming");
|
|
92
|
+
const extra_parens_1 = require("./checks/extra-parens");
|
|
93
|
+
const arg_count_1 = require("./checks/arg-count");
|
|
94
|
+
const strcat_usage_1 = require("./checks/strcat-usage");
|
|
95
|
+
const cond_simplify_1 = require("./checks/cond-simplify");
|
|
96
|
+
const quote_style_1 = require("./checks/quote-style");
|
|
97
|
+
const eq_usage_1 = require("./checks/eq-usage");
|
|
98
|
+
const lambda_syntax_1 = require("./checks/lambda-syntax");
|
|
99
|
+
const comment_style_1 = require("./checks/comment-style");
|
|
100
|
+
const empty_catch_1 = require("./checks/empty-catch");
|
|
101
|
+
const nth_usage_1 = require("./checks/nth-usage");
|
|
102
|
+
const append_single_1 = require("./checks/append-single");
|
|
103
|
+
const setq_multiple_1 = require("./checks/setq-multiple");
|
|
104
|
+
const function_order_1 = require("./checks/function-order");
|
|
105
|
+
const magic_number_1 = require("./checks/magic-number");
|
|
106
|
+
const mixed_indent_1 = require("./checks/mixed-indent");
|
|
107
|
+
const long_function_call_1 = require("./checks/long-function-call");
|
|
108
|
+
const no_return_1 = require("./checks/no-return");
|
|
109
|
+
const shadow_builtin_1 = require("./checks/shadow-builtin");
|
|
110
|
+
const dynamic_doc_1 = require("./checks/dynamic-doc");
|
|
111
|
+
const loop_optimization_1 = require("./checks/loop-optimization");
|
|
112
|
+
const type_check_1 = require("./checks/type-check");
|
|
113
|
+
const redundant_if_1 = require("./checks/redundant-if");
|
|
114
|
+
const format_indent_1 = require("./checks/format-indent");
|
|
83
115
|
const config_1 = require("./config");
|
|
84
116
|
const locale_1 = require("./locale");
|
|
85
117
|
const disable_1 = require("./disable");
|
|
@@ -87,11 +119,47 @@ const parser_1 = require("@atlisp/parser");
|
|
|
87
119
|
function runChecks(content, file, config) {
|
|
88
120
|
const issues = [];
|
|
89
121
|
const disableMap = (0, disable_1.parseDisableComments)(content);
|
|
90
|
-
//
|
|
91
|
-
const
|
|
122
|
+
// Deferred AST parsing: only parse if any AST-based checks are enabled
|
|
123
|
+
const astRules = new Set([
|
|
124
|
+
'quit_exit',
|
|
125
|
+
'open_without_close',
|
|
126
|
+
'function_complexity',
|
|
127
|
+
'parameter_naming',
|
|
128
|
+
'unused_variable',
|
|
129
|
+
'unused_parameter',
|
|
130
|
+
'constant_condition',
|
|
131
|
+
'redundant_progn',
|
|
132
|
+
'empty_branch',
|
|
133
|
+
'unused_let_binding',
|
|
134
|
+
'recursive_call',
|
|
135
|
+
'variable_shadow',
|
|
136
|
+
'redundant_cond',
|
|
137
|
+
'unused_local_fun',
|
|
138
|
+
'multiple_setq',
|
|
139
|
+
'redundant_quotes',
|
|
140
|
+
'redundant_setq',
|
|
141
|
+
'redundant_nil_else',
|
|
142
|
+
'single_arg_and_or',
|
|
143
|
+
'redundant_let',
|
|
144
|
+
'self_compare',
|
|
145
|
+
'misplaced_else',
|
|
146
|
+
'quote_vs_function',
|
|
147
|
+
'double_not',
|
|
148
|
+
'setq_single_arg',
|
|
149
|
+
'assoc_without_cdr',
|
|
150
|
+
'identical_branches',
|
|
151
|
+
'while_constant',
|
|
152
|
+
'cond_duplicate',
|
|
153
|
+
]);
|
|
154
|
+
const checks = config.checks;
|
|
155
|
+
const needsAst = Array.from(astRules).some(r => checks[r] !== 'off');
|
|
156
|
+
let ast;
|
|
157
|
+
if (needsAst) {
|
|
158
|
+
ast = (0, parser_1.parseAst)(content);
|
|
159
|
+
}
|
|
92
160
|
function addIfEnabled(rule, fn) {
|
|
93
161
|
const severity = config.checks[rule];
|
|
94
|
-
if (severity === 'off')
|
|
162
|
+
if (!severity || severity === 'off')
|
|
95
163
|
return;
|
|
96
164
|
const results = fn();
|
|
97
165
|
for (const r of results) {
|
|
@@ -138,6 +206,36 @@ function runChecks(content, file, config) {
|
|
|
138
206
|
addIfEnabled('quote_vs_function', () => (0, quote_vs_function_1.checkQuoteVsFunctionAst)(ast, file));
|
|
139
207
|
addIfEnabled('commented_code', () => (0, commented_code_1.checkCommentedCode)(content, file));
|
|
140
208
|
addIfEnabled('double_not', () => (0, double_not_1.checkDoubleNotAst)(ast, file));
|
|
209
|
+
addIfEnabled('setq_single_arg', () => (0, setq_single_arg_1.checkSetqSingleArgAst)(ast, file));
|
|
210
|
+
addIfEnabled('assoc_without_cdr', () => (0, assoc_without_cdr_1.checkAssocWithoutCdrAst)(ast, file));
|
|
211
|
+
addIfEnabled('identical_branches', () => (0, identical_branches_1.checkIdenticalBranchesAst)(ast, file));
|
|
212
|
+
addIfEnabled('while_constant', () => (0, while_constant_1.checkWhileConstantAst)(ast, file));
|
|
213
|
+
addIfEnabled('cond_duplicate', () => (0, cond_duplicate_1.checkCondDuplicateAst)(ast, file));
|
|
214
|
+
addIfEnabled('error_handling', () => (0, error_handling_1.checkErrorHandling)(content, file));
|
|
215
|
+
addIfEnabled('global_naming', () => (0, global_naming_1.checkGlobalNaming)(content, file));
|
|
216
|
+
addIfEnabled('extra_parens', () => (0, extra_parens_1.checkExtraParens)(content, file));
|
|
217
|
+
addIfEnabled('arg_count', () => (0, arg_count_1.checkArgCount)(content, file));
|
|
218
|
+
addIfEnabled('strcat_usage', () => (0, strcat_usage_1.checkStrcatUsage)(content, file));
|
|
219
|
+
addIfEnabled('cond_simplify', () => (0, cond_simplify_1.checkCondSimplify)(content, file));
|
|
220
|
+
addIfEnabled('quote_style', () => (0, quote_style_1.checkQuoteStyle)(content, file));
|
|
221
|
+
addIfEnabled('eq_usage', () => (0, eq_usage_1.checkEqUsage)(content, file));
|
|
222
|
+
addIfEnabled('lambda_syntax', () => (0, lambda_syntax_1.checkLambdaSyntax)(content, file));
|
|
223
|
+
addIfEnabled('comment_style', () => (0, comment_style_1.checkCommentStyle)(content, file));
|
|
224
|
+
addIfEnabled('empty_catch', () => (0, empty_catch_1.checkEmptyCatch)(content, file));
|
|
225
|
+
addIfEnabled('nth_usage', () => (0, nth_usage_1.checkNthUsage)(content, file));
|
|
226
|
+
addIfEnabled('append_single', () => (0, append_single_1.checkAppendSingle)(content, file));
|
|
227
|
+
addIfEnabled('setq_multiple', () => (0, setq_multiple_1.checkSetqMultiple)(content, file));
|
|
228
|
+
addIfEnabled('function_order', () => (0, function_order_1.checkFunctionOrder)(content, file));
|
|
229
|
+
addIfEnabled('magic_number', () => (0, magic_number_1.checkMagicNumber)(content, file));
|
|
230
|
+
addIfEnabled('mixed_indent', () => (0, mixed_indent_1.checkMixedIndent)(content, file));
|
|
231
|
+
addIfEnabled('long_function_call', () => (0, long_function_call_1.checkLongFunctionCall)(content, file, 6));
|
|
232
|
+
addIfEnabled('no_return', () => (0, no_return_1.checkNoReturn)(content, file));
|
|
233
|
+
addIfEnabled('shadow_builtin', () => (0, shadow_builtin_1.checkShadowBuiltin)(content, file));
|
|
234
|
+
addIfEnabled('dynamic_doc', () => (0, dynamic_doc_1.checkDynamicDoc)(content, file));
|
|
235
|
+
addIfEnabled('loop_optimization', () => (0, loop_optimization_1.checkLoopOptimization)(content, file));
|
|
236
|
+
addIfEnabled('type_check', () => (0, type_check_1.checkTypeCheck)(content, file));
|
|
237
|
+
addIfEnabled('redundant_if', () => (0, redundant_if_1.checkRedundantIf)(content, file));
|
|
238
|
+
addIfEnabled('format_indent', () => (0, format_indent_1.checkFormatIndent)(content, file));
|
|
141
239
|
return issues;
|
|
142
240
|
}
|
|
143
241
|
/** Single-pass visitor-based runChecks using AstVisitor */
|
|
@@ -156,7 +254,7 @@ function runChecksWithVisitor(content, file, config) {
|
|
|
156
254
|
}
|
|
157
255
|
// Register all AST-based checks as visitor handlers
|
|
158
256
|
if (checks['redundant_quotes'] !== 'off') {
|
|
159
|
-
visitor.on('quote',
|
|
257
|
+
visitor.on('quote', node => {
|
|
160
258
|
if (node.children &&
|
|
161
259
|
node.children.length >= 2 &&
|
|
162
260
|
node.children[1].type === 'list' &&
|
|
@@ -169,7 +267,7 @@ function runChecksWithVisitor(content, file, config) {
|
|
|
169
267
|
});
|
|
170
268
|
}
|
|
171
269
|
if (checks['double_not'] !== 'off') {
|
|
172
|
-
visitor.on('not',
|
|
270
|
+
visitor.on('not', node => {
|
|
173
271
|
if (node.children &&
|
|
174
272
|
node.children.length >= 2 &&
|
|
175
273
|
node.children[1].type === 'list' &&
|
|
@@ -182,7 +280,7 @@ function runChecksWithVisitor(content, file, config) {
|
|
|
182
280
|
});
|
|
183
281
|
}
|
|
184
282
|
if (checks['misplaced_else'] !== 'off') {
|
|
185
|
-
visitor.on('if',
|
|
283
|
+
visitor.on('if', node => {
|
|
186
284
|
if (node.children && node.children.length === 4) {
|
|
187
285
|
const cond = node.children[1];
|
|
188
286
|
if (cond.type === 'list' &&
|
|
@@ -209,7 +307,7 @@ function runChecksWithVisitor(content, file, config) {
|
|
|
209
307
|
'vl-some',
|
|
210
308
|
'vl-every',
|
|
211
309
|
]);
|
|
212
|
-
visitor.on('quote',
|
|
310
|
+
visitor.on('quote', node => {
|
|
213
311
|
if (node.children &&
|
|
214
312
|
node.children.length >= 2 &&
|
|
215
313
|
node.children[1].type === 'list' &&
|
|
@@ -258,7 +356,7 @@ function lintFiles(files, config, rootDir) {
|
|
|
258
356
|
}
|
|
259
357
|
function lintFilesParallel(files, config, rootDir) {
|
|
260
358
|
(0, locale_1.setLocale)(config.locale || 'zh');
|
|
261
|
-
return new Promise(
|
|
359
|
+
return new Promise(resolve => {
|
|
262
360
|
const allIssues = [];
|
|
263
361
|
let completed = 0;
|
|
264
362
|
const maxWorkers = Math.min(os.cpus().length, files.length);
|
|
@@ -280,7 +378,7 @@ function lintFilesParallel(files, config, rootDir) {
|
|
|
280
378
|
if (completed >= files.length)
|
|
281
379
|
resolve(allIssues);
|
|
282
380
|
});
|
|
283
|
-
worker.on('error',
|
|
381
|
+
worker.on('error', err => {
|
|
284
382
|
allIssues.push({
|
|
285
383
|
file: relPath,
|
|
286
384
|
line: 1,
|
|
@@ -294,7 +392,7 @@ function lintFilesParallel(files, config, rootDir) {
|
|
|
294
392
|
if (completed >= files.length)
|
|
295
393
|
resolve(allIssues);
|
|
296
394
|
});
|
|
297
|
-
worker.on('exit',
|
|
395
|
+
worker.on('exit', code => {
|
|
298
396
|
if (code !== 0) {
|
|
299
397
|
allIssues.push({
|
|
300
398
|
file: relPath,
|
|
@@ -317,6 +415,225 @@ function lintFilesParallel(files, config, rootDir) {
|
|
|
317
415
|
resolve([]);
|
|
318
416
|
});
|
|
319
417
|
}
|
|
418
|
+
/** Find the matching close paren for the first complete form starting at `start` */
|
|
419
|
+
function findMatchingParen(s, start) {
|
|
420
|
+
if (s[start] !== '(')
|
|
421
|
+
return start;
|
|
422
|
+
let depth = 0;
|
|
423
|
+
for (let i = start; i < s.length; i++) {
|
|
424
|
+
if (s[i] === '"') {
|
|
425
|
+
i++;
|
|
426
|
+
while (i < s.length && s[i] !== '"') {
|
|
427
|
+
if (s[i] === '\\')
|
|
428
|
+
i++;
|
|
429
|
+
i++;
|
|
430
|
+
}
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (s[i] === '(')
|
|
434
|
+
depth++;
|
|
435
|
+
else if (s[i] === ')') {
|
|
436
|
+
depth--;
|
|
437
|
+
if (depth === 0)
|
|
438
|
+
return i;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return s.length - 1;
|
|
442
|
+
}
|
|
443
|
+
/** Extract the next top-level form(s) from a string starting at `start`. Returns [form, endIdx]. */
|
|
444
|
+
function extractForm(s, start) {
|
|
445
|
+
let i = start;
|
|
446
|
+
while (i < s.length && s[i] === ' ')
|
|
447
|
+
i++;
|
|
448
|
+
if (i >= s.length)
|
|
449
|
+
return ['', i];
|
|
450
|
+
if (s[i] !== '(') {
|
|
451
|
+
const m = s.slice(i).match(/^\S+/);
|
|
452
|
+
if (m)
|
|
453
|
+
return [m[0], i + m[0].length];
|
|
454
|
+
return ['', i];
|
|
455
|
+
}
|
|
456
|
+
const end = findMatchingParen(s, i);
|
|
457
|
+
return [s.slice(i, end + 1), end + 1];
|
|
458
|
+
}
|
|
459
|
+
function applyFixes(issues, content, filepath) {
|
|
460
|
+
const lines = content.split('\n');
|
|
461
|
+
const fixesByRule = new Map();
|
|
462
|
+
for (const iss of issues) {
|
|
463
|
+
if (iss.file !== filepath)
|
|
464
|
+
continue;
|
|
465
|
+
const set = fixesByRule.get(iss.rule) || new Set();
|
|
466
|
+
set.add(iss.line);
|
|
467
|
+
fixesByRule.set(iss.rule, set);
|
|
468
|
+
}
|
|
469
|
+
// Apply fixes from higher line numbers first to preserve offsets
|
|
470
|
+
const fixableRules = new Set([
|
|
471
|
+
'redundant_quotes',
|
|
472
|
+
'double_not',
|
|
473
|
+
'redundant_nil_else',
|
|
474
|
+
'single_arg_and_or',
|
|
475
|
+
'redundant_setq',
|
|
476
|
+
'redundant_progn',
|
|
477
|
+
'redundant_let',
|
|
478
|
+
'quote_style',
|
|
479
|
+
'redundant_if',
|
|
480
|
+
'misplaced_else',
|
|
481
|
+
]);
|
|
482
|
+
let result = content;
|
|
483
|
+
for (const [rule, lineSet] of fixesByRule) {
|
|
484
|
+
if (!fixableRules.has(rule))
|
|
485
|
+
continue;
|
|
486
|
+
const sortedLines = Array.from(lineSet).sort((a, b) => b - a);
|
|
487
|
+
for (const line of sortedLines) {
|
|
488
|
+
const idx = line - 1;
|
|
489
|
+
if (idx < 0 || idx >= lines.length)
|
|
490
|
+
continue;
|
|
491
|
+
const lineContent = result.split('\n')[idx];
|
|
492
|
+
switch (rule) {
|
|
493
|
+
case 'redundant_quotes': {
|
|
494
|
+
// Replace ''x with 'x on this line
|
|
495
|
+
const fixed = lineContent.replace(/''(\S)/g, "'$1");
|
|
496
|
+
if (fixed !== lineContent) {
|
|
497
|
+
result = replaceLine(result, idx, fixed);
|
|
498
|
+
}
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
case 'double_not': {
|
|
502
|
+
// Replace (not (not ...)) with (not ...)
|
|
503
|
+
const fixed = lineContent.replace(/\(not\s+\(not\s+/g, '(not ');
|
|
504
|
+
if (fixed !== lineContent) {
|
|
505
|
+
result = replaceLine(result, idx, fixed);
|
|
506
|
+
}
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
case 'redundant_nil_else': {
|
|
510
|
+
// Replace (if ... nil) with (if ...)
|
|
511
|
+
const fixed = lineContent.replace(/\s+nil\s*\)$/, ')');
|
|
512
|
+
if (fixed !== lineContent) {
|
|
513
|
+
result = replaceLine(result, idx, fixed);
|
|
514
|
+
}
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
case 'single_arg_and_or': {
|
|
518
|
+
// Replace (and x) with x, (or x) with x
|
|
519
|
+
const m = lineContent.match(/\((and|or)\s+([^)\s]+)\s*\)/);
|
|
520
|
+
if (m) {
|
|
521
|
+
result = replaceLine(result, idx, m[2]);
|
|
522
|
+
}
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
case 'redundant_progn': {
|
|
526
|
+
// (progn <single-form>) → <single-form>
|
|
527
|
+
const proIdx = lineContent.indexOf('(progn ');
|
|
528
|
+
if (proIdx !== -1) {
|
|
529
|
+
const beforePro = lineContent.slice(0, proIdx);
|
|
530
|
+
const close = findMatchingParen(lineContent, proIdx);
|
|
531
|
+
if (close > proIdx) {
|
|
532
|
+
const inner = lineContent.slice(proIdx + 7, close);
|
|
533
|
+
const rest = lineContent.slice(close + 1);
|
|
534
|
+
const fixed = beforePro + inner + rest;
|
|
535
|
+
if (fixed !== lineContent)
|
|
536
|
+
result = replaceLine(result, idx, fixed);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
case 'redundant_let': {
|
|
542
|
+
// (let () body...) → (progn body...)
|
|
543
|
+
const fixed = lineContent.replace(/\(let\s+\(\)\s*/, '(progn ');
|
|
544
|
+
if (fixed !== lineContent)
|
|
545
|
+
result = replaceLine(result, idx, fixed);
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
case 'quote_style': {
|
|
549
|
+
// (quote x) → 'x
|
|
550
|
+
const qIdx = lineContent.indexOf('(quote ');
|
|
551
|
+
if (qIdx !== -1) {
|
|
552
|
+
const beforeQ = lineContent.slice(0, qIdx);
|
|
553
|
+
const close = findMatchingParen(lineContent, qIdx);
|
|
554
|
+
if (close > qIdx) {
|
|
555
|
+
const quoted = lineContent.slice(qIdx + 7, close);
|
|
556
|
+
const rest = lineContent.slice(close + 1);
|
|
557
|
+
const fixed = beforeQ + "'" + quoted + rest;
|
|
558
|
+
if (fixed !== lineContent)
|
|
559
|
+
result = replaceLine(result, idx, fixed);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
case 'redundant_if': {
|
|
565
|
+
// (if/when/unless (progn ...) ...) → remove inner progn
|
|
566
|
+
const pIdx = lineContent.indexOf('(progn ');
|
|
567
|
+
if (pIdx !== -1) {
|
|
568
|
+
const beforeP = lineContent.slice(0, pIdx);
|
|
569
|
+
const close = findMatchingParen(lineContent, pIdx);
|
|
570
|
+
if (close > pIdx) {
|
|
571
|
+
const inner = lineContent.slice(pIdx + 7, close);
|
|
572
|
+
const rest = lineContent.slice(close + 1);
|
|
573
|
+
const fixed = beforeP + inner + rest;
|
|
574
|
+
if (fixed !== lineContent)
|
|
575
|
+
result = replaceLine(result, idx, fixed);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
case 'misplaced_else': {
|
|
581
|
+
// (if (not x) then else) → (if x else then)
|
|
582
|
+
const ifStart = lineContent.indexOf('(if (not ');
|
|
583
|
+
if (ifStart !== -1) {
|
|
584
|
+
const notIdx = lineContent.indexOf('(not ', ifStart);
|
|
585
|
+
if (notIdx === -1)
|
|
586
|
+
break;
|
|
587
|
+
const notClose = findMatchingParen(lineContent, notIdx);
|
|
588
|
+
if (notClose <= notIdx)
|
|
589
|
+
break;
|
|
590
|
+
const ifClose = findMatchingParen(lineContent, ifStart);
|
|
591
|
+
if (ifClose <= ifStart)
|
|
592
|
+
break;
|
|
593
|
+
const indent = lineContent.slice(0, ifStart);
|
|
594
|
+
const condVar = lineContent.slice(notIdx + 5, notClose);
|
|
595
|
+
const branchContent = lineContent.slice(notClose + 1, ifClose).trim();
|
|
596
|
+
const [thenForm, thenEnd] = extractForm(branchContent, 0);
|
|
597
|
+
const elseForm = branchContent.slice(thenEnd).trim();
|
|
598
|
+
if (thenForm && elseForm) {
|
|
599
|
+
const rest = lineContent.slice(ifClose + 1);
|
|
600
|
+
const fixed = indent + '(if ' + condVar + ' ' + elseForm + ' ' + thenForm + ')' + rest;
|
|
601
|
+
if (fixed !== lineContent)
|
|
602
|
+
result = replaceLine(result, idx, fixed);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
case 'redundant_setq': {
|
|
608
|
+
// Remove (setq x x) lines
|
|
609
|
+
const allLines = result.split('\n');
|
|
610
|
+
const trimmed = allLines[idx].trim();
|
|
611
|
+
if (/^\(setq\s+(\S+)\s+\1\s*\)$/.test(trimmed)) {
|
|
612
|
+
allLines.splice(idx, 1);
|
|
613
|
+
result = allLines.join('\n');
|
|
614
|
+
}
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return result;
|
|
621
|
+
}
|
|
622
|
+
function replaceLine(content, lineIdx, newLine) {
|
|
623
|
+
const lines = content.split('\n');
|
|
624
|
+
lines[lineIdx] = newLine;
|
|
625
|
+
return lines.join('\n');
|
|
626
|
+
}
|
|
627
|
+
function parseIgnoreFile(rootDir) {
|
|
628
|
+
const ignoreFile = path.join(rootDir, '.atlisp-lint-ignore');
|
|
629
|
+
if (!fs.existsSync(ignoreFile))
|
|
630
|
+
return [];
|
|
631
|
+
const content = fs.readFileSync(ignoreFile, 'utf-8');
|
|
632
|
+
return content
|
|
633
|
+
.split('\n')
|
|
634
|
+
.map(l => l.trim())
|
|
635
|
+
.filter(l => l.length > 0 && !l.startsWith('#'));
|
|
636
|
+
}
|
|
320
637
|
const UTF8_BOM = 0xfeff;
|
|
321
638
|
function fixFile(filepath) {
|
|
322
639
|
const fixes = [];
|
|
@@ -324,7 +641,7 @@ function fixFile(filepath) {
|
|
|
324
641
|
// Fix trailing whitespace
|
|
325
642
|
const wsFixed = content
|
|
326
643
|
.split('\n')
|
|
327
|
-
.map(
|
|
644
|
+
.map(l => l.replace(/[ \t]+$/, ''))
|
|
328
645
|
.join('\n');
|
|
329
646
|
if (wsFixed !== content) {
|
|
330
647
|
content = wsFixed;
|
|
@@ -362,7 +679,7 @@ function fixFile(filepath) {
|
|
|
362
679
|
// Fix empty comments: remove lines that are only ';' with optional whitespace
|
|
363
680
|
const lines = content.split('\n');
|
|
364
681
|
let changed = false;
|
|
365
|
-
const fixedLines = lines.map(
|
|
682
|
+
const fixedLines = lines.map(l => {
|
|
366
683
|
if (/^\s*;\s*$/.test(l)) {
|
|
367
684
|
changed = true;
|
|
368
685
|
return '';
|
package/dist/sbcl.js
CHANGED
|
@@ -72,7 +72,7 @@ function runSbclLint(srcDir, sbclConfig, stubPackagesPath) {
|
|
|
72
72
|
}
|
|
73
73
|
try {
|
|
74
74
|
// Convert arrays to Lisp-readable format: ("a" "b")
|
|
75
|
-
const toLispList = (arr) => '(' + arr.map(
|
|
75
|
+
const toLispList = (arr) => '(' + arr.map(s => `"${s}"`).join(' ') + ')';
|
|
76
76
|
const result = (0, child_process_1.execFileSync)(sbcl, [
|
|
77
77
|
'--script',
|
|
78
78
|
scriptPath,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
((AUTOLISP ())
|
|
2
|
+
(@ (COMMON-LISP AUTOLISP))
|
|
3
|
+
(C ())
|
|
4
|
+
(JSON ())
|
|
5
|
+
(SIDEBAR ())
|
|
6
|
+
(FUN ())
|
|
7
|
+
(DCL ())
|
|
8
|
+
(BASE ())
|
|
9
|
+
(STRING ())
|
|
10
|
+
(BLOCK ())
|
|
11
|
+
(ENTITY ())
|
|
12
|
+
(LAYER ())
|
|
13
|
+
(LAYOUT ())
|
|
14
|
+
(CURVE ())
|
|
15
|
+
(LIST ())
|
|
16
|
+
(M ())
|
|
17
|
+
(P ())
|
|
18
|
+
(UI ())
|
|
19
|
+
(SYS ())
|
|
20
|
+
(EXCEL ())
|
|
21
|
+
(WORD ())
|
|
22
|
+
(RE ())
|
|
23
|
+
(VECTRA ())
|
|
24
|
+
(DOS ())
|
|
25
|
+
(AT-PM ())
|
|
26
|
+
(QRENCODE ())
|
|
27
|
+
(NETWORK ())
|
|
28
|
+
(PKGMAN ())
|
|
29
|
+
(DATETIME ())
|
|
30
|
+
(DICT ())
|
|
31
|
+
(@NLP ())
|
|
32
|
+
(AT-SIDEBAR ())
|
|
33
|
+
(S ())
|
|
34
|
+
(VL ())
|
|
35
|
+
(VLR ())
|
|
36
|
+
(VLAX ())
|
|
37
|
+
(THEME ())
|
|
38
|
+
(AJAX ())
|
|
39
|
+
(ATLISP ())
|
|
40
|
+
(LOG ())
|
|
41
|
+
(ATLISP-CORE ()))
|
package/dist/types.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
export type Severity = 'off' | 'warn' | 'error';
|
|
2
|
+
export interface Fix {
|
|
3
|
+
range: [number, number];
|
|
4
|
+
replacement: string;
|
|
5
|
+
}
|
|
2
6
|
export interface Issue {
|
|
3
7
|
file: string;
|
|
4
8
|
line: number;
|
|
5
9
|
severity: Severity;
|
|
6
10
|
rule: string;
|
|
7
11
|
message: string;
|
|
12
|
+
fix?: Fix;
|
|
8
13
|
}
|
|
9
14
|
export interface CheckConfig {
|
|
10
15
|
severity: Severity;
|
|
@@ -61,6 +66,7 @@ export interface LintConfig {
|
|
|
61
66
|
namespace_header: NamespaceHeaderConfig;
|
|
62
67
|
bare_function_names: BareFunctionNamesConfig;
|
|
63
68
|
sbcl: SbclConfig;
|
|
69
|
+
preset?: string;
|
|
64
70
|
}
|
|
65
71
|
export interface FormattedResult {
|
|
66
72
|
issues: Issue[];
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateConfig = validateConfig;
|
|
4
|
+
const VALID_RULES = [
|
|
5
|
+
'append_single',
|
|
6
|
+
'arg_count',
|
|
7
|
+
'assoc_without_cdr',
|
|
8
|
+
'bare_function_names',
|
|
9
|
+
'cl_syntax',
|
|
10
|
+
'command_shell',
|
|
11
|
+
'comment_style',
|
|
12
|
+
'commented_code',
|
|
13
|
+
'cond_simplify',
|
|
14
|
+
'cond_duplicate',
|
|
15
|
+
'constant_condition',
|
|
16
|
+
'dangling_defun',
|
|
17
|
+
'double_not',
|
|
18
|
+
'duplicate_defun',
|
|
19
|
+
'dynamic_doc',
|
|
20
|
+
'empty_branch',
|
|
21
|
+
'empty_catch',
|
|
22
|
+
'empty_comment',
|
|
23
|
+
'encoding',
|
|
24
|
+
'eq_usage',
|
|
25
|
+
'error_handling',
|
|
26
|
+
'extra_parens',
|
|
27
|
+
'format_indent',
|
|
28
|
+
'function_complexity',
|
|
29
|
+
'function_order',
|
|
30
|
+
'global_naming',
|
|
31
|
+
'identical_branches',
|
|
32
|
+
'lambda_syntax',
|
|
33
|
+
'line_length',
|
|
34
|
+
'long_function_call',
|
|
35
|
+
'loop_optimization',
|
|
36
|
+
'magic_number',
|
|
37
|
+
'misplaced_else',
|
|
38
|
+
'missing_doc',
|
|
39
|
+
'missing_export',
|
|
40
|
+
'mixed_indent',
|
|
41
|
+
'module_registration',
|
|
42
|
+
'multiple_setq',
|
|
43
|
+
'namespace_header',
|
|
44
|
+
'no_return',
|
|
45
|
+
'nth_usage',
|
|
46
|
+
'open_without_close',
|
|
47
|
+
'parameter_naming',
|
|
48
|
+
'parens',
|
|
49
|
+
'quit_exit',
|
|
50
|
+
'quote_style',
|
|
51
|
+
'quote_vs_function',
|
|
52
|
+
'recursive_call',
|
|
53
|
+
'redundant_cond',
|
|
54
|
+
'redundant_if',
|
|
55
|
+
'redundant_let',
|
|
56
|
+
'redundant_nil_else',
|
|
57
|
+
'redundant_progn',
|
|
58
|
+
'redundant_quotes',
|
|
59
|
+
'redundant_setq',
|
|
60
|
+
'self_compare',
|
|
61
|
+
'setq_multiple',
|
|
62
|
+
'setq_single_arg',
|
|
63
|
+
'shadow_builtin',
|
|
64
|
+
'single_arg_and_or',
|
|
65
|
+
'startapp',
|
|
66
|
+
'strcat_usage',
|
|
67
|
+
'token_in_url',
|
|
68
|
+
'trailing_paren',
|
|
69
|
+
'trailing_whitespace',
|
|
70
|
+
'type_check',
|
|
71
|
+
'unused_let_binding',
|
|
72
|
+
'unused_local_fun',
|
|
73
|
+
'unused_package_dep',
|
|
74
|
+
'unused_parameter',
|
|
75
|
+
'unused_variable',
|
|
76
|
+
'variable_shadow',
|
|
77
|
+
'while_constant',
|
|
78
|
+
'vl_registry_write',
|
|
79
|
+
'vlax_without_loading',
|
|
80
|
+
];
|
|
81
|
+
const VALID_SEVERITIES = ['off', 'warn', 'error'];
|
|
82
|
+
function validateConfig(config) {
|
|
83
|
+
const errors = [];
|
|
84
|
+
if (config.locale && config.locale !== 'zh' && config.locale !== 'en') {
|
|
85
|
+
errors.push({ path: 'locale', message: `Invalid locale '${config.locale}', expected 'zh' or 'en'` });
|
|
86
|
+
}
|
|
87
|
+
if (config.checks) {
|
|
88
|
+
for (const [rule, sev] of Object.entries(config.checks)) {
|
|
89
|
+
if (!VALID_RULES.includes(rule)) {
|
|
90
|
+
errors.push({ path: `checks.${rule}`, message: `Unknown rule '${rule}'` });
|
|
91
|
+
}
|
|
92
|
+
if (!VALID_SEVERITIES.includes(sev)) {
|
|
93
|
+
errors.push({
|
|
94
|
+
path: `checks.${rule}`,
|
|
95
|
+
message: `Invalid severity '${sev}', expected 'off', 'warn', or 'error'`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (config.source) {
|
|
101
|
+
if (!Array.isArray(config.source.globs)) {
|
|
102
|
+
errors.push({ path: 'source.globs', message: 'Must be an array of glob patterns' });
|
|
103
|
+
}
|
|
104
|
+
if (!Array.isArray(config.source.exclude)) {
|
|
105
|
+
errors.push({ path: 'source.exclude', message: 'Must be an array of glob patterns' });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (config.line_length) {
|
|
109
|
+
if (typeof config.line_length.max !== 'number' || config.line_length.max < 1) {
|
|
110
|
+
errors.push({ path: 'line_length.max', message: 'Must be a positive number' });
|
|
111
|
+
}
|
|
112
|
+
if (typeof config.line_length.tab_width !== 'number' || config.line_length.tab_width < 1) {
|
|
113
|
+
errors.push({ path: 'line_length.tab_width', message: 'Must be a positive number' });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (config.function_complexity) {
|
|
117
|
+
if (typeof config.function_complexity.max_lines !== 'number' || config.function_complexity.max_lines < 1) {
|
|
118
|
+
errors.push({ path: 'function_complexity.max_lines', message: 'Must be a positive number' });
|
|
119
|
+
}
|
|
120
|
+
if (typeof config.function_complexity.max_nesting !== 'number' || config.function_complexity.max_nesting < 1) {
|
|
121
|
+
errors.push({ path: 'function_complexity.max_nesting', message: 'Must be a positive number' });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return errors;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=validate.js.map
|
package/dist/watch.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { LintConfig } from './types';
|
|
2
|
+
interface WatchOptions {
|
|
3
|
+
rootDir: string;
|
|
4
|
+
format: 'default' | 'json';
|
|
5
|
+
config: LintConfig;
|
|
6
|
+
}
|
|
7
|
+
export declare function watchFiles(files: string[], options: WatchOptions): void;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=watch.d.ts.map
|