unguard 0.15.1 → 0.15.2
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 +22 -1
- package/dist/{chunk-VHRD75ET.js → chunk-BLCVXVLX.js} +157 -242
- package/dist/chunk-BLCVXVLX.js.map +1 -0
- package/dist/chunk-WXUUG77Y.js +396 -0
- package/dist/chunk-WXUUG77Y.js.map +1 -0
- package/dist/cli.js +43 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -3
- package/dist/worker.js +44 -0
- package/dist/worker.js.map +1 -0
- package/package.json +1 -1
- package/schema.json +9 -0
- package/dist/chunk-VHRD75ET.js.map +0 -1
package/README.md
CHANGED
|
@@ -36,6 +36,8 @@ unguard src --severity=error,warning # show errors+warnings
|
|
|
36
36
|
unguard src --fail-on=error # fail only on errors
|
|
37
37
|
unguard src --format=flat # one-line-per-diagnostic, grepable
|
|
38
38
|
unguard src --format=flat | grep error
|
|
39
|
+
unguard src --concurrency 1 # disable worker-thread parallelism
|
|
40
|
+
unguard src --no-cache # bypass on-disk diagnostic cache
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
Add `unguard` to your lint check, especially if code is written by AI.
|
|
@@ -56,12 +58,15 @@ Add `unguard` to your lint check, especially if code is written by AI.
|
|
|
56
58
|
"no-ts-ignore": "error",
|
|
57
59
|
"prefer-*": "off"
|
|
58
60
|
},
|
|
59
|
-
"failOn": "error"
|
|
61
|
+
"failOn": "error",
|
|
62
|
+
"concurrency": 4,
|
|
63
|
+
"cache": true
|
|
60
64
|
}
|
|
61
65
|
```
|
|
62
66
|
|
|
63
67
|
`rules` values can be `off`, `info`, `warning`, or `error`.
|
|
64
68
|
Selectors support:
|
|
69
|
+
|
|
65
70
|
- exact rule id: `no-ts-ignore`
|
|
66
71
|
- wildcard: `duplicate-*`
|
|
67
72
|
- category: `category:cross-file`
|
|
@@ -70,6 +75,7 @@ Selectors support:
|
|
|
70
75
|
### Ignore behavior
|
|
71
76
|
|
|
72
77
|
`unguard` ignores:
|
|
78
|
+
|
|
73
79
|
- built-in: `node_modules`, `dist`, `.git`, declaration files (`*.d.ts`, `*.d.cts`, `*.d.mts`)
|
|
74
80
|
- generated files: `*.gen.*`, `*.generated.*`
|
|
75
81
|
- project `.gitignore`
|
|
@@ -232,6 +238,21 @@ for (const d of execution.visibleDiagnostics) {
|
|
|
232
238
|
}
|
|
233
239
|
```
|
|
234
240
|
|
|
241
|
+
### Caching
|
|
242
|
+
|
|
243
|
+
unguard caches scan results under `node_modules/.cache/unguard/`. On a warm
|
|
244
|
+
run, if every file's content hash and the active rule set are unchanged,
|
|
245
|
+
unguard returns cached diagnostics without building a TypeScript program.
|
|
246
|
+
The cache invalidates automatically on:
|
|
247
|
+
|
|
248
|
+
- file content changes (mtime-only changes are ignored — `git checkout` and `git stash` stay cache hits)
|
|
249
|
+
- changes to active rules or rule severities
|
|
250
|
+
- changes to scan paths, ignore globs, or `failOn`
|
|
251
|
+
- unguard version upgrades
|
|
252
|
+
|
|
253
|
+
Disable with `--no-cache` (CLI), `cache: false` (config), or pass
|
|
254
|
+
`cache: false` to `executeScan({ cache: false })` programmatically.
|
|
255
|
+
|
|
235
256
|
## License
|
|
236
257
|
|
|
237
258
|
MIT
|
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
// src/rules/types.ts
|
|
2
|
+
function isTSRule(r) {
|
|
3
|
+
return "kind" in r && r.kind === "ts";
|
|
4
|
+
}
|
|
5
|
+
function reportDuplicateGroup(group, ruleId, severity, formatOther, formatMessage, diagnostics, context) {
|
|
6
|
+
const sorted = [...group].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
7
|
+
const entries = selectDuplicateReportEntries(sorted, context.reportableFiles);
|
|
8
|
+
for (const entry of entries) {
|
|
9
|
+
const others = sorted.filter((e) => e !== entry).map(formatOther).join(", ");
|
|
10
|
+
diagnostics.push({
|
|
11
|
+
ruleId,
|
|
12
|
+
severity,
|
|
13
|
+
message: formatMessage(entry, others),
|
|
14
|
+
file: entry.file,
|
|
15
|
+
line: entry.line,
|
|
16
|
+
column: 1
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function selectReportTarget(group, reportableFiles) {
|
|
21
|
+
const sorted = [...group].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
22
|
+
if (reportableFiles === void 0) return sorted[0];
|
|
23
|
+
return sorted.find((entry) => reportableFiles.has(entry.file));
|
|
24
|
+
}
|
|
25
|
+
function selectDuplicateReportEntries(sorted, reportableFiles) {
|
|
26
|
+
const defaultEntries = sorted.slice(1);
|
|
27
|
+
if (reportableFiles === void 0) return defaultEntries;
|
|
28
|
+
const reportableDefaultEntries = defaultEntries.filter((entry) => reportableFiles.has(entry.file));
|
|
29
|
+
if (reportableDefaultEntries.length > 0) return reportableDefaultEntries;
|
|
30
|
+
const first = sorted[0];
|
|
31
|
+
if (first !== void 0 && sorted.length > 1 && reportableFiles.has(first.file)) return [first];
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
|
|
1
35
|
// src/rules/cross-file/dead-overload.ts
|
|
2
36
|
import * as ts from "typescript";
|
|
3
37
|
var deadOverload = {
|
|
@@ -170,40 +204,6 @@ function normalizeText(text) {
|
|
|
170
204
|
return text.replace(/\s+/g, "");
|
|
171
205
|
}
|
|
172
206
|
|
|
173
|
-
// src/rules/types.ts
|
|
174
|
-
function isTSRule(r) {
|
|
175
|
-
return "kind" in r && r.kind === "ts";
|
|
176
|
-
}
|
|
177
|
-
function reportDuplicateGroup(group, ruleId, severity, formatOther, formatMessage, diagnostics, context) {
|
|
178
|
-
const sorted = [...group].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
179
|
-
const entries = selectDuplicateReportEntries(sorted, context.reportableFiles);
|
|
180
|
-
for (const entry of entries) {
|
|
181
|
-
const others = sorted.filter((e) => e !== entry).map(formatOther).join(", ");
|
|
182
|
-
diagnostics.push({
|
|
183
|
-
ruleId,
|
|
184
|
-
severity,
|
|
185
|
-
message: formatMessage(entry, others),
|
|
186
|
-
file: entry.file,
|
|
187
|
-
line: entry.line,
|
|
188
|
-
column: 1
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
function selectReportTarget(group, reportableFiles) {
|
|
193
|
-
const sorted = [...group].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
194
|
-
if (reportableFiles === void 0) return sorted[0];
|
|
195
|
-
return sorted.find((entry) => reportableFiles.has(entry.file));
|
|
196
|
-
}
|
|
197
|
-
function selectDuplicateReportEntries(sorted, reportableFiles) {
|
|
198
|
-
const defaultEntries = sorted.slice(1);
|
|
199
|
-
if (reportableFiles === void 0) return defaultEntries;
|
|
200
|
-
const reportableDefaultEntries = defaultEntries.filter((entry) => reportableFiles.has(entry.file));
|
|
201
|
-
if (reportableDefaultEntries.length > 0) return reportableDefaultEntries;
|
|
202
|
-
const first = sorted[0];
|
|
203
|
-
if (first !== void 0 && sorted.length > 1 && reportableFiles.has(first.file)) return [first];
|
|
204
|
-
return [];
|
|
205
|
-
}
|
|
206
|
-
|
|
207
207
|
// src/rules/cross-file/duplicate-constant-declaration.ts
|
|
208
208
|
var duplicateConstantDeclaration = {
|
|
209
209
|
id: "duplicate-constant-declaration",
|
|
@@ -2389,57 +2389,6 @@ function getRuleMetadata(ruleId) {
|
|
|
2389
2389
|
return { category: "cross-file", tags: [] };
|
|
2390
2390
|
}
|
|
2391
2391
|
|
|
2392
|
-
// src/scan/config.ts
|
|
2393
|
-
var BUILTIN_IGNORE = [
|
|
2394
|
-
"**/node_modules/**",
|
|
2395
|
-
"**/dist/**",
|
|
2396
|
-
"**/.git/**",
|
|
2397
|
-
"**/*.d.ts",
|
|
2398
|
-
"**/*.d.cts",
|
|
2399
|
-
"**/*.d.mts"
|
|
2400
|
-
];
|
|
2401
|
-
var GENERATED_IGNORE = [
|
|
2402
|
-
"**/*.gen.*",
|
|
2403
|
-
"**/*.generated.*"
|
|
2404
|
-
];
|
|
2405
|
-
var DEFAULT_FAIL_ON = "info";
|
|
2406
|
-
function resolveScanConfig(options) {
|
|
2407
|
-
const paths = options.paths.length > 0 ? options.paths : ["."];
|
|
2408
|
-
const ignore2 = [...BUILTIN_IGNORE, ...GENERATED_IGNORE, ...options.ignore ?? []];
|
|
2409
|
-
return {
|
|
2410
|
-
paths,
|
|
2411
|
-
strict: options.strict ?? false,
|
|
2412
|
-
rules: options.rules ? [...options.rules] : null,
|
|
2413
|
-
ignore: ignore2,
|
|
2414
|
-
rulePolicy: toRulePolicyEntries(options.rulePolicy),
|
|
2415
|
-
showSeverities: normalizeSeveritySet(options.showSeverities),
|
|
2416
|
-
failOn: options.failOn ?? DEFAULT_FAIL_ON,
|
|
2417
|
-
useGitIgnore: options.useGitIgnore ?? true
|
|
2418
|
-
};
|
|
2419
|
-
}
|
|
2420
|
-
function toRulePolicyEntries(policy) {
|
|
2421
|
-
if (policy === void 0) return [];
|
|
2422
|
-
if (Array.isArray(policy)) return [...policy];
|
|
2423
|
-
const entries = [];
|
|
2424
|
-
for (const [selector, severity] of Object.entries(policy)) {
|
|
2425
|
-
entries.push({ selector, severity });
|
|
2426
|
-
}
|
|
2427
|
-
return entries;
|
|
2428
|
-
}
|
|
2429
|
-
function normalizeSeveritySet(levels) {
|
|
2430
|
-
if (levels === void 0 || levels.length === 0) return null;
|
|
2431
|
-
return new Set(levels);
|
|
2432
|
-
}
|
|
2433
|
-
function isRulePolicySeverity(value) {
|
|
2434
|
-
return value === "off" || value === "info" || value === "warning" || value === "error";
|
|
2435
|
-
}
|
|
2436
|
-
function isSeverity(value) {
|
|
2437
|
-
return value === "info" || value === "warning" || value === "error";
|
|
2438
|
-
}
|
|
2439
|
-
function isFailOn(value) {
|
|
2440
|
-
return value === "none" || isSeverity(value);
|
|
2441
|
-
}
|
|
2442
|
-
|
|
2443
2392
|
// src/scan/analyze.ts
|
|
2444
2393
|
import { readFileSync } from "fs";
|
|
2445
2394
|
|
|
@@ -3484,9 +3433,8 @@ function createCachedCompilerHost(options, cache) {
|
|
|
3484
3433
|
};
|
|
3485
3434
|
return host;
|
|
3486
3435
|
}
|
|
3487
|
-
function isStableCachedSourceFile(
|
|
3488
|
-
|
|
3489
|
-
return normalized.includes("/node_modules/") || /\.d\.[cm]?ts$/.test(normalized) || normalized.endsWith(".json");
|
|
3436
|
+
function isStableCachedSourceFile(_fileName) {
|
|
3437
|
+
return true;
|
|
3490
3438
|
}
|
|
3491
3439
|
function sourceFileCacheKey(fileKey, languageVersionOrOptions) {
|
|
3492
3440
|
if (typeof languageVersionOrOptions !== "object") {
|
|
@@ -3566,8 +3514,80 @@ function createProgramForGroup(group, options) {
|
|
|
3566
3514
|
return createProgramForConfig(rootFiles, group.configPath, options.cache);
|
|
3567
3515
|
}
|
|
3568
3516
|
|
|
3517
|
+
// src/scan/worker-pool.ts
|
|
3518
|
+
import { existsSync as existsSync2 } from "fs";
|
|
3519
|
+
import { Worker } from "worker_threads";
|
|
3520
|
+
import { fileURLToPath } from "url";
|
|
3521
|
+
var WORKER_URL = new URL("./worker.js", import.meta.url);
|
|
3522
|
+
function workersAvailable() {
|
|
3523
|
+
return existsSync2(fileURLToPath(WORKER_URL));
|
|
3524
|
+
}
|
|
3525
|
+
async function runGroupsInWorkers(tasks, ruleSpecs, indexNeeds, concurrency) {
|
|
3526
|
+
if (tasks.length === 0) return [];
|
|
3527
|
+
const ordered = [...tasks].sort((a, b) => b.groupConfig.scanFiles.length - a.groupConfig.scanFiles.length);
|
|
3528
|
+
const workerCount = Math.min(concurrency, ordered.length);
|
|
3529
|
+
const workers = [];
|
|
3530
|
+
const results = new Array(tasks.length);
|
|
3531
|
+
for (let i = 0; i < tasks.length; i++) results[i] = [];
|
|
3532
|
+
let nextIndex = 0;
|
|
3533
|
+
let remaining = ordered.length;
|
|
3534
|
+
let firstError = null;
|
|
3535
|
+
await new Promise((resolve2, reject) => {
|
|
3536
|
+
function finishOne() {
|
|
3537
|
+
remaining--;
|
|
3538
|
+
if (remaining === 0) {
|
|
3539
|
+
if (firstError !== null) reject(firstError);
|
|
3540
|
+
else resolve2();
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
function dispatchNext(worker) {
|
|
3544
|
+
if (nextIndex >= ordered.length) return;
|
|
3545
|
+
const task = ordered[nextIndex];
|
|
3546
|
+
if (task === void 0) return;
|
|
3547
|
+
nextIndex++;
|
|
3548
|
+
const req = {
|
|
3549
|
+
taskId: task.id,
|
|
3550
|
+
groupConfig: task.groupConfig,
|
|
3551
|
+
ruleSpecs,
|
|
3552
|
+
indexNeeds
|
|
3553
|
+
};
|
|
3554
|
+
worker.postMessage(req);
|
|
3555
|
+
}
|
|
3556
|
+
for (let i = 0; i < workerCount; i++) {
|
|
3557
|
+
const worker = new Worker(fileURLToPath(WORKER_URL));
|
|
3558
|
+
workers.push(worker);
|
|
3559
|
+
worker.on("message", (response) => {
|
|
3560
|
+
if (response.ok) {
|
|
3561
|
+
results[response.taskId] = response.diagnostics;
|
|
3562
|
+
} else if (firstError === null) {
|
|
3563
|
+
firstError = new Error(`unguard worker error: ${response.error}`);
|
|
3564
|
+
}
|
|
3565
|
+
finishOne();
|
|
3566
|
+
dispatchNext(worker);
|
|
3567
|
+
});
|
|
3568
|
+
worker.on("error", (err) => {
|
|
3569
|
+
if (firstError === null) {
|
|
3570
|
+
firstError = err instanceof Error ? err : new Error(String(err));
|
|
3571
|
+
}
|
|
3572
|
+
finishOne();
|
|
3573
|
+
});
|
|
3574
|
+
worker.on("exit", (code) => {
|
|
3575
|
+
if (remaining > 0 && code !== 0 && firstError === null) {
|
|
3576
|
+
firstError = new Error(`unguard worker exited with code ${code}`);
|
|
3577
|
+
finishOne();
|
|
3578
|
+
}
|
|
3579
|
+
});
|
|
3580
|
+
dispatchNext(worker);
|
|
3581
|
+
}
|
|
3582
|
+
}).finally(() => {
|
|
3583
|
+
for (const w of workers) w.terminate().catch(() => {
|
|
3584
|
+
});
|
|
3585
|
+
});
|
|
3586
|
+
return results;
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3569
3589
|
// src/scan/analyze.ts
|
|
3570
|
-
function analyzeFiles(files, rules) {
|
|
3590
|
+
async function analyzeFiles(files, rules, options) {
|
|
3571
3591
|
const tsRules = rules.filter(isTSRule);
|
|
3572
3592
|
const crossFileRules = rules.filter((r) => !isTSRule(r));
|
|
3573
3593
|
const indexNeeds = collectIndexNeeds(crossFileRules);
|
|
@@ -3576,24 +3596,46 @@ function analyzeFiles(files, rules) {
|
|
|
3576
3596
|
}
|
|
3577
3597
|
const groupConfigs = mergeCompatibleGroups(groupFilesByTsconfig(files));
|
|
3578
3598
|
if (groupConfigs.length === 0) return [];
|
|
3579
|
-
const
|
|
3580
|
-
|
|
3599
|
+
const concurrency = resolveConcurrency(options.concurrency, groupConfigs.length);
|
|
3600
|
+
if (concurrency > 1 && groupConfigs.length > 1 && workersAvailable()) {
|
|
3601
|
+
return await runGroupsViaWorkers(groupConfigs, rules, indexNeeds, concurrency);
|
|
3602
|
+
}
|
|
3603
|
+
return runGroupsSerial(groupConfigs, tsRules, crossFileRules, indexNeeds);
|
|
3604
|
+
}
|
|
3605
|
+
function runGroupsSerial(groupConfigs, tsRules, crossFileRules, indexNeeds) {
|
|
3581
3606
|
const programCache = createProgramBuildCache();
|
|
3607
|
+
const allDiagnostics = [];
|
|
3582
3608
|
for (const groupConfig of groupConfigs) {
|
|
3583
|
-
const
|
|
3584
|
-
|
|
3585
|
-
const allowed = new Set(groupConfig.scanFiles);
|
|
3586
|
-
const collectFiles = indexNeeds.size > 0 ? new Set(expandProjectFiles(groupConfig).filter(isAnalyzableSourcePath)) : /* @__PURE__ */ new Set();
|
|
3587
|
-
const { diagnostics } = collectProject(program, tsRules, allowed, {
|
|
3588
|
-
collectFiles,
|
|
3589
|
-
needs: indexNeeds,
|
|
3590
|
-
index: projectIndex
|
|
3591
|
-
});
|
|
3592
|
-
allDiagnostics.push(...diagnostics);
|
|
3593
|
-
allDiagnostics.push(...runCrossFileRules(crossFileRules, projectIndex, allowed));
|
|
3594
|
-
addFileData(allFiles, projectIndex, allowed);
|
|
3609
|
+
const groupDiags = analyzeGroup(groupConfig, tsRules, crossFileRules, indexNeeds, programCache);
|
|
3610
|
+
allDiagnostics.push(...groupDiags);
|
|
3595
3611
|
}
|
|
3596
|
-
return
|
|
3612
|
+
return dedupeDiagnostics(allDiagnostics);
|
|
3613
|
+
}
|
|
3614
|
+
async function runGroupsViaWorkers(groupConfigs, rules, indexNeeds, concurrency) {
|
|
3615
|
+
const ruleSpecs = rules.map((rule) => ({ id: rule.id, severity: rule.severity }));
|
|
3616
|
+
const tasks = groupConfigs.map((groupConfig, idx) => ({ id: idx, groupConfig }));
|
|
3617
|
+
const diagnosticsByTask = await runGroupsInWorkers(tasks, ruleSpecs, [...indexNeeds], concurrency);
|
|
3618
|
+
const allDiagnostics = [];
|
|
3619
|
+
for (const diags of diagnosticsByTask) {
|
|
3620
|
+
allDiagnostics.push(...diags);
|
|
3621
|
+
}
|
|
3622
|
+
return dedupeDiagnostics(allDiagnostics);
|
|
3623
|
+
}
|
|
3624
|
+
function analyzeGroup(groupConfig, tsRules, crossFileRules, indexNeeds, programCache) {
|
|
3625
|
+
const projectIndex = createProjectIndex();
|
|
3626
|
+
const program = createProgramForGroup(groupConfig, { expandProjectFiles: true, cache: programCache });
|
|
3627
|
+
const allowed = new Set(groupConfig.scanFiles);
|
|
3628
|
+
const collectFiles = indexNeeds.size > 0 ? new Set(expandProjectFiles(groupConfig).filter(isAnalyzableSourcePath)) : /* @__PURE__ */ new Set();
|
|
3629
|
+
const { diagnostics } = collectProject(program, tsRules, allowed, {
|
|
3630
|
+
collectFiles,
|
|
3631
|
+
needs: indexNeeds,
|
|
3632
|
+
index: projectIndex
|
|
3633
|
+
});
|
|
3634
|
+
const groupDiagnostics = [...diagnostics];
|
|
3635
|
+
groupDiagnostics.push(...runCrossFileRules(crossFileRules, projectIndex, allowed));
|
|
3636
|
+
const fileData = /* @__PURE__ */ new Map();
|
|
3637
|
+
addFileData(fileData, projectIndex, allowed);
|
|
3638
|
+
return finalizeDiagnostics(groupDiagnostics, fileData);
|
|
3597
3639
|
}
|
|
3598
3640
|
function analyzeSourceOnlyFiles(files, tsRules, crossFileRules, indexNeeds) {
|
|
3599
3641
|
const diagnostics = [];
|
|
@@ -3685,6 +3727,13 @@ function finalizeDiagnostics(diagnostics, files) {
|
|
|
3685
3727
|
}
|
|
3686
3728
|
return dedupeDiagnostics(finalized);
|
|
3687
3729
|
}
|
|
3730
|
+
function resolveConcurrency(requested, groupCount) {
|
|
3731
|
+
if (requested !== void 0) {
|
|
3732
|
+
if (!Number.isInteger(requested) || requested < 1) return 1;
|
|
3733
|
+
return Math.min(requested, groupCount);
|
|
3734
|
+
}
|
|
3735
|
+
return 1;
|
|
3736
|
+
}
|
|
3688
3737
|
function annotateAndFilter(diagnostics, comments, source) {
|
|
3689
3738
|
if (diagnostics.length === 0) return diagnostics;
|
|
3690
3739
|
if (comments.length === 0) return diagnostics;
|
|
@@ -3791,145 +3840,11 @@ function lineAt(source, offset) {
|
|
|
3791
3840
|
return line;
|
|
3792
3841
|
}
|
|
3793
3842
|
|
|
3794
|
-
// src/scan/discover.ts
|
|
3795
|
-
import fg from "fast-glob";
|
|
3796
|
-
import ignore from "ignore";
|
|
3797
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
3798
|
-
import { relative, resolve as resolve2, sep } from "path";
|
|
3799
|
-
async function discoverFiles(config) {
|
|
3800
|
-
const globs = expandGlobs(config.paths);
|
|
3801
|
-
const discoveredFiles = await fg(globs, {
|
|
3802
|
-
ignore: config.ignore,
|
|
3803
|
-
absolute: true
|
|
3804
|
-
});
|
|
3805
|
-
if (!config.useGitIgnore) return discoveredFiles;
|
|
3806
|
-
return applyGitIgnore(discoveredFiles);
|
|
3807
|
-
}
|
|
3808
|
-
function expandGlobs(paths) {
|
|
3809
|
-
return paths.map((p) => {
|
|
3810
|
-
if (p === ".") return "./**/*.{ts,cts,mts,tsx}";
|
|
3811
|
-
if (p.endsWith("/")) return `${p}**/*.{ts,cts,mts,tsx}`;
|
|
3812
|
-
if (!p.includes("*") && !p.endsWith(".ts") && !p.endsWith(".tsx") && !p.endsWith(".cts") && !p.endsWith(".mts")) {
|
|
3813
|
-
return `${p}/**/*.{ts,cts,mts,tsx}`;
|
|
3814
|
-
}
|
|
3815
|
-
return p;
|
|
3816
|
-
});
|
|
3817
|
-
}
|
|
3818
|
-
function applyGitIgnore(files) {
|
|
3819
|
-
const gitIgnorePath = resolve2(process.cwd(), ".gitignore");
|
|
3820
|
-
if (!existsSync2(gitIgnorePath)) return files;
|
|
3821
|
-
const matcher = ignore().add(readFileSync2(gitIgnorePath, "utf8"));
|
|
3822
|
-
return files.filter((file) => {
|
|
3823
|
-
const rel = relative(process.cwd(), file);
|
|
3824
|
-
if (rel.startsWith("..")) return true;
|
|
3825
|
-
const normalized = rel.split(sep).join("/");
|
|
3826
|
-
return !matcher.ignores(normalized);
|
|
3827
|
-
});
|
|
3828
|
-
}
|
|
3829
|
-
|
|
3830
|
-
// src/scan/policy.ts
|
|
3831
|
-
function buildRuleDescriptors(rules) {
|
|
3832
|
-
return rules.map((rule) => {
|
|
3833
|
-
const metadata = getRuleMetadata(rule.id);
|
|
3834
|
-
return {
|
|
3835
|
-
rule,
|
|
3836
|
-
category: metadata.category,
|
|
3837
|
-
tags: metadata.tags
|
|
3838
|
-
};
|
|
3839
|
-
});
|
|
3840
|
-
}
|
|
3841
|
-
function resolveActiveRules(descriptors, config) {
|
|
3842
|
-
const selectedRules = config.rules ? new Set(config.rules) : null;
|
|
3843
|
-
const active = [];
|
|
3844
|
-
for (const descriptor of descriptors) {
|
|
3845
|
-
if (selectedRules && !selectedRules.has(descriptor.rule.id)) continue;
|
|
3846
|
-
const resolvedSeverity = resolveRuleSeverity(descriptor, config);
|
|
3847
|
-
if (resolvedSeverity === "off") continue;
|
|
3848
|
-
if (descriptor.rule.severity === resolvedSeverity) {
|
|
3849
|
-
active.push(descriptor.rule);
|
|
3850
|
-
continue;
|
|
3851
|
-
}
|
|
3852
|
-
active.push({ ...descriptor.rule, severity: resolvedSeverity });
|
|
3853
|
-
}
|
|
3854
|
-
return active;
|
|
3855
|
-
}
|
|
3856
|
-
function resolveRuleSeverity(descriptor, config) {
|
|
3857
|
-
let severity = descriptor.rule.severity;
|
|
3858
|
-
for (const entry of config.rulePolicy) {
|
|
3859
|
-
if (!matchesSelector(descriptor, entry.selector)) continue;
|
|
3860
|
-
severity = entry.severity;
|
|
3861
|
-
}
|
|
3862
|
-
if (severity === "off") return "off";
|
|
3863
|
-
if (config.strict) return "error";
|
|
3864
|
-
return severity;
|
|
3865
|
-
}
|
|
3866
|
-
function matchesSelector(descriptor, selector) {
|
|
3867
|
-
if (selector.startsWith("category:")) {
|
|
3868
|
-
return descriptor.category === selector.slice("category:".length);
|
|
3869
|
-
}
|
|
3870
|
-
if (selector.startsWith("tag:")) {
|
|
3871
|
-
return descriptor.tags.includes(selector.slice("tag:".length));
|
|
3872
|
-
}
|
|
3873
|
-
if (selector === descriptor.rule.id) return true;
|
|
3874
|
-
if (!selector.includes("*")) return false;
|
|
3875
|
-
const regex = new RegExp(`^${escapeRegex(selector).replaceAll("\\*", ".*")}$`);
|
|
3876
|
-
return regex.test(descriptor.rule.id);
|
|
3877
|
-
}
|
|
3878
|
-
function escapeRegex(value) {
|
|
3879
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3880
|
-
}
|
|
3881
|
-
function finalizeScanResult(diagnostics, fileCount, config) {
|
|
3882
|
-
const visibleDiagnostics = config.showSeverities ? diagnostics.filter((d) => config.showSeverities?.has(d.severity)) : diagnostics;
|
|
3883
|
-
return {
|
|
3884
|
-
diagnostics,
|
|
3885
|
-
visibleDiagnostics,
|
|
3886
|
-
fileCount,
|
|
3887
|
-
exitCode: computeExitCode(diagnostics, config.failOn)
|
|
3888
|
-
};
|
|
3889
|
-
}
|
|
3890
|
-
function toScanResult(execution) {
|
|
3891
|
-
return {
|
|
3892
|
-
diagnostics: execution.diagnostics,
|
|
3893
|
-
fileCount: execution.fileCount
|
|
3894
|
-
};
|
|
3895
|
-
}
|
|
3896
|
-
function computeExitCode(diagnostics, failOn) {
|
|
3897
|
-
if (failOn === "none") return 0;
|
|
3898
|
-
const hasError = diagnostics.some((d) => d.severity === "error");
|
|
3899
|
-
const hasWarning = diagnostics.some((d) => d.severity === "warning");
|
|
3900
|
-
const hasInfo = diagnostics.some((d) => d.severity === "info");
|
|
3901
|
-
if (failOn === "error") return hasError ? 2 : 0;
|
|
3902
|
-
if (failOn === "warning") {
|
|
3903
|
-
if (hasError) return 2;
|
|
3904
|
-
return hasWarning ? 1 : 0;
|
|
3905
|
-
}
|
|
3906
|
-
if (hasError) return 2;
|
|
3907
|
-
if (hasWarning || hasInfo) return 1;
|
|
3908
|
-
return 0;
|
|
3909
|
-
}
|
|
3910
|
-
|
|
3911
|
-
// src/engine.ts
|
|
3912
|
-
async function executeScan(options) {
|
|
3913
|
-
const config = resolveScanConfig(options);
|
|
3914
|
-
const files = await discoverFiles(config);
|
|
3915
|
-
const descriptors = buildRuleDescriptors(allRules);
|
|
3916
|
-
const activeRules = resolveActiveRules(descriptors, config);
|
|
3917
|
-
const diagnostics = analyzeFiles(files, activeRules);
|
|
3918
|
-
return finalizeScanResult(diagnostics, files.length, config);
|
|
3919
|
-
}
|
|
3920
|
-
async function scan(options) {
|
|
3921
|
-
const execution = await executeScan(options);
|
|
3922
|
-
return toScanResult(execution);
|
|
3923
|
-
}
|
|
3924
|
-
|
|
3925
3843
|
export {
|
|
3844
|
+
isTSRule,
|
|
3926
3845
|
allRules,
|
|
3927
3846
|
getRuleMetadata,
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
isSeverity,
|
|
3931
|
-
isFailOn,
|
|
3932
|
-
executeScan,
|
|
3933
|
-
scan
|
|
3847
|
+
analyzeFiles,
|
|
3848
|
+
analyzeGroup
|
|
3934
3849
|
};
|
|
3935
|
-
//# sourceMappingURL=chunk-
|
|
3850
|
+
//# sourceMappingURL=chunk-BLCVXVLX.js.map
|