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.
@@ -0,0 +1,93 @@
1
+ import { readFile } from "node:fs/promises";
2
+
3
+ //#region src/utils/file-utils.ts
4
+ /** Read file contents with encoding detection */
5
+ async function readFileContent(filePath) {
6
+ let content = (await readFile(filePath)).toString("utf-8");
7
+ if (content.charCodeAt(0) === 65279) content = content.slice(1);
8
+ return content;
9
+ }
10
+ /** Detect BOM, CRLF, and other encoding anomalies */
11
+ function detectEncodingAnomalies(content) {
12
+ const hasBom = content.charCodeAt(0) === 65279;
13
+ const hasCrlf = content.includes("\r\n");
14
+ const hasZwnbsp = content.includes("");
15
+ const lfOnly = (content.match(/(?<!\r)\n/g) ?? []).length;
16
+ const crlfCount = (content.match(/\r\n/g) ?? []).length;
17
+ return {
18
+ hasBom,
19
+ hasCrlf,
20
+ hasZwnbsp,
21
+ lineEnding: crlfCount > 0 && lfOnly > crlfCount ? "mixed" : crlfCount > 0 ? "crlf" : "lf"
22
+ };
23
+ }
24
+ /** Split content into lines with line numbers */
25
+ function toLines(content) {
26
+ return content.split("\n").map((text, i) => ({
27
+ num: i + 1,
28
+ text
29
+ }));
30
+ }
31
+ /** Find all import statements in a file (regex-based, for quick scan) */
32
+ function extractImports(content, language) {
33
+ const imports = [];
34
+ const lines = toLines(content);
35
+ for (const { num, text } of lines) {
36
+ const trimmed = text.trim();
37
+ if (language === "typescript" || language === "javascript") {
38
+ const jsMatch = trimmed.match(/^import\s+(?:type\s+)?(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+['"]([^'"]+)['"]/);
39
+ if (jsMatch) imports.push({
40
+ line: num,
41
+ source: jsMatch[1],
42
+ raw: trimmed,
43
+ isTypeOnly: trimmed.includes("import type"),
44
+ isDefault: !trimmed.includes("{")
45
+ });
46
+ const dynMatch = trimmed.match(/import\s*\(\s*['"]([^'"]+)['"]\s*\)/);
47
+ if (dynMatch) imports.push({
48
+ line: num,
49
+ source: dynMatch[1],
50
+ raw: trimmed,
51
+ isTypeOnly: false,
52
+ isDynamic: true
53
+ });
54
+ const reqMatch = trimmed.match(/(?:const|let|var)\s+[^=]*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
55
+ if (reqMatch) imports.push({
56
+ line: num,
57
+ source: reqMatch[1],
58
+ raw: trimmed,
59
+ isTypeOnly: false,
60
+ isRequire: true
61
+ });
62
+ }
63
+ if (language === "python") {
64
+ const pyMatch = trimmed.match(/^from\s+([^\s]+)\s+import/);
65
+ if (pyMatch) imports.push({
66
+ line: num,
67
+ source: pyMatch[1],
68
+ raw: trimmed,
69
+ isTypeOnly: false
70
+ });
71
+ const pyImport = trimmed.match(/^import\s+([^\s]+)/);
72
+ if (pyImport) imports.push({
73
+ line: num,
74
+ source: pyImport[1],
75
+ raw: trimmed,
76
+ isTypeOnly: false
77
+ });
78
+ }
79
+ if (language === "go") {
80
+ const goMatch = trimmed.match(/"([^"]+)"/);
81
+ if (trimmed.startsWith("import") && goMatch) imports.push({
82
+ line: num,
83
+ source: goMatch[1],
84
+ raw: trimmed,
85
+ isTypeOnly: false
86
+ });
87
+ }
88
+ }
89
+ return imports;
90
+ }
91
+
92
+ //#endregion
93
+ export { toLines as i, extractImports as n, readFileContent as r, detectEncodingAnomalies as t };
@@ -0,0 +1,445 @@
1
+ import { i as toLines, r as readFileContent } from "./file-utils-B_HFXhCs.js";
2
+ import { extname, join, relative } from "node:path";
3
+ import { readdir } from "node:fs/promises";
4
+
5
+ //#region src/engines/format-lint/index.ts
6
+ const ALL_EXTENSIONS = new Set([
7
+ ".ts",
8
+ ".tsx",
9
+ ".js",
10
+ ".jsx",
11
+ ".mjs",
12
+ ".cjs",
13
+ ".py",
14
+ ".go",
15
+ ".rs",
16
+ ".rb",
17
+ ".php",
18
+ ".java",
19
+ ".cs",
20
+ ".swift",
21
+ ".json",
22
+ ".yaml",
23
+ ".yml",
24
+ ".css",
25
+ ".html",
26
+ ".md"
27
+ ]);
28
+ const JS_TS_EXTENSIONS = new Set([
29
+ ".ts",
30
+ ".tsx",
31
+ ".js",
32
+ ".jsx",
33
+ ".mjs",
34
+ ".cjs"
35
+ ]);
36
+ function isRelevantFile(filePath) {
37
+ const ext = extname(filePath);
38
+ return ALL_EXTENSIONS.has(ext);
39
+ }
40
+ function isJsTsFile(filePath) {
41
+ const ext = extname(filePath);
42
+ return JS_TS_EXTENSIONS.has(ext);
43
+ }
44
+ /** Recursively collect file paths under root, respecting exclude list */
45
+ async function collectFiles(root, exclude) {
46
+ const results = [];
47
+ async function walk(dir) {
48
+ let entries;
49
+ try {
50
+ entries = await readdir(dir, { withFileTypes: true });
51
+ } catch {
52
+ return;
53
+ }
54
+ for (const entry of entries) {
55
+ const full = join(dir, entry.name);
56
+ if (exclude.some((pat) => full.includes(pat))) continue;
57
+ if (entry.isDirectory()) await walk(full);
58
+ else if (entry.isFile() && isRelevantFile(full)) results.push(full);
59
+ }
60
+ }
61
+ await walk(root);
62
+ return results;
63
+ }
64
+ /** Make a diagnostic with sensible defaults for format-lint */
65
+ function makeDiagnostic(overrides) {
66
+ return {
67
+ engine: "format-lint",
68
+ severity: "info",
69
+ column: 1,
70
+ category: "style",
71
+ fixable: false,
72
+ help: "",
73
+ ...overrides
74
+ };
75
+ }
76
+ function detectInconsistentIndent(content, lines, filePath) {
77
+ const diagnostics = [];
78
+ const hasTab = /^[\t]/m.test(content);
79
+ const hasSpace = /^ [^\s]/m.test(content) || /^ {2,}/m.test(content);
80
+ if (!hasTab || !hasSpace) return diagnostics;
81
+ let tabLines = 0;
82
+ let spaceLines = 0;
83
+ const indentLines = [];
84
+ for (const { num, text } of lines) {
85
+ if (text.length === 0) continue;
86
+ const leading = text.match(/^[\t ]+/);
87
+ if (!leading) continue;
88
+ const indent = leading[0];
89
+ const usesTab = indent.includes(" ");
90
+ const usesSpace = indent.includes(" ");
91
+ if (usesTab && !usesSpace) {
92
+ tabLines++;
93
+ indentLines.push({
94
+ num,
95
+ text,
96
+ usesTab: true
97
+ });
98
+ } else if (usesSpace && !usesTab) {
99
+ spaceLines++;
100
+ indentLines.push({
101
+ num,
102
+ text,
103
+ usesTab: false
104
+ });
105
+ }
106
+ }
107
+ if (tabLines === 0 || spaceLines === 0) return diagnostics;
108
+ const minorityIsTab = tabLines < spaceLines;
109
+ const minorityLines = indentLines.filter((l) => l.usesTab === minorityIsTab);
110
+ const majorityLabel = minorityIsTab ? "spaces" : "tabs";
111
+ const minorityLabel = minorityIsTab ? "tabs" : "spaces";
112
+ const reported = minorityLines.slice(0, 5);
113
+ for (const { num } of reported) diagnostics.push(makeDiagnostic({
114
+ filePath,
115
+ rule: "format-lint/inconsistent-indent",
116
+ message: `Mixed indentation: file uses ${majorityLabel} primarily but ${minorityLabel} on this line`,
117
+ line: num,
118
+ severity: "warning",
119
+ help: `Standardize on one indentation style (${majorityLabel}) throughout the file`,
120
+ fixable: true,
121
+ suggestion: {
122
+ type: "replace",
123
+ text: minorityIsTab ? "// Replace tabs with spaces" : "// Replace leading spaces with tabs",
124
+ confidence: .85,
125
+ reason: `Mixed indentation causes rendering differences across editors and breaks tooling that expects consistent indentation`
126
+ },
127
+ detail: {
128
+ majorityStyle: majorityLabel,
129
+ minorityStyle: minorityLabel,
130
+ minorityCount: minorityLines.length
131
+ }
132
+ }));
133
+ return diagnostics;
134
+ }
135
+ function detectInconsistentQuotes(content, lines, filePath) {
136
+ const diagnostics = [];
137
+ if (!isJsTsFile(filePath)) return diagnostics;
138
+ const singleQuoteRe = /(^|[^\\])'[^']*'/g;
139
+ const doubleQuoteRe = /(^|[^\\])"[^"]*"/g;
140
+ const isImportLine = (text) => /^\s*import\s+/.test(text) || /^\s*export\s+/.test(text);
141
+ let singleCount = 0;
142
+ let doubleCount = 0;
143
+ const quoteLines = [];
144
+ for (const { num, text } of lines) {
145
+ const trimmed = text.trim();
146
+ if (trimmed.length === 0) continue;
147
+ if (isImportLine(trimmed)) continue;
148
+ singleQuoteRe.lastIndex = 0;
149
+ doubleQuoteRe.lastIndex = 0;
150
+ const hasSingle = singleQuoteRe.test(trimmed);
151
+ const hasDouble = doubleQuoteRe.test(trimmed);
152
+ if (hasSingle) singleCount++;
153
+ if (hasDouble) doubleCount++;
154
+ if (hasSingle || hasDouble) quoteLines.push({
155
+ num,
156
+ hasSingle,
157
+ hasDouble
158
+ });
159
+ }
160
+ if (singleCount === 0 || doubleCount === 0) return diagnostics;
161
+ const majorityIsSingle = singleCount > doubleCount;
162
+ const majorityLabel = majorityIsSingle ? "single" : "double";
163
+ const minorityLabel = majorityIsSingle ? "double" : "single";
164
+ const minorityLines = quoteLines.filter((l) => majorityIsSingle ? l.hasDouble : l.hasSingle).slice(0, 5);
165
+ for (const { num } of minorityLines) diagnostics.push(makeDiagnostic({
166
+ filePath,
167
+ rule: "format-lint/inconsistent-quotes",
168
+ message: `Mixed quote styles: file primarily uses ${majorityLabel} quotes but ${minorityLabel} quotes here`,
169
+ line: num,
170
+ severity: "info",
171
+ help: `Standardize on ${majorityLabel} quotes throughout the file, or configure Prettier/ESLint to enforce a style`,
172
+ fixable: true,
173
+ suggestion: {
174
+ type: "replace",
175
+ text: `// Use ${majorityLabel} quotes consistently`,
176
+ confidence: .7,
177
+ reason: `Mixed quote styles are inconsistent; picking one style reduces noise in diffs and reviews`
178
+ },
179
+ detail: {
180
+ majorityStyle: majorityLabel,
181
+ minorityStyle: minorityLabel,
182
+ singleCount,
183
+ doubleCount
184
+ }
185
+ }));
186
+ return diagnostics;
187
+ }
188
+ function detectMaxLineLength(content, lines, filePath, maxLength) {
189
+ const diagnostics = [];
190
+ for (const { num, text } of lines) {
191
+ const trimmed = text.trim();
192
+ if (/^\s*import\s+/.test(trimmed)) continue;
193
+ if (/^https?:\/\//.test(trimmed)) continue;
194
+ if (text.length > maxLength) diagnostics.push(makeDiagnostic({
195
+ filePath,
196
+ rule: "format-lint/max-line-length",
197
+ message: `Line exceeds ${maxLength} characters (${text.length} chars)`,
198
+ line: num,
199
+ column: maxLength + 1,
200
+ severity: "info",
201
+ help: "Break long lines into multiple lines for readability, or configure Prettier printWidth",
202
+ fixable: false,
203
+ suggestion: {
204
+ type: "refactor",
205
+ text: "// Break into multiple lines",
206
+ confidence: .5,
207
+ reason: "Long lines require horizontal scrolling and reduce readability in code review and terminals"
208
+ },
209
+ detail: {
210
+ lineLength: text.length,
211
+ maxLength
212
+ }
213
+ }));
214
+ }
215
+ return diagnostics.slice(0, 10);
216
+ }
217
+ function detectInconsistentSemicolons(content, lines, filePath) {
218
+ const diagnostics = [];
219
+ if (!isJsTsFile(filePath)) return diagnostics;
220
+ const statementRe = /^(?:return|throw|const|let|var|export|import|function|class|interface|type|enum)\b/;
221
+ let withSemi = 0;
222
+ let withoutSemi = 0;
223
+ const semiLines = [];
224
+ for (const { num, text } of lines) {
225
+ const trimmed = text.trim();
226
+ if (trimmed.length === 0) continue;
227
+ if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) continue;
228
+ if (trimmed.startsWith("import ") || trimmed.startsWith("export type")) continue;
229
+ if (!(statementRe.test(trimmed) || /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[=+\-*/]/.test(trimmed) || /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(/.test(trimmed) || /^this\./.test(trimmed) || /^\./.test(trimmed))) continue;
230
+ const endsWithSemi = /;\s*$/.test(trimmed);
231
+ const endsWithoutSemi = /[^;{(\[]\s*$/.test(trimmed) && !trimmed.endsWith("{") && !trimmed.endsWith("(") && !trimmed.endsWith("[") && !trimmed.endsWith(",");
232
+ if (endsWithSemi) {
233
+ withSemi++;
234
+ semiLines.push({
235
+ num,
236
+ hasSemi: true
237
+ });
238
+ } else if (endsWithoutSemi && !trimmed.endsWith("}")) {
239
+ withoutSemi++;
240
+ semiLines.push({
241
+ num,
242
+ hasSemi: false
243
+ });
244
+ }
245
+ }
246
+ if (withSemi === 0 || withoutSemi === 0) return diagnostics;
247
+ const majorityHasSemi = withSemi > withoutSemi;
248
+ const minorityLabel = majorityHasSemi ? "without semicolons" : "with semicolons";
249
+ const majorityLabel = majorityHasSemi ? "with semicolons" : "without semicolons";
250
+ const minorityLines = semiLines.filter((l) => l.hasSemi !== majorityHasSemi).slice(0, 5);
251
+ for (const { num } of minorityLines) diagnostics.push(makeDiagnostic({
252
+ filePath,
253
+ rule: "format-lint/inconsistent-semicolons",
254
+ message: `Inconsistent semicolons: file mostly uses ${majorityLabel} but this line is ${minorityLabel}`,
255
+ line: num,
256
+ severity: "info",
257
+ help: `Standardize on one semicolon style: ${majorityLabel}. Configure ESLint semi rule or Prettier semi option`,
258
+ fixable: true,
259
+ suggestion: {
260
+ type: "replace",
261
+ text: majorityHasSemi ? "// Add semicolon" : "// Remove semicolon",
262
+ confidence: .75,
263
+ reason: "Inconsistent semicolon usage creates noise in diffs and makes the codebase harder to read"
264
+ },
265
+ detail: {
266
+ majorityStyle: majorityLabel,
267
+ withSemi,
268
+ withoutSemi
269
+ }
270
+ }));
271
+ return diagnostics;
272
+ }
273
+ function detectBlankLineCluster(content, lines, filePath) {
274
+ const diagnostics = [];
275
+ let blankRun = 0;
276
+ let runStartLine = 0;
277
+ for (const { num, text } of lines) if (text.trim().length === 0) {
278
+ if (blankRun === 0) runStartLine = num;
279
+ blankRun++;
280
+ } else {
281
+ if (blankRun >= 3) diagnostics.push(makeDiagnostic({
282
+ filePath,
283
+ rule: "format-lint/blank-line-cluster",
284
+ message: `${blankRun} consecutive blank lines — excessive whitespace`,
285
+ line: runStartLine,
286
+ severity: "suggestion",
287
+ help: "Reduce to 1-2 blank lines for readability. Most style guides recommend at most 2 blank lines between sections",
288
+ fixable: true,
289
+ suggestion: {
290
+ type: "delete",
291
+ text: "",
292
+ confidence: .95,
293
+ reason: "Excessive blank lines waste vertical space and add noise; 1-2 blank lines are sufficient for visual separation",
294
+ range: {
295
+ startLine: runStartLine + 1,
296
+ startCol: 1,
297
+ endLine: runStartLine + blankRun - 1,
298
+ endCol: 1
299
+ }
300
+ },
301
+ detail: {
302
+ blankCount: blankRun,
303
+ startLine: runStartLine
304
+ }
305
+ }));
306
+ blankRun = 0;
307
+ }
308
+ if (blankRun >= 3) diagnostics.push(makeDiagnostic({
309
+ filePath,
310
+ rule: "format-lint/blank-line-cluster",
311
+ message: `${blankRun} consecutive blank lines at end of file — excessive whitespace`,
312
+ line: runStartLine,
313
+ severity: "suggestion",
314
+ help: "Reduce to 1 blank line at end of file",
315
+ fixable: true,
316
+ suggestion: {
317
+ type: "delete",
318
+ text: "",
319
+ confidence: .95,
320
+ reason: "Excessive trailing blank lines add noise; one blank line is sufficient before EOF"
321
+ },
322
+ detail: {
323
+ blankCount: blankRun,
324
+ startLine: runStartLine
325
+ }
326
+ }));
327
+ return diagnostics;
328
+ }
329
+ function detectTrailingCommaInconsistency(content, lines, filePath) {
330
+ const diagnostics = [];
331
+ if (!isJsTsFile(filePath)) return diagnostics;
332
+ const closingLineRe = /^\s*[}\])]\s*[;,]?\s*$/;
333
+ const lastItemWithComma = /^\s*[^/].*,\s*$/;
334
+ const lastItemNoComma = /^\s*[^/].*[^,]\s*$/;
335
+ let trailingCount = 0;
336
+ let noTrailingCount = 0;
337
+ const commaLines = [];
338
+ for (let i = 0; i < lines.length - 1; i++) {
339
+ const current = lines[i];
340
+ const next = lines[i + 1];
341
+ const trimmed = current.text.trim();
342
+ if (trimmed.length === 0 || trimmed.startsWith("//")) continue;
343
+ if (!closingLineRe.test(next.text.trim())) continue;
344
+ const hasTrailing = lastItemWithComma.test(trimmed) && !trimmed.startsWith("//");
345
+ const noTrailing = lastItemNoComma.test(trimmed) && !trimmed.startsWith("//");
346
+ if (hasTrailing) {
347
+ trailingCount++;
348
+ commaLines.push({
349
+ num: current.num,
350
+ hasTrailing: true
351
+ });
352
+ } else if (noTrailing) {
353
+ noTrailingCount++;
354
+ commaLines.push({
355
+ num: current.num,
356
+ hasTrailing: false
357
+ });
358
+ }
359
+ }
360
+ if (trailingCount === 0 || noTrailingCount === 0) return diagnostics;
361
+ const majorityTrailing = trailingCount > noTrailingCount;
362
+ const minorityLabel = majorityTrailing ? "without trailing commas" : "with trailing commas";
363
+ const majorityLabel = majorityTrailing ? "with trailing commas" : "without trailing commas";
364
+ const minorityLines = commaLines.filter((l) => l.hasTrailing !== majorityTrailing).slice(0, 5);
365
+ for (const { num } of minorityLines) diagnostics.push(makeDiagnostic({
366
+ filePath,
367
+ rule: "format-lint/trailing-comma-inconsistency",
368
+ message: `Inconsistent trailing commas: file mostly ${majorityLabel} but this line is ${minorityLabel}`,
369
+ line: num,
370
+ severity: "info",
371
+ help: `Standardize trailing comma style. Configure ESLint comma-dangle or Prettier trailingComma`,
372
+ fixable: true,
373
+ suggestion: {
374
+ type: "replace",
375
+ text: majorityTrailing ? "// Add trailing comma" : "// Remove trailing comma",
376
+ confidence: .8,
377
+ reason: "Inconsistent trailing commas create diff noise and make refactoring error-prone"
378
+ },
379
+ detail: {
380
+ majorityStyle: majorityLabel,
381
+ trailingCount,
382
+ noTrailingCount
383
+ }
384
+ }));
385
+ return diagnostics;
386
+ }
387
+ const formatLintEngine = {
388
+ name: "format-lint",
389
+ description: "Format linting: mixed indentation, inconsistent quotes, max line length, inconsistent semicolons, blank line clusters, trailing comma inconsistency",
390
+ supportedLanguages: [
391
+ "typescript",
392
+ "javascript",
393
+ "tsx",
394
+ "jsx",
395
+ "python",
396
+ "go",
397
+ "rust",
398
+ "ruby",
399
+ "php",
400
+ "java",
401
+ "csharp",
402
+ "swift"
403
+ ],
404
+ async run(context) {
405
+ const start = Date.now();
406
+ const diagnostics = [];
407
+ const { rootDirectory, config, files: specifiedFiles } = context;
408
+ const maxLineLength = config.format?.maxLineLength ?? 120;
409
+ const filePaths = specifiedFiles ? specifiedFiles.filter(isRelevantFile) : await collectFiles(rootDirectory, config.exclude);
410
+ if (filePaths.length === 0) return {
411
+ engine: this.name,
412
+ diagnostics: [],
413
+ elapsed: Date.now() - start,
414
+ skipped: true,
415
+ skipReason: "No relevant files found to analyze"
416
+ };
417
+ for (const fp of filePaths) try {
418
+ const content = await readFileContent(fp);
419
+ const relPath = relative(rootDirectory, fp);
420
+ const lines = toLines(content);
421
+ diagnostics.push(...detectInconsistentIndent(content, lines, relPath));
422
+ diagnostics.push(...detectInconsistentQuotes(content, lines, relPath));
423
+ diagnostics.push(...detectMaxLineLength(content, lines, relPath, maxLineLength));
424
+ diagnostics.push(...detectInconsistentSemicolons(content, lines, relPath));
425
+ diagnostics.push(...detectBlankLineCluster(content, lines, relPath));
426
+ diagnostics.push(...detectTrailingCommaInconsistency(content, lines, relPath));
427
+ } catch {}
428
+ const seen = /* @__PURE__ */ new Set();
429
+ const unique = diagnostics.filter((d) => {
430
+ const key = `${d.filePath}:${d.line}:${d.rule}`;
431
+ if (seen.has(key)) return false;
432
+ seen.add(key);
433
+ return true;
434
+ });
435
+ return {
436
+ engine: this.name,
437
+ diagnostics: unique,
438
+ elapsed: Date.now() - start,
439
+ skipped: false
440
+ };
441
+ }
442
+ };
443
+
444
+ //#endregion
445
+ export { formatLintEngine };