deep-slop 1.4.1
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/.deep-slop/.deep-slop-ignore +13 -0
- package/LICENSE +21 -0
- package/README.md +1170 -0
- package/dist/arch-constraints-C7s1E_bc.js +450 -0
- package/dist/arch-rules-DI1SYPqu.js +358 -0
- package/dist/ast-slop-BGdr58wZ.js +1839 -0
- package/dist/config-lint-ph3vMUbg.js +371 -0
- package/dist/dead-flow-DHRkyxZT.js +1422 -0
- package/dist/deep-slop-bundled.js +33140 -0
- package/dist/discover-B_S_Fy2S.js +164 -0
- package/dist/dup-detect-DKRXM04q.js +709 -0
- package/dist/file-utils-B_HFXhCs.js +93 -0
- package/dist/format-lint-DeElllNm.js +445 -0
- package/dist/framework-lint-CqdlF9hX.js +782 -0
- package/dist/i18n-lint-CPzx7V8Q.js +605 -0
- package/dist/import-intelligence-SK4F7XpL.js +966 -0
- package/dist/index.d.ts +233 -0
- package/dist/index.js +1030 -0
- package/dist/knip-CgxnnTBZ.js +93 -0
- package/dist/lint-external-ZbW3jGvB.js +326 -0
- package/dist/markup-lint-DKVEDz9M.js +805 -0
- package/dist/mcp.js +35939 -0
- package/dist/meta-quality-Dai1W5iC.js +224 -0
- package/dist/perf-hints-BnWFMFff.js +500 -0
- package/dist/security-deep-DJRINs10.js +1198 -0
- package/dist/syntax-deep-ZQYMutky.js +624 -0
- package/dist/tree-sitter-CM-cP0nl.js +661 -0
- package/dist/type-safety-Dboj2C1t.js +519 -0
- package/package.json +92 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
|
|
4
|
+
//#region src/engines/knip/index.ts
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
const KNIPTIMEOUT_MS = 3e4;
|
|
7
|
+
/** Check if knip is installed by running `npx knip --version` */
|
|
8
|
+
async function isKnipInstalled() {
|
|
9
|
+
try {
|
|
10
|
+
await execFileAsync("npx", ["knip", "--version"], { timeout: 15e3 });
|
|
11
|
+
return true;
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Run knip with JSON reporter and return parsed output */
|
|
17
|
+
async function runKnip(rootDir) {
|
|
18
|
+
try {
|
|
19
|
+
const { stdout } = await execFileAsync("npx", [
|
|
20
|
+
"knip",
|
|
21
|
+
"--reporter",
|
|
22
|
+
"json",
|
|
23
|
+
"--no-progress"
|
|
24
|
+
], {
|
|
25
|
+
timeout: KNIPTIMEOUT_MS,
|
|
26
|
+
cwd: rootDir
|
|
27
|
+
});
|
|
28
|
+
if (!String(stdout).trim()) return null;
|
|
29
|
+
return JSON.parse(String(stdout));
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Map a knip section to diagnostics */
|
|
35
|
+
function mapKnipSection(section, rulePrefix, messageLabel) {
|
|
36
|
+
if (!section) return [];
|
|
37
|
+
const diagnostics = [];
|
|
38
|
+
for (const [filePath, symbols] of Object.entries(section)) for (const symbol of symbols) diagnostics.push({
|
|
39
|
+
filePath,
|
|
40
|
+
engine: "knip",
|
|
41
|
+
rule: `knip/${rulePrefix}`,
|
|
42
|
+
severity: "warning",
|
|
43
|
+
message: `${messageLabel}: ${symbol}`,
|
|
44
|
+
help: `Remove the unused ${messageLabel.toLowerCase()} \`${symbol}\` or re-export it if it is part of the public API`,
|
|
45
|
+
line: 1,
|
|
46
|
+
column: 1,
|
|
47
|
+
category: "dead-code",
|
|
48
|
+
fixable: true,
|
|
49
|
+
detail: {
|
|
50
|
+
symbol,
|
|
51
|
+
type: rulePrefix
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return diagnostics;
|
|
55
|
+
}
|
|
56
|
+
const knipEngine = {
|
|
57
|
+
name: "knip",
|
|
58
|
+
description: "Runs knip to detect unused files, dependencies, exports, and types in TypeScript/JavaScript projects",
|
|
59
|
+
supportedLanguages: ["typescript", "javascript"],
|
|
60
|
+
async run(context) {
|
|
61
|
+
const start = Date.now();
|
|
62
|
+
if (!await isKnipInstalled()) return {
|
|
63
|
+
engine: "knip",
|
|
64
|
+
diagnostics: [],
|
|
65
|
+
elapsed: Date.now() - start,
|
|
66
|
+
skipped: true,
|
|
67
|
+
skipReason: "knip is not installed. Install with: npm i -D knip"
|
|
68
|
+
};
|
|
69
|
+
const output = await runKnip(context.rootDirectory);
|
|
70
|
+
if (!output) return {
|
|
71
|
+
engine: "knip",
|
|
72
|
+
diagnostics: [],
|
|
73
|
+
elapsed: Date.now() - start,
|
|
74
|
+
skipped: false
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
engine: "knip",
|
|
78
|
+
diagnostics: [
|
|
79
|
+
...mapKnipSection(output.files, "unused-file", "Unused file"),
|
|
80
|
+
...mapKnipSection(output.dependencies, "unused-dependency", "Unused dependency"),
|
|
81
|
+
...mapKnipSection(output.exports, "unused-export", "Unused export"),
|
|
82
|
+
...mapKnipSection(output.types, "unused-type", "Unused type"),
|
|
83
|
+
...mapKnipSection(output.classMembers, "unused-class-member", "Unused class member"),
|
|
84
|
+
...mapKnipSection(output.enumMembers, "unused-enum-member", "Unused enum member")
|
|
85
|
+
],
|
|
86
|
+
elapsed: Date.now() - start,
|
|
87
|
+
skipped: false
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
//#endregion
|
|
93
|
+
export { knipEngine };
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
//#region src/engines/lint-external/python.ts
|
|
6
|
+
/** Default timeout for ruff execution (ms) */
|
|
7
|
+
const RUFF_TIMEOUT_MS = 3e4;
|
|
8
|
+
/** Check if ruff is available on PATH */
|
|
9
|
+
function isRuffAvailable() {
|
|
10
|
+
try {
|
|
11
|
+
execSync("ruff --version", {
|
|
12
|
+
stdio: "pipe",
|
|
13
|
+
timeout: 5e3
|
|
14
|
+
});
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Map ruff severity-like codes to our severity levels */
|
|
21
|
+
function mapSeverity$2(code) {
|
|
22
|
+
if ([
|
|
23
|
+
"S",
|
|
24
|
+
"T20",
|
|
25
|
+
"ERA"
|
|
26
|
+
].some((p) => code.startsWith(p))) return "error";
|
|
27
|
+
return "warning";
|
|
28
|
+
}
|
|
29
|
+
/** Run ruff and return diagnostics */
|
|
30
|
+
function runRuff(context) {
|
|
31
|
+
if (!isRuffAvailable()) return [];
|
|
32
|
+
const root = context.rootDirectory;
|
|
33
|
+
let rawOutput;
|
|
34
|
+
try {
|
|
35
|
+
rawOutput = execSync("ruff check --output-format=json .", {
|
|
36
|
+
cwd: root,
|
|
37
|
+
stdio: [
|
|
38
|
+
"pipe",
|
|
39
|
+
"pipe",
|
|
40
|
+
"pipe"
|
|
41
|
+
],
|
|
42
|
+
timeout: RUFF_TIMEOUT_MS,
|
|
43
|
+
encoding: "utf-8"
|
|
44
|
+
});
|
|
45
|
+
} catch (err) {
|
|
46
|
+
const e = err;
|
|
47
|
+
if (e.stdout && typeof e.stdout === "string") rawOutput = e.stdout;
|
|
48
|
+
else return [];
|
|
49
|
+
}
|
|
50
|
+
let findings;
|
|
51
|
+
try {
|
|
52
|
+
findings = JSON.parse(rawOutput);
|
|
53
|
+
} catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
if (!Array.isArray(findings)) return [];
|
|
57
|
+
const diagnostics = [];
|
|
58
|
+
for (const f of findings) {
|
|
59
|
+
const filePath = relative(root, f.filename).replace(/\\/g, "/");
|
|
60
|
+
const ruleId = f.code ?? "unknown";
|
|
61
|
+
const severity = mapSeverity$2(ruleId);
|
|
62
|
+
diagnostics.push({
|
|
63
|
+
engine: "lint-external",
|
|
64
|
+
filePath,
|
|
65
|
+
rule: `lint-external/ruff-${ruleId}`,
|
|
66
|
+
severity,
|
|
67
|
+
message: f.message,
|
|
68
|
+
help: f.fix?.message ?? `Run 'ruff rule ${ruleId}' for details`,
|
|
69
|
+
line: f.location?.row ?? 1,
|
|
70
|
+
column: f.location?.column ?? 1,
|
|
71
|
+
category: "style",
|
|
72
|
+
fixable: f.fix != null
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return diagnostics;
|
|
76
|
+
}
|
|
77
|
+
/** Check if ruff is installed (for skip detection) */
|
|
78
|
+
function ruffAvailable() {
|
|
79
|
+
return isRuffAvailable();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/engines/lint-external/go.ts
|
|
84
|
+
/** Default timeout for golangci-lint execution (ms) */
|
|
85
|
+
const GOLANGCI_TIMEOUT_MS = 6e4;
|
|
86
|
+
/** Check if golangci-lint is available on PATH */
|
|
87
|
+
function isGolangciAvailable() {
|
|
88
|
+
try {
|
|
89
|
+
execSync("golangci-lint version", {
|
|
90
|
+
stdio: "pipe",
|
|
91
|
+
timeout: 5e3
|
|
92
|
+
});
|
|
93
|
+
return true;
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Check if go.mod exists in the project */
|
|
99
|
+
function hasGoMod(root) {
|
|
100
|
+
return existsSync(join(root, "go.mod"));
|
|
101
|
+
}
|
|
102
|
+
/** Map golangci-lint severity to our severity levels */
|
|
103
|
+
function mapSeverity$1(linter, textSeverity) {
|
|
104
|
+
if (textSeverity === "error") return "error";
|
|
105
|
+
if ([
|
|
106
|
+
"govet",
|
|
107
|
+
"errcheck",
|
|
108
|
+
"sqlclosecheck",
|
|
109
|
+
"rowserrcheck"
|
|
110
|
+
].includes(linter)) return "error";
|
|
111
|
+
return "warning";
|
|
112
|
+
}
|
|
113
|
+
/** Run golangci-lint and return diagnostics */
|
|
114
|
+
function runGolangciLint(context) {
|
|
115
|
+
if (!isGolangciAvailable()) return [];
|
|
116
|
+
if (!hasGoMod(context.rootDirectory)) return [];
|
|
117
|
+
const root = context.rootDirectory;
|
|
118
|
+
let rawOutput;
|
|
119
|
+
try {
|
|
120
|
+
rawOutput = execSync("golangci-lint run --out-format=json ./...", {
|
|
121
|
+
cwd: root,
|
|
122
|
+
stdio: [
|
|
123
|
+
"pipe",
|
|
124
|
+
"pipe",
|
|
125
|
+
"pipe"
|
|
126
|
+
],
|
|
127
|
+
timeout: GOLANGCI_TIMEOUT_MS,
|
|
128
|
+
encoding: "utf-8"
|
|
129
|
+
});
|
|
130
|
+
} catch (err) {
|
|
131
|
+
const e = err;
|
|
132
|
+
if (e.stdout && typeof e.stdout === "string") rawOutput = e.stdout;
|
|
133
|
+
else return [];
|
|
134
|
+
}
|
|
135
|
+
let report;
|
|
136
|
+
try {
|
|
137
|
+
report = JSON.parse(rawOutput);
|
|
138
|
+
} catch {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
const issues = report.Issues;
|
|
142
|
+
if (!Array.isArray(issues)) return [];
|
|
143
|
+
const diagnostics = [];
|
|
144
|
+
for (const issue of issues) {
|
|
145
|
+
const filePath = relative(root, issue.Pos.Filename).replace(/\\/g, "/");
|
|
146
|
+
const linter = issue.FromLinter ?? "unknown";
|
|
147
|
+
const severity = mapSeverity$1(linter, issue.Severity ?? "");
|
|
148
|
+
diagnostics.push({
|
|
149
|
+
engine: "lint-external",
|
|
150
|
+
filePath,
|
|
151
|
+
rule: `lint-external/golangci-${linter}`,
|
|
152
|
+
severity,
|
|
153
|
+
message: issue.Text,
|
|
154
|
+
help: `See golangci-lint documentation for linter: ${linter}`,
|
|
155
|
+
line: issue.Pos.Line ?? 1,
|
|
156
|
+
column: issue.Pos.Column ?? 1,
|
|
157
|
+
category: "style",
|
|
158
|
+
fixable: issue.Replacement != null
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return diagnostics;
|
|
162
|
+
}
|
|
163
|
+
/** Check if golangci-lint is installed (for skip detection) */
|
|
164
|
+
function golangciAvailable() {
|
|
165
|
+
return isGolangciAvailable();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/engines/lint-external/rust.ts
|
|
170
|
+
/** Default timeout for cargo clippy execution (ms) */
|
|
171
|
+
const CLIPPY_TIMEOUT_MS = 12e4;
|
|
172
|
+
/** Check if cargo is available on PATH */
|
|
173
|
+
function isCargoAvailable() {
|
|
174
|
+
try {
|
|
175
|
+
execSync("cargo --version", {
|
|
176
|
+
stdio: "pipe",
|
|
177
|
+
timeout: 5e3
|
|
178
|
+
});
|
|
179
|
+
return true;
|
|
180
|
+
} catch {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** Check if Cargo.toml exists in the project */
|
|
185
|
+
function hasCargoToml(root) {
|
|
186
|
+
return existsSync(join(root, "Cargo.toml"));
|
|
187
|
+
}
|
|
188
|
+
/** Map clippy level to our severity */
|
|
189
|
+
function mapSeverity(level) {
|
|
190
|
+
if (level === "error") return "error";
|
|
191
|
+
if (level === "warning") return "warning";
|
|
192
|
+
return "info";
|
|
193
|
+
}
|
|
194
|
+
/** Map clippy lint code to a category */
|
|
195
|
+
function mapCategory(code) {
|
|
196
|
+
if (code.startsWith("clippy::correctness")) return "syntax";
|
|
197
|
+
if (code.startsWith("clippy::suspicious")) return "security";
|
|
198
|
+
if (code.startsWith("clippy::style")) return "style";
|
|
199
|
+
if (code.startsWith("clippy::complexity")) return "architecture";
|
|
200
|
+
if (code.startsWith("clippy::perf")) return "performance";
|
|
201
|
+
if (code.startsWith("clippy::pedantic") || code.startsWith("clippy::restriction")) return "style";
|
|
202
|
+
if (code.startsWith("clippy::nursery")) return "style";
|
|
203
|
+
return "style";
|
|
204
|
+
}
|
|
205
|
+
/** Run cargo clippy and return diagnostics */
|
|
206
|
+
function runClippy(context) {
|
|
207
|
+
if (!isCargoAvailable()) return [];
|
|
208
|
+
if (!hasCargoToml(context.rootDirectory)) return [];
|
|
209
|
+
const root = context.rootDirectory;
|
|
210
|
+
let rawOutput;
|
|
211
|
+
try {
|
|
212
|
+
rawOutput = execSync("cargo clippy --message-format=json 2>&1", {
|
|
213
|
+
cwd: root,
|
|
214
|
+
stdio: [
|
|
215
|
+
"pipe",
|
|
216
|
+
"pipe",
|
|
217
|
+
"pipe"
|
|
218
|
+
],
|
|
219
|
+
timeout: CLIPPY_TIMEOUT_MS,
|
|
220
|
+
encoding: "utf-8",
|
|
221
|
+
maxBuffer: 10 * 1024 * 1024
|
|
222
|
+
});
|
|
223
|
+
} catch (err) {
|
|
224
|
+
const e = err;
|
|
225
|
+
if (e.stdout && typeof e.stdout === "string") rawOutput = e.stdout;
|
|
226
|
+
else return [];
|
|
227
|
+
}
|
|
228
|
+
const diagnostics = [];
|
|
229
|
+
const lines = rawOutput.split("\n").filter((l) => l.trim().length > 0);
|
|
230
|
+
for (const line of lines) {
|
|
231
|
+
let msg;
|
|
232
|
+
try {
|
|
233
|
+
msg = JSON.parse(line);
|
|
234
|
+
} catch {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (msg.reason !== "compiler-message" || !msg.message) continue;
|
|
238
|
+
const m = msg.message;
|
|
239
|
+
if (!m.spans || m.spans.length === 0) continue;
|
|
240
|
+
const primarySpan = m.spans.find((s) => s.is_primary) ?? m.spans[0];
|
|
241
|
+
const code = m.code?.code ?? "clippy::unknown";
|
|
242
|
+
const severity = mapSeverity(m.level);
|
|
243
|
+
const category = mapCategory(code);
|
|
244
|
+
const filePath = relative(root, primarySpan.file_name).replace(/\\/g, "/");
|
|
245
|
+
const help = m.children?.map((c) => c.message).join("; ") ?? `Run 'cargo clippy --explain ${code}' for details`;
|
|
246
|
+
diagnostics.push({
|
|
247
|
+
engine: "lint-external",
|
|
248
|
+
filePath,
|
|
249
|
+
rule: `lint-external/${code}`,
|
|
250
|
+
severity,
|
|
251
|
+
message: m.message ?? m.rendered?.split("\n")[0] ?? code,
|
|
252
|
+
help,
|
|
253
|
+
line: primarySpan.line_start ?? 1,
|
|
254
|
+
column: primarySpan.column_start ?? 1,
|
|
255
|
+
category,
|
|
256
|
+
fixable: false
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return diagnostics;
|
|
260
|
+
}
|
|
261
|
+
/** Check if cargo/clippy is installed (for skip detection) */
|
|
262
|
+
function clippyAvailable() {
|
|
263
|
+
return isCargoAvailable();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/engines/lint-external/index.ts
|
|
268
|
+
/** Lint-external engine: runs external linters (ruff, golangci-lint, clippy) */
|
|
269
|
+
const lintExternalEngine = {
|
|
270
|
+
name: "lint-external",
|
|
271
|
+
description: "External linter integration: runs ruff (Python), golangci-lint (Go), and clippy (Rust) when available",
|
|
272
|
+
supportedLanguages: [
|
|
273
|
+
"python",
|
|
274
|
+
"go",
|
|
275
|
+
"rust"
|
|
276
|
+
],
|
|
277
|
+
async run(context) {
|
|
278
|
+
const start = performance.now();
|
|
279
|
+
const diagnostics = [];
|
|
280
|
+
const languages = context.languages;
|
|
281
|
+
const skipReasons = [];
|
|
282
|
+
let anyLinterRan = false;
|
|
283
|
+
if (languages.includes("python")) if (ruffAvailable()) {
|
|
284
|
+
const ruffDiags = runRuff(context);
|
|
285
|
+
diagnostics.push(...ruffDiags);
|
|
286
|
+
anyLinterRan = true;
|
|
287
|
+
} else skipReasons.push("python: ruff not installed");
|
|
288
|
+
if (languages.includes("go")) if (golangciAvailable()) {
|
|
289
|
+
const goDiags = runGolangciLint(context);
|
|
290
|
+
diagnostics.push(...goDiags);
|
|
291
|
+
anyLinterRan = true;
|
|
292
|
+
} else skipReasons.push("go: golangci-lint not installed");
|
|
293
|
+
if (languages.includes("rust")) if (clippyAvailable()) {
|
|
294
|
+
const rustDiags = runClippy(context);
|
|
295
|
+
diagnostics.push(...rustDiags);
|
|
296
|
+
anyLinterRan = true;
|
|
297
|
+
} else skipReasons.push("rust: cargo/clippy not installed");
|
|
298
|
+
if (!languages.some((l) => [
|
|
299
|
+
"python",
|
|
300
|
+
"go",
|
|
301
|
+
"rust"
|
|
302
|
+
].includes(l))) return {
|
|
303
|
+
engine: this.name,
|
|
304
|
+
diagnostics: [],
|
|
305
|
+
elapsed: performance.now() - start,
|
|
306
|
+
skipped: true,
|
|
307
|
+
skipReason: "No Python, Go, or Rust detected in project"
|
|
308
|
+
};
|
|
309
|
+
if (!anyLinterRan) return {
|
|
310
|
+
engine: this.name,
|
|
311
|
+
diagnostics: [],
|
|
312
|
+
elapsed: performance.now() - start,
|
|
313
|
+
skipped: true,
|
|
314
|
+
skipReason: `No external linters available (${skipReasons.join("; ")})`
|
|
315
|
+
};
|
|
316
|
+
return {
|
|
317
|
+
engine: this.name,
|
|
318
|
+
diagnostics,
|
|
319
|
+
elapsed: performance.now() - start,
|
|
320
|
+
skipped: false
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
//#endregion
|
|
326
|
+
export { lintExternalEngine };
|