@atlisp/lint 0.1.2 → 0.1.4
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 +3 -0
- package/atlisp-lint.default.json +26 -7
- package/dist/checks/bare-names.js +4 -1
- package/dist/checks/cl-syntax.js +4 -1
- package/dist/checks/commented-code.d.ts +3 -0
- package/dist/checks/commented-code.js +46 -0
- package/dist/checks/constant-condition.d.ts +5 -0
- package/dist/checks/constant-condition.js +88 -0
- package/dist/checks/dangerous-calls.d.ts +2 -0
- package/dist/checks/dangerous-calls.js +70 -29
- package/dist/checks/dangling-defun.d.ts +6 -0
- package/dist/checks/dangling-defun.js +85 -0
- package/dist/checks/double-not.d.ts +5 -0
- package/dist/checks/double-not.js +30 -0
- package/dist/checks/empty-branch.d.ts +5 -0
- package/dist/checks/empty-branch.js +34 -0
- package/dist/checks/empty-comments.d.ts +3 -0
- package/dist/checks/empty-comments.js +26 -0
- package/dist/checks/encoding.js +13 -4
- package/dist/checks/function-complexity.d.ts +2 -0
- package/dist/checks/function-complexity.js +89 -45
- package/dist/checks/line-length.js +4 -1
- package/dist/checks/misplaced-else.d.ts +5 -0
- package/dist/checks/misplaced-else.js +31 -0
- package/dist/checks/missing-doc.js +5 -2
- package/dist/checks/missing-export.d.ts +13 -0
- package/dist/checks/missing-export.js +94 -0
- package/dist/checks/module-reg.js +9 -4
- package/dist/checks/multiple-setq.d.ts +5 -0
- package/dist/checks/multiple-setq.js +51 -0
- package/dist/checks/namespace-header.js +16 -6
- package/dist/checks/open-close.d.ts +2 -0
- package/dist/checks/open-close.js +18 -13
- package/dist/checks/parameter-naming.d.ts +2 -0
- package/dist/checks/parameter-naming.js +26 -14
- package/dist/checks/parens.js +8 -3
- package/dist/checks/quote-vs-function.d.ts +5 -0
- package/dist/checks/quote-vs-function.js +50 -0
- package/dist/checks/recursive-call.d.ts +5 -0
- package/dist/checks/recursive-call.js +49 -0
- package/dist/checks/redundant-cond.d.ts +5 -0
- package/dist/checks/redundant-cond.js +50 -0
- package/dist/checks/redundant-let.d.ts +5 -0
- package/dist/checks/redundant-let.js +31 -0
- package/dist/checks/redundant-nil-else.d.ts +5 -0
- package/dist/checks/redundant-nil-else.js +32 -0
- package/dist/checks/redundant-progn.d.ts +5 -0
- package/dist/checks/redundant-progn.js +31 -0
- package/dist/checks/redundant-quotes.d.ts +5 -0
- package/dist/checks/redundant-quotes.js +33 -0
- package/dist/checks/redundant-setq.d.ts +5 -0
- package/dist/checks/redundant-setq.js +34 -0
- package/dist/checks/self-compare.d.ts +5 -0
- package/dist/checks/self-compare.js +37 -0
- package/dist/checks/single-arg-and-or.d.ts +5 -0
- package/dist/checks/single-arg-and-or.js +32 -0
- package/dist/checks/token-in-url.js +4 -1
- package/dist/checks/trailing-paren.d.ts +3 -0
- package/dist/checks/trailing-paren.js +44 -0
- package/dist/checks/trailing-ws.js +4 -1
- package/dist/checks/unused-let.d.ts +5 -0
- package/dist/checks/unused-let.js +57 -0
- package/dist/checks/unused-local-fun.d.ts +5 -0
- package/dist/checks/unused-local-fun.js +62 -0
- package/dist/checks/unused-package-dep.d.ts +3 -0
- package/dist/checks/unused-package-dep.js +93 -0
- package/dist/checks/unused-param.d.ts +5 -0
- package/dist/checks/unused-param.js +63 -0
- package/dist/checks/unused-variable.d.ts +2 -0
- package/dist/checks/unused-variable.js +70 -30
- package/dist/checks/variable-shadow.d.ts +5 -0
- package/dist/checks/variable-shadow.js +66 -0
- package/dist/checks/vlax.js +8 -3
- package/dist/config.d.ts +1 -2
- package/dist/config.js +0 -13
- package/dist/disable.js +4 -1
- package/dist/formatters.d.ts +1 -2
- package/dist/formatters.js +8 -61
- package/dist/index.d.ts +1 -1
- package/dist/index.js +137 -117
- package/dist/locale.js +83 -47
- package/dist/project.d.ts +3 -0
- package/dist/project.js +138 -0
- package/dist/runner.d.ts +3 -0
- package/dist/runner.js +215 -23
- package/dist/sbcl.js +16 -11
- package/dist/worker.js +10 -50
- package/lib/lint-sbcl.lisp +19 -54
- package/package.json +18 -4
- package/dist/atlisp-lint.default.json +0 -71
- package/dist/cache.d.ts.map +0 -1
- package/dist/cache.js.map +0 -1
- package/dist/checks/arg-count.d.ts +0 -3
- package/dist/checks/arg-count.d.ts.map +0 -1
- package/dist/checks/arg-count.js +0 -120
- package/dist/checks/arg-count.js.map +0 -1
- package/dist/checks/bare-names.d.ts.map +0 -1
- package/dist/checks/bare-names.js.map +0 -1
- package/dist/checks/cl-syntax.d.ts.map +0 -1
- package/dist/checks/cl-syntax.js.map +0 -1
- package/dist/checks/cond-simplify.d.ts +0 -3
- package/dist/checks/cond-simplify.d.ts.map +0 -1
- package/dist/checks/cond-simplify.js +0 -42
- package/dist/checks/cond-simplify.js.map +0 -1
- package/dist/checks/dangerous-calls.d.ts.map +0 -1
- package/dist/checks/dangerous-calls.js.map +0 -1
- package/dist/checks/encoding.d.ts.map +0 -1
- package/dist/checks/encoding.js.map +0 -1
- package/dist/checks/error-handling.d.ts +0 -3
- package/dist/checks/error-handling.d.ts.map +0 -1
- package/dist/checks/error-handling.js +0 -53
- package/dist/checks/error-handling.js.map +0 -1
- package/dist/checks/extra-parens.d.ts +0 -3
- package/dist/checks/extra-parens.d.ts.map +0 -1
- package/dist/checks/extra-parens.js +0 -42
- package/dist/checks/extra-parens.js.map +0 -1
- package/dist/checks/function-complexity.d.ts.map +0 -1
- package/dist/checks/function-complexity.js.map +0 -1
- package/dist/checks/global-naming.d.ts +0 -3
- package/dist/checks/global-naming.d.ts.map +0 -1
- package/dist/checks/global-naming.js +0 -51
- package/dist/checks/global-naming.js.map +0 -1
- package/dist/checks/line-length.d.ts.map +0 -1
- package/dist/checks/line-length.js.map +0 -1
- package/dist/checks/missing-doc.d.ts.map +0 -1
- package/dist/checks/missing-doc.js.map +0 -1
- package/dist/checks/module-reg.d.ts.map +0 -1
- package/dist/checks/module-reg.js.map +0 -1
- package/dist/checks/namespace-header.d.ts.map +0 -1
- package/dist/checks/namespace-header.js.map +0 -1
- package/dist/checks/open-close.d.ts.map +0 -1
- package/dist/checks/open-close.js.map +0 -1
- package/dist/checks/parameter-naming.d.ts.map +0 -1
- package/dist/checks/parameter-naming.js.map +0 -1
- package/dist/checks/parens.d.ts.map +0 -1
- package/dist/checks/parens.js.map +0 -1
- package/dist/checks/strcat-usage.d.ts +0 -3
- package/dist/checks/strcat-usage.d.ts.map +0 -1
- package/dist/checks/strcat-usage.js +0 -22
- package/dist/checks/strcat-usage.js.map +0 -1
- package/dist/checks/token-in-url.d.ts.map +0 -1
- package/dist/checks/token-in-url.js.map +0 -1
- package/dist/checks/trailing-ws.d.ts.map +0 -1
- package/dist/checks/trailing-ws.js.map +0 -1
- package/dist/checks/unused-variable.d.ts.map +0 -1
- package/dist/checks/unused-variable.js.map +0 -1
- package/dist/checks/vlax.d.ts.map +0 -1
- package/dist/checks/vlax.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/disable.d.ts.map +0 -1
- package/dist/disable.js.map +0 -1
- package/dist/formatters.d.ts.map +0 -1
- package/dist/formatters.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/lint-sbcl.lisp +0 -161
- package/dist/locale.d.ts.map +0 -1
- package/dist/locale.js.map +0 -1
- package/dist/rules.d.ts +0 -9
- package/dist/rules.d.ts.map +0 -1
- package/dist/rules.js +0 -39
- package/dist/rules.js.map +0 -1
- package/dist/runner.d.ts.map +0 -1
- package/dist/runner.js.map +0 -1
- package/dist/sbcl.d.ts.map +0 -1
- package/dist/sbcl.js.map +0 -1
- package/dist/stub-packages.json +0 -41
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js.map +0 -1
- package/dist/validate.d.ts +0 -8
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js +0 -55
- package/dist/validate.js.map +0 -1
- package/dist/watch.d.ts +0 -9
- package/dist/watch.d.ts.map +0 -1
- package/dist/watch.js +0 -109
- package/dist/watch.js.map +0 -1
- package/dist/worker.d.ts.map +0 -1
- package/dist/worker.js.map +0 -1
- package/pre-commit/hook.sh +0 -4
package/dist/index.js
CHANGED
|
@@ -39,15 +39,27 @@ const path = __importStar(require("path"));
|
|
|
39
39
|
const child_process_1 = require("child_process");
|
|
40
40
|
const config_1 = require("./config");
|
|
41
41
|
const runner_1 = require("./runner");
|
|
42
|
+
const project_1 = require("./project");
|
|
42
43
|
const sbcl_1 = require("./sbcl");
|
|
43
44
|
const formatters_1 = require("./formatters");
|
|
44
|
-
const watch_1 = require("./watch");
|
|
45
|
-
const validate_1 = require("./validate");
|
|
46
|
-
const rules_1 = require("./rules");
|
|
47
45
|
const locale_1 = require("./locale");
|
|
46
|
+
const cache_1 = require("./cache");
|
|
48
47
|
function parseArgs() {
|
|
49
48
|
const argv = process.argv.slice(2);
|
|
50
|
-
const opts = {
|
|
49
|
+
const opts = {
|
|
50
|
+
src: [],
|
|
51
|
+
test: [],
|
|
52
|
+
staged: false,
|
|
53
|
+
format: 'default',
|
|
54
|
+
init: false,
|
|
55
|
+
installHook: false,
|
|
56
|
+
fix: false,
|
|
57
|
+
hookArgs: '',
|
|
58
|
+
cache: false,
|
|
59
|
+
clearCache: false,
|
|
60
|
+
parallel: false,
|
|
61
|
+
project: false,
|
|
62
|
+
};
|
|
51
63
|
for (let i = 0; i < argv.length; i++) {
|
|
52
64
|
switch (argv[i]) {
|
|
53
65
|
case '--src':
|
|
@@ -77,19 +89,20 @@ function parseArgs() {
|
|
|
77
89
|
case '--hook-args':
|
|
78
90
|
opts.hookArgs = argv[++i];
|
|
79
91
|
break;
|
|
80
|
-
case '--
|
|
81
|
-
opts.
|
|
92
|
+
case '--cache':
|
|
93
|
+
opts.cache = true;
|
|
94
|
+
break;
|
|
95
|
+
case '--clear-cache':
|
|
96
|
+
opts.clearCache = true;
|
|
82
97
|
break;
|
|
83
|
-
case '--
|
|
84
|
-
opts.
|
|
98
|
+
case '--parallel':
|
|
99
|
+
opts.parallel = true;
|
|
85
100
|
break;
|
|
86
|
-
case '--
|
|
87
|
-
opts.
|
|
101
|
+
case '--project':
|
|
102
|
+
opts.project = true;
|
|
88
103
|
break;
|
|
89
|
-
|
|
90
|
-
opts.output = argv[++i];
|
|
104
|
+
default:
|
|
91
105
|
break;
|
|
92
|
-
default: break;
|
|
93
106
|
}
|
|
94
107
|
}
|
|
95
108
|
return opts;
|
|
@@ -155,28 +168,32 @@ function collectFiles(rootDir, opts, config) {
|
|
|
155
168
|
}
|
|
156
169
|
}
|
|
157
170
|
}
|
|
158
|
-
const excludePatterns = config.source.exclude.map(e => {
|
|
171
|
+
const excludePatterns = config.source.exclude.map((e) => {
|
|
159
172
|
const str = e.replace(/\./g, '\\.').replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*');
|
|
160
173
|
return new RegExp(`^${str}$`);
|
|
161
174
|
});
|
|
162
|
-
return Array.from(files)
|
|
175
|
+
return Array.from(files)
|
|
176
|
+
.filter((f) => {
|
|
163
177
|
const rel = path.relative(rootDir, f).replace(/\\/g, '/');
|
|
164
178
|
for (const re of excludePatterns) {
|
|
165
179
|
if (re.test(rel))
|
|
166
180
|
return false;
|
|
167
181
|
}
|
|
168
182
|
return true;
|
|
169
|
-
})
|
|
183
|
+
})
|
|
184
|
+
.sort();
|
|
170
185
|
}
|
|
171
186
|
function collectStagedFiles(rootDir) {
|
|
172
187
|
const output = (0, child_process_1.execSync)('git diff --cached --name-only --diff-filter=ACM', {
|
|
173
|
-
cwd: rootDir,
|
|
188
|
+
cwd: rootDir,
|
|
189
|
+
encoding: 'utf-8',
|
|
174
190
|
});
|
|
175
|
-
return output
|
|
176
|
-
.
|
|
177
|
-
.
|
|
178
|
-
.
|
|
179
|
-
.
|
|
191
|
+
return output
|
|
192
|
+
.split('\n')
|
|
193
|
+
.map((l) => l.trim())
|
|
194
|
+
.filter((l) => l.endsWith('.lsp'))
|
|
195
|
+
.map((l) => path.resolve(rootDir, l))
|
|
196
|
+
.filter((f) => fs.existsSync(f));
|
|
180
197
|
}
|
|
181
198
|
function initConfig(rootDir) {
|
|
182
199
|
const defaultPath = path.join(__dirname, '..', 'atlisp-lint.default.json');
|
|
@@ -205,115 +222,118 @@ npx @atlisp/lint ${args} || exit 1
|
|
|
205
222
|
fs.chmodSync(hookPath, 0o755);
|
|
206
223
|
console.log((0, locale_1.t)('index.hook_installed', hookPath));
|
|
207
224
|
}
|
|
208
|
-
function main() {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
fs.writeFileSync(opts.output, docs, 'utf-8');
|
|
216
|
-
console.log(`${(0, locale_1.t)('index.config_created', opts.output)}`);
|
|
225
|
+
async function main() {
|
|
226
|
+
try {
|
|
227
|
+
const opts = parseArgs();
|
|
228
|
+
const rootDir = process.cwd();
|
|
229
|
+
if (opts.init) {
|
|
230
|
+
initConfig(rootDir);
|
|
231
|
+
return;
|
|
217
232
|
}
|
|
218
|
-
|
|
219
|
-
|
|
233
|
+
if (opts.installHook) {
|
|
234
|
+
installPreCommitHook(rootDir, opts.hookArgs);
|
|
235
|
+
return;
|
|
220
236
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
if (opts.installHook) {
|
|
228
|
-
installPreCommitHook(rootDir, opts.hookArgs);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
// Load config
|
|
232
|
-
const configPath = opts.config || path.join(rootDir, 'atlisp-lint.json');
|
|
233
|
-
const config = (0, config_1.loadConfig)(fs.existsSync(configPath) ? configPath : undefined);
|
|
234
|
-
(0, locale_1.setLocale)(config.locale || 'zh');
|
|
235
|
-
// --validate-config: check config validity
|
|
236
|
-
if (opts.validateConfig) {
|
|
237
|
-
const errors = (0, validate_1.validateConfig)(config);
|
|
238
|
-
if (errors.length === 0) {
|
|
239
|
-
console.log((0, locale_1.t)('index.config_valid'));
|
|
237
|
+
if (opts.clearCache) {
|
|
238
|
+
(0, cache_1.clearCache)(rootDir);
|
|
239
|
+
console.log((0, locale_1.t)('index.cache_cleared'));
|
|
240
|
+
return;
|
|
240
241
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
242
|
+
// Load config
|
|
243
|
+
const configPath = opts.config || path.join(rootDir, 'atlisp-lint.json');
|
|
244
|
+
const config = (0, config_1.loadConfig)(fs.existsSync(configPath) ? configPath : undefined);
|
|
245
|
+
(0, locale_1.setLocale)(config.locale || 'zh');
|
|
246
|
+
// Collect files
|
|
247
|
+
const files = opts.staged ? collectStagedFiles(rootDir) : collectFiles(rootDir, opts, config);
|
|
248
|
+
if (files.length === 0) {
|
|
249
|
+
console.log((0, locale_1.t)('index.no_files'));
|
|
250
|
+
return;
|
|
246
251
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
// --watch mode: continuous monitoring
|
|
256
|
-
if (opts.watch) {
|
|
257
|
-
(0, watch_1.watchFiles)(files, { rootDir, format: opts.format === 'html' ? 'default' : opts.format, config });
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
// --fix mode: auto-fix before linting
|
|
261
|
-
if (opts.fix) {
|
|
262
|
-
for (const f of files) {
|
|
263
|
-
const fixes = (0, runner_1.fixFile)(f);
|
|
264
|
-
for (const rule of fixes) {
|
|
265
|
-
console.log(`${(0, locale_1.t)('summary.tag_fail')}: ${path.relative(rootDir, f)} — ${(0, locale_1.t)('index.fixed', rule)}`);
|
|
252
|
+
// --fix mode: auto-fix before linting
|
|
253
|
+
if (opts.fix) {
|
|
254
|
+
for (const f of files) {
|
|
255
|
+
const fixes = (0, runner_1.fixFile)(f);
|
|
256
|
+
for (const rule of fixes) {
|
|
257
|
+
console.log(`${(0, locale_1.t)('summary.tag_fail')}: ${path.relative(rootDir, f)} — ${(0, locale_1.t)('index.fixed', rule)}`);
|
|
258
|
+
}
|
|
266
259
|
}
|
|
260
|
+
if (!opts.staged)
|
|
261
|
+
return;
|
|
267
262
|
}
|
|
268
|
-
|
|
263
|
+
// Filter cached files
|
|
264
|
+
const filesToLint = opts.cache
|
|
265
|
+
? files.filter((f) => {
|
|
266
|
+
const cached = (0, cache_1.isCached)(f, rootDir);
|
|
267
|
+
if (cached) {
|
|
268
|
+
console.log(`${(0, locale_1.t)('summary.tag_warn')}: ${path.relative(rootDir, f)} — ${(0, locale_1.t)('index.cached')}`);
|
|
269
|
+
}
|
|
270
|
+
return !cached;
|
|
271
|
+
})
|
|
272
|
+
: files;
|
|
273
|
+
if (filesToLint.length === 0) {
|
|
274
|
+
console.log((0, locale_1.t)('index.all_cached'));
|
|
269
275
|
return;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
// Build file content map for code line display
|
|
287
|
-
const fileContents = new Map();
|
|
288
|
-
for (const iss of issues) {
|
|
289
|
-
if (!fileContents.has(iss.file)) {
|
|
290
|
-
const fp = path.resolve(rootDir, iss.file);
|
|
291
|
-
try {
|
|
292
|
-
fileContents.set(iss.file, fs.readFileSync(fp, 'utf-8'));
|
|
276
|
+
}
|
|
277
|
+
// Phase 1: Static checks (parallel or sequential)
|
|
278
|
+
const issues = opts.parallel
|
|
279
|
+
? await (0, runner_1.lintFilesParallel)(filesToLint, config, rootDir)
|
|
280
|
+
: (0, runner_1.lintFiles)(filesToLint, config, rootDir);
|
|
281
|
+
// Phase 1b: Project-level cross-file checks
|
|
282
|
+
if (opts.project) {
|
|
283
|
+
const projectIssues = (0, project_1.lintProject)(filesToLint, config, rootDir);
|
|
284
|
+
issues.push(...projectIssues);
|
|
285
|
+
}
|
|
286
|
+
// Mark as cached
|
|
287
|
+
if (opts.cache) {
|
|
288
|
+
for (const f of filesToLint) {
|
|
289
|
+
(0, cache_1.markCached)(f, rootDir);
|
|
293
290
|
}
|
|
294
|
-
catch { /* ignore */ }
|
|
295
291
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
292
|
+
// Phase 2: SBCL syntax validation
|
|
293
|
+
const stubPkgPath = path.join(__dirname, '..', 'stub-packages.json');
|
|
294
|
+
try {
|
|
295
|
+
const sbclIssues = (0, sbcl_1.runSbclLint)(rootDir, config.sbcl, stubPkgPath);
|
|
296
|
+
issues.push(...sbclIssues);
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
300
|
+
issues.push({
|
|
301
|
+
file: 'sbcl',
|
|
302
|
+
line: 1,
|
|
303
|
+
severity: 'error',
|
|
304
|
+
rule: 'sbcl',
|
|
305
|
+
message: msg,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
// Build file content map for code line display
|
|
309
|
+
const fileContents = new Map();
|
|
310
|
+
for (const iss of issues) {
|
|
311
|
+
if (!fileContents.has(iss.file)) {
|
|
312
|
+
const fp = path.resolve(rootDir, iss.file);
|
|
313
|
+
try {
|
|
314
|
+
fileContents.set(iss.file, fs.readFileSync(fp, 'utf-8'));
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
/* ignore */
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// Format & output
|
|
322
|
+
const errorCount = issues.filter((i) => i.severity === 'error').length;
|
|
323
|
+
const warningCount = issues.filter((i) => i.severity === 'warn').length;
|
|
324
|
+
if (opts.format === 'json') {
|
|
325
|
+
console.log((0, formatters_1.formatJson)(issues, errorCount, warningCount));
|
|
305
326
|
}
|
|
306
327
|
else {
|
|
307
|
-
console.log(
|
|
328
|
+
console.log((0, formatters_1.formatDefault)(issues, fileContents));
|
|
308
329
|
}
|
|
330
|
+
process.exit(errorCount > 0 ? 1 : 0);
|
|
309
331
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
console.log((0, formatters_1.formatDefault)(issues, fileContents));
|
|
332
|
+
catch (err) {
|
|
333
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
334
|
+
console.error(`Error: ${msg}`);
|
|
335
|
+
process.exit(1);
|
|
315
336
|
}
|
|
316
|
-
process.exit(errorCount > 0 ? 1 : 0);
|
|
317
337
|
}
|
|
318
338
|
if (require.main === module) {
|
|
319
339
|
main();
|
package/dist/locale.js
CHANGED
|
@@ -9,35 +9,55 @@ const messages = {
|
|
|
9
9
|
'encoding.bom': 'UTF-8 BOM detected',
|
|
10
10
|
'encoding.non_utf8_cad': 'Non-UTF-8 encoding (may cause CAD bracket matching errors)',
|
|
11
11
|
'encoding.non_utf8': 'Non-UTF-8 encoding detected',
|
|
12
|
-
|
|
12
|
+
cl_syntax: "Common Lisp syntax '{0}' (not valid in AutoLISP)",
|
|
13
13
|
'dangerous.quit': 'quit call (terminates CAD process)',
|
|
14
14
|
'dangerous.exit': 'exit call (terminates CAD process)',
|
|
15
15
|
'dangerous.command_shell': 'command shell call (shell injection risk)',
|
|
16
16
|
'dangerous.startapp': 'startapp call (launches external process)',
|
|
17
17
|
'dangerous.vl_registry_write': 'vl-registry-write call (modifies Windows registry)',
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
vlax_without_loading: 'vlax-* call without vl-load-com in file',
|
|
19
|
+
token_in_url: 'Possible token exposure in string: token= in URL',
|
|
20
|
+
open_without_close: 'File has {0} open() calls but {1} close() calls (possible resource leak)',
|
|
21
|
+
bare_function_names: "Unnamespaced function '{0}' (missing @:: or C: prefix)",
|
|
22
|
+
module_registration: 'Module file missing @::*modules* registration (expected in last lines)',
|
|
23
23
|
'namespace_header.missing': 'Missing (in-package {0}) header',
|
|
24
24
|
'namespace_header.wrong': 'in-package target is not {0}',
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
trailing_whitespace: 'Trailing whitespace',
|
|
26
|
+
line_length: 'Line length {0} exceeds {1} characters',
|
|
27
27
|
'function_complexity.lines': "Function '{0}' has {1} lines (max {2})",
|
|
28
28
|
'function_complexity.nesting': "Function '{0}' nesting depth {1} exceeds {2}",
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
parameter_naming: "Parameter '{0}' in '{1}' starts with uppercase (AutoLISP convention: lowercase)",
|
|
30
|
+
unused_variable: "Variable '{0}' is set but never used",
|
|
31
|
+
missing_doc: "Function '{0}' is missing a documentation comment",
|
|
32
|
+
unused_parameter: "Parameter '{0}' in '{1}' is never used",
|
|
33
|
+
constant_condition: "Constant condition '{0}' in {1} — possibly leftover debug code",
|
|
34
|
+
redundant_progn: 'Redundant progn in {0}: only one form inside',
|
|
35
|
+
empty_branch: "Empty branch in '{0}' — no body forms",
|
|
36
|
+
unused_let_binding: "Let binding '{0}' is never used",
|
|
37
|
+
recursive_call: "Function '{0}' calls itself — possible infinite recursion",
|
|
38
|
+
variable_shadow: "Variable '{0}' is set with setq inside a let that binds the same name",
|
|
39
|
+
'redundant_cond.single': 'Single-clause {0} — simplify to (if ...)',
|
|
40
|
+
'redundant_cond.t_last': '{0} with T as last clause condition — use (if ...) else branch instead',
|
|
41
|
+
unused_local_fun: "Local function/variable '{0}' is defined in defun with / but never used",
|
|
42
|
+
multiple_setq: 'Consecutive (setq ...) forms — consider merging into a single (setq ...)',
|
|
43
|
+
redundant_quotes: "Redundant double quote — ''x can be simplified to 'x",
|
|
44
|
+
trailing_paren: "Excess closing parenthesis: {0} extra ')' found",
|
|
45
|
+
empty_comment: 'Empty comment line — contains only a semicolon with no text',
|
|
46
|
+
redundant_setq: "Redundant setq: variable '{0}' is set to itself",
|
|
47
|
+
redundant_nil_else: 'Redundant nil else branch — (if cond x nil) can be (if cond x)',
|
|
48
|
+
single_arg_and_or: 'Single-argument {0} — simplifies to just the argument',
|
|
49
|
+
redundant_let: 'let with no bindings — use (progn ...) instead',
|
|
50
|
+
self_compare: 'Self-comparison {0} is always T or always nil',
|
|
51
|
+
misplaced_else: '(if (not {0}) ...) — swap branches for better readability: (if {0} ... ... reversed)',
|
|
52
|
+
quote_vs_function: 'Quoted lambda passed to {0} — use (function (lambda ...)) instead for compiled code',
|
|
53
|
+
commented_code: 'Commented-out code detected — remove or uncomment if needed',
|
|
54
|
+
double_not: 'Double (not (not ...)) — simplifies to single not',
|
|
55
|
+
dangling_defun: "Function '{0}' is defined but never called",
|
|
56
|
+
missing_export: "Function '{0}' is not registered in @::*modules*",
|
|
57
|
+
unused_package_dep: "Imported package '{0}' is never referenced",
|
|
38
58
|
'runner.read_error': 'Cannot read file',
|
|
39
59
|
'sbcl.script_not_found': 'lint-sbcl.lisp not found at {0}',
|
|
40
|
-
'sbcl.not_found':
|
|
60
|
+
'sbcl.not_found': 'SBCL (Steel Bank Common Lisp) is required but not found.\nInstall it from: https://www.sbcl.org/platform-table.html\nOr on Windows via: winget install sbcl\nOr on macOS via: brew install sbcl\nOr on Linux via: apt install sbcl',
|
|
41
61
|
'summary.tag_fail': 'FAIL',
|
|
42
62
|
'summary.tag_warn': 'WARN',
|
|
43
63
|
'summary.line': 'Line',
|
|
@@ -51,45 +71,64 @@ const messages = {
|
|
|
51
71
|
'index.not_git': 'Not a git repository: no .git/hooks directory',
|
|
52
72
|
'index.hook_installed': 'Installed pre-commit hook at {0}',
|
|
53
73
|
'index.fixed': 'Auto-fixed: {0}',
|
|
54
|
-
'index.
|
|
55
|
-
'index.
|
|
56
|
-
'index.
|
|
57
|
-
'index.config_valid': 'Configuration is valid',
|
|
74
|
+
'index.cached': 'Cached, skipping',
|
|
75
|
+
'index.all_cached': 'All files are cached, nothing to lint',
|
|
76
|
+
'index.cache_cleared': 'Cache cleared',
|
|
58
77
|
},
|
|
59
78
|
zh: {
|
|
60
79
|
'parens.mismatch': "括号不匹配:{0} 个 '(' vs {1} 个 ')'({2})",
|
|
61
80
|
'encoding.bom': '检测到 UTF-8 BOM',
|
|
62
81
|
'encoding.non_utf8_cad': '非 UTF-8 编码(可能导致 CAD 括号匹配错误)',
|
|
63
82
|
'encoding.non_utf8': '检测到非 UTF-8 编码',
|
|
64
|
-
|
|
83
|
+
cl_syntax: "Common Lisp 语法 '{0}'(在 AutoLISP 中无效)",
|
|
65
84
|
'dangerous.quit': 'quit 调用(会终止 CAD 进程)',
|
|
66
85
|
'dangerous.exit': 'exit 调用(会终止 CAD 进程)',
|
|
67
86
|
'dangerous.command_shell': 'command shell 调用(存在命令注入风险)',
|
|
68
87
|
'dangerous.startapp': 'startapp 调用(会启动外部程序)',
|
|
69
88
|
'dangerous.vl_registry_write': 'vl-registry-write 调用(会修改 Windows 注册表)',
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
89
|
+
vlax_without_loading: '文件中使用了 vlax-* 函数但未调用 vl-load-com',
|
|
90
|
+
token_in_url: '字符串中可能泄露令牌:URL 中包含 token=',
|
|
91
|
+
open_without_close: '文件中有 {0} 个 open() 调用,但只有 {1} 个 close() 调用(可能存在资源泄露)',
|
|
92
|
+
bare_function_names: "函数 '{0}' 缺少命名空间前缀(缺少 @:: 或 C: 前缀)",
|
|
93
|
+
module_registration: '模块文件缺少 @::*modules* 注册(应在文件末尾附近)',
|
|
75
94
|
'namespace_header.missing': '缺少 (in-package {0}) 头',
|
|
76
95
|
'namespace_header.wrong': 'in-package 目标不是 {0}',
|
|
77
|
-
|
|
78
|
-
|
|
96
|
+
trailing_whitespace: '行尾有多余空格',
|
|
97
|
+
line_length: '行长度 {0} 超过 {1} 个字符',
|
|
79
98
|
'function_complexity.lines': "函数 '{0}' 有 {1} 行(最大 {2} 行)",
|
|
80
99
|
'function_complexity.nesting': "函数 '{0}' 嵌套深度 {1} 超过 {2}",
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
parameter_naming: "参数 '{0}' 在 '{1}' 中首字母大写(AutoLISP 惯例:小写)",
|
|
101
|
+
unused_variable: "变量 '{0}' 被赋值但从未使用",
|
|
102
|
+
missing_doc: "函数 '{0}' 缺少注释说明",
|
|
103
|
+
unused_parameter: "参数 '{0}' 在函数 '{1}' 中从未使用",
|
|
104
|
+
constant_condition: "常量条件 '{0}' 在 {1} 中——可能是调试残留代码",
|
|
105
|
+
redundant_progn: '冗余 progn:{0} 内只有一个表达式',
|
|
106
|
+
empty_branch: "'{0}' 的条件分支为空——没有表达式",
|
|
107
|
+
unused_let_binding: "let 绑定 '{0}' 从未被使用",
|
|
108
|
+
recursive_call: "函数 '{0}' 调用了自身——可能存在无限递归",
|
|
109
|
+
variable_shadow: "变量 '{0}' 在 let 绑定了相同名称后被 setq 赋值",
|
|
110
|
+
'redundant_cond.single': '单子句 {0} ——建议简化为 (if ...)',
|
|
111
|
+
'redundant_cond.t_last': '{0} 的最后一条子句条件为 T ——建议改用 (if ...) else 分支',
|
|
112
|
+
unused_local_fun: "局部函数/变量 '{0}' 在 defun 的 / 后定义但从未使用",
|
|
113
|
+
multiple_setq: '连续多个 (setq ...) ——建议合并为一个 (setq ...)',
|
|
114
|
+
redundant_quotes: "冗余双引号 —— ''x 可以简化为 'x",
|
|
115
|
+
trailing_paren: "多余闭合括号:发现 {0} 个额外的 ')'",
|
|
116
|
+
empty_comment: '空注释行 —— 分号后没有内容',
|
|
117
|
+
redundant_setq: "冗余 setq:变量 '{0}' 赋值为自身",
|
|
118
|
+
redundant_nil_else: '冗余 nil 分支 —— (if cond x nil) 可简化为 (if cond x)',
|
|
119
|
+
single_arg_and_or: '单参数 {0} —— 可直接简化为该参数本身',
|
|
120
|
+
redundant_let: '无绑定的 let —— 建议改用 (progn ...)',
|
|
121
|
+
self_compare: '自比较 {0} 的结果恒为 T 或恒为 nil',
|
|
122
|
+
misplaced_else: '(if (not {0}) ...) —— 建议交换分支以提高可读性: (if {0} ... ... 交换后)',
|
|
123
|
+
quote_vs_function: '在 {0} 中使用引用 lambda —— 建议改用 (function (lambda ...)) 以支持编译优化',
|
|
124
|
+
commented_code: '检测到被注释掉的代码 —— 请删除或取消注释',
|
|
125
|
+
double_not: '双重 (not (not ...)) —— 可以简化为单个 not',
|
|
126
|
+
dangling_defun: "函数 '{0}' 定义了但从未被调用",
|
|
127
|
+
missing_export: "函数 '{0}' 未注册到 @::*modules*",
|
|
128
|
+
unused_package_dep: "导入的包 '{0}' 从未被引用",
|
|
90
129
|
'runner.read_error': '无法读取文件',
|
|
91
130
|
'sbcl.script_not_found': '未找到 lint-sbcl.lisp:{0}',
|
|
92
|
-
'sbcl.not_found':
|
|
131
|
+
'sbcl.not_found': '未找到 SBCL(Steel Bank Common Lisp)。\n请从以下地址安装:https://www.sbcl.org/platform-table.html\nWindows 下可用:winget install sbcl\nmacOS 下可用:brew install sbcl\nLinux 下可用:apt install sbcl',
|
|
93
132
|
'summary.tag_fail': '失败',
|
|
94
133
|
'summary.tag_warn': '警告',
|
|
95
134
|
'summary.line': '行',
|
|
@@ -103,10 +142,9 @@ const messages = {
|
|
|
103
142
|
'index.not_git': '不是 git 仓库:没有 .git/hooks 目录',
|
|
104
143
|
'index.hook_installed': '已在 {0} 安装 pre-commit hook',
|
|
105
144
|
'index.fixed': '自动修复:{0}',
|
|
106
|
-
'index.
|
|
107
|
-
'index.
|
|
108
|
-
'index.
|
|
109
|
-
'index.config_valid': '配置文件校验通过',
|
|
145
|
+
'index.cached': '已缓存,跳过',
|
|
146
|
+
'index.all_cached': '所有文件均已缓存,无需检查',
|
|
147
|
+
'index.cache_cleared': '缓存已清除',
|
|
110
148
|
},
|
|
111
149
|
};
|
|
112
150
|
let currentLocale = 'zh';
|
|
@@ -117,9 +155,7 @@ function getLocale() {
|
|
|
117
155
|
return currentLocale;
|
|
118
156
|
}
|
|
119
157
|
function t(key, ...args) {
|
|
120
|
-
const msg = messages[currentLocale]?.[key] ||
|
|
121
|
-
messages['en']?.[key] ||
|
|
122
|
-
key;
|
|
158
|
+
const msg = messages[currentLocale]?.[key] || messages['en']?.[key] || key;
|
|
123
159
|
return msg.replace(/\{(\d+)\}/g, (_, index) => String(args[Number(index)] ?? ''));
|
|
124
160
|
}
|
|
125
161
|
//# sourceMappingURL=locale.js.map
|
package/dist/project.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.lintProject = lintProject;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const parser_1 = require("@atlisp/parser");
|
|
40
|
+
const dangling_defun_1 = require("./checks/dangling-defun");
|
|
41
|
+
const missing_export_1 = require("./checks/missing-export");
|
|
42
|
+
const unused_package_dep_1 = require("./checks/unused-package-dep");
|
|
43
|
+
const locale_1 = require("./locale");
|
|
44
|
+
function collectDefuns(file) {
|
|
45
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
46
|
+
const ast = (0, parser_1.parseAst)(content);
|
|
47
|
+
const results = [];
|
|
48
|
+
const defunNodes = (0, parser_1.astFindAll)(ast, (n) => (0, parser_1.astIsList)(n, 'defun') || (0, parser_1.astIsList)(n, 'defun-q'));
|
|
49
|
+
for (const node of defunNodes) {
|
|
50
|
+
if (node.children && node.children.length >= 2 && (0, parser_1.astIsSymbol)(node.children[1])) {
|
|
51
|
+
results.push({ name: node.children[1].name, line: node.pos.line });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return results;
|
|
55
|
+
}
|
|
56
|
+
function lintProject(files, config, rootDir) {
|
|
57
|
+
(0, locale_1.setLocale)(config.locale || 'zh');
|
|
58
|
+
const allIssues = [];
|
|
59
|
+
const defuns = { defuns: new Map() };
|
|
60
|
+
const fileContents = new Map();
|
|
61
|
+
for (const filepath of files) {
|
|
62
|
+
try {
|
|
63
|
+
const content = fs.readFileSync(filepath, 'utf-8');
|
|
64
|
+
fileContents.set(filepath, content);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const [filepath] of fileContents) {
|
|
71
|
+
const defunList = collectDefuns(filepath);
|
|
72
|
+
for (const d of defunList) {
|
|
73
|
+
const list = defuns.defuns.get(d.name) || [];
|
|
74
|
+
list.push({ file: filepath, line: d.line });
|
|
75
|
+
defuns.defuns.set(d.name, list);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const [filepath] of fileContents) {
|
|
79
|
+
const relPath = path.relative(rootDir, filepath);
|
|
80
|
+
const override = findProjectOverride(filepath);
|
|
81
|
+
const checks = override?.checks || config.checks;
|
|
82
|
+
if (checks['dangling_defun'] !== 'off') {
|
|
83
|
+
const danglingIssues = (0, dangling_defun_1.checkDanglingDefun)(filepath, defuns.defuns);
|
|
84
|
+
for (const iss of danglingIssues) {
|
|
85
|
+
iss.file = relPath;
|
|
86
|
+
allIssues.push(iss);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (checks['missing_export'] !== 'off') {
|
|
90
|
+
const pkgDir = findPackageDir(filepath);
|
|
91
|
+
if (pkgDir) {
|
|
92
|
+
const missingIssues = (0, missing_export_1.checkMissingExport)(filepath, pkgDir, defuns.defuns);
|
|
93
|
+
for (const iss of missingIssues) {
|
|
94
|
+
iss.file = relPath;
|
|
95
|
+
allIssues.push(iss);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (checks['unused_package_dep'] !== 'off') {
|
|
100
|
+
const depIssues = (0, unused_package_dep_1.checkUnusedPackageDep)(filepath);
|
|
101
|
+
for (const iss of depIssues) {
|
|
102
|
+
iss.file = relPath;
|
|
103
|
+
allIssues.push(iss);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return allIssues;
|
|
108
|
+
}
|
|
109
|
+
function findProjectOverride(filepath) {
|
|
110
|
+
let dir = path.dirname(filepath);
|
|
111
|
+
for (;;) {
|
|
112
|
+
const cfgPath = path.join(dir, '.atlisp-lint.json');
|
|
113
|
+
if (fs.existsSync(cfgPath)) {
|
|
114
|
+
try {
|
|
115
|
+
return JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const parent = path.dirname(dir);
|
|
122
|
+
if (parent === dir)
|
|
123
|
+
return null;
|
|
124
|
+
dir = parent;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function findPackageDir(filepath) {
|
|
128
|
+
let dir = path.dirname(filepath);
|
|
129
|
+
for (;;) {
|
|
130
|
+
if (fs.existsSync(path.join(dir, 'pkg.lsp')))
|
|
131
|
+
return dir;
|
|
132
|
+
const parent = path.dirname(dir);
|
|
133
|
+
if (parent === dir)
|
|
134
|
+
return null;
|
|
135
|
+
dir = parent;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=project.js.map
|