@emeryld/manager 1.4.5 → 1.4.7
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.
|
@@ -98,6 +98,7 @@ export function reportViolations(violations, reportingMode) {
|
|
|
98
98
|
logViolationEntry(entry, ' ', locationDescriptor);
|
|
99
99
|
}
|
|
100
100
|
console.log('');
|
|
101
|
+
console.log('');
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
return true;
|
|
@@ -115,5 +116,4 @@ function logViolationEntry(entry, messagePrefix, locationDescriptor) {
|
|
|
115
116
|
if (locationDescriptor) {
|
|
116
117
|
console.log(` ${colors.dim(locationDescriptor)}`);
|
|
117
118
|
}
|
|
118
|
-
console.log('');
|
|
119
119
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
// analysis/analysis.ts
|
|
1
2
|
import { readFile } from 'node:fs/promises';
|
|
2
3
|
import { collectDuplicateViolations, recordDuplicateLines, } from './duplicates.js';
|
|
3
4
|
import { countIndentation, groupIndentationViolations, } from './indentation.js';
|
|
4
5
|
import { collectFunctionRecords } from './functions.js';
|
|
5
|
-
import { markImportLines, normalizeLine, resolveScriptKind } from './utils.js';
|
|
6
|
+
import { isPunctuationOnlyLine, markImportLines, normalizeLine, resolveScriptKind, } from './utils.js';
|
|
6
7
|
import { collectRequiredVariables } from './variables.js';
|
|
7
8
|
import * as ts from 'typescript';
|
|
8
9
|
export async function analyzeFiles(files, limits) {
|
|
@@ -29,6 +30,7 @@ async function analyzeSingleFile(filePath, limits, duplicates, fileSnapshots) {
|
|
|
29
30
|
normalized: normalizeLine(line),
|
|
30
31
|
trimmed: line.trim(),
|
|
31
32
|
isImport: Boolean(importFlags[index]),
|
|
33
|
+
isPunctuationOnly: isPunctuationOnlyLine(line),
|
|
32
34
|
raw: line,
|
|
33
35
|
}));
|
|
34
36
|
const violations = [];
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
// analysis/duplicates.ts
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import { getLineColumnRange } from './utils.js';
|
|
3
4
|
import { collectRequiredVariables } from './variables.js';
|
|
5
|
+
function trimRightKeepIndent(line) {
|
|
6
|
+
// Preserve leading indentation exactly; remove trailing whitespace only.
|
|
7
|
+
return line.replace(/[ \t]+$/g, '');
|
|
8
|
+
}
|
|
4
9
|
export function recordDuplicateLines(normalizedLines, filePath, limits, duplicates, sourceFile, lines, fileSnapshots) {
|
|
5
10
|
const minLines = Math.max(1, limits.minDuplicateLines);
|
|
6
11
|
const registerSnapshot = () => {
|
|
@@ -10,8 +15,11 @@ export function recordDuplicateLines(normalizedLines, filePath, limits, duplicat
|
|
|
10
15
|
normalizedLines.forEach((entry, index) => {
|
|
11
16
|
if (!entry.normalized)
|
|
12
17
|
return;
|
|
13
|
-
|
|
14
|
-
if (
|
|
18
|
+
// Do not treat punctuation-only lines as standalone duplicate lines.
|
|
19
|
+
if (entry.isPunctuationOnly)
|
|
20
|
+
return;
|
|
21
|
+
const snippet = trimRightKeepIndent(entry.raw);
|
|
22
|
+
if (!snippet.trim())
|
|
15
23
|
return;
|
|
16
24
|
if (entry.isImport)
|
|
17
25
|
return;
|
|
@@ -25,19 +33,14 @@ export function recordDuplicateLines(normalizedLines, filePath, limits, duplicat
|
|
|
25
33
|
const slice = normalizedLines.slice(i, i + minLines);
|
|
26
34
|
if (slice.some((entry) => !entry.normalized))
|
|
27
35
|
continue;
|
|
28
|
-
const key = slice.map((entry) => entry.normalized).join('\n');
|
|
29
|
-
const snippetLines = slice
|
|
30
|
-
.map((entry) => entry.trimmed)
|
|
31
|
-
.filter(Boolean);
|
|
32
|
-
if (!snippetLines.length)
|
|
33
|
-
continue;
|
|
34
36
|
if (slice.some((entry) => entry.isImport))
|
|
35
37
|
continue;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (
|
|
38
|
+
// Punctuation-only lines should not count as a line for the minDuplicateLines threshold.
|
|
39
|
+
// i.e., we require >= minLines "real" lines (non-empty AND not punctuation-only).
|
|
40
|
+
const realLineCount = slice.filter((entry) => entry.trimmed.length > 0 && !entry.isPunctuationOnly).length;
|
|
41
|
+
if (realLineCount < minLines)
|
|
40
42
|
continue;
|
|
43
|
+
const key = slice.map((entry) => entry.normalized).join('\n');
|
|
41
44
|
const firstNonEmptyIndex = slice.findIndex((entry) => entry.trimmed.length > 0);
|
|
42
45
|
let lastNonEmptyIndex = -1;
|
|
43
46
|
for (let offset = slice.length - 1; offset >= 0; offset -= 1) {
|
|
@@ -52,6 +55,11 @@ export function recordDuplicateLines(normalizedLines, filePath, limits, duplicat
|
|
|
52
55
|
const endLine = i + 1 + lastNonEmptyIndex;
|
|
53
56
|
const { startColumn } = getLineColumnRange(slice[firstNonEmptyIndex].raw);
|
|
54
57
|
const { endColumn } = getLineColumnRange(slice[lastNonEmptyIndex].raw);
|
|
58
|
+
// Keep punctuation-only lines inside the snippet, but keep indentation.
|
|
59
|
+
const snippet = slice
|
|
60
|
+
.slice(firstNonEmptyIndex, lastNonEmptyIndex + 1)
|
|
61
|
+
.map((entry) => trimRightKeepIndent(entry.raw))
|
|
62
|
+
.join('\n');
|
|
55
63
|
registerSnapshot();
|
|
56
64
|
addDuplicateOccurrence(duplicates, key, filePath, startLine, startColumn, endLine, endColumn, snippet, sourceFile, lines);
|
|
57
65
|
}
|
|
@@ -147,13 +155,11 @@ function expandDuplicateTracker(tracker, fileSnapshots) {
|
|
|
147
155
|
}
|
|
148
156
|
return current;
|
|
149
157
|
}, infos[0]);
|
|
150
|
-
const
|
|
158
|
+
const snippetLines = primary.snapshot.normalizedLines
|
|
151
159
|
.slice(primary.startIndex, primary.endIndex + 1)
|
|
152
|
-
.map((entry) => entry.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return undefined;
|
|
156
|
-
const snippet = lines.map((line) => line.replace(/\s+/g, ' ')).join('\n');
|
|
160
|
+
.map((entry) => trimRightKeepIndent(entry.raw));
|
|
161
|
+
// Preserve indentation; do not collapse whitespace.
|
|
162
|
+
const snippet = snippetLines.join('\n');
|
|
157
163
|
const startLine = primary.startIndex + 1;
|
|
158
164
|
const endLine = primary.endIndex + 1;
|
|
159
165
|
const startColumn = getLineColumnRange(primary.snapshot.normalizedLines[primary.startIndex].raw).startColumn;
|
|
@@ -211,12 +217,8 @@ function formatOccurrenceDetail(info) {
|
|
|
211
217
|
const endLine = info.endIndex + 1;
|
|
212
218
|
const startEntry = info.snapshot.normalizedLines[info.startIndex];
|
|
213
219
|
const endEntry = info.snapshot.normalizedLines[info.endIndex];
|
|
214
|
-
const startColumn = startEntry
|
|
215
|
-
|
|
216
|
-
: 1;
|
|
217
|
-
const endColumn = endEntry
|
|
218
|
-
? getLineColumnRange(endEntry.raw).endColumn
|
|
219
|
-
: 1;
|
|
220
|
+
const startColumn = startEntry ? getLineColumnRange(startEntry.raw).startColumn : 1;
|
|
221
|
+
const endColumn = endEntry ? getLineColumnRange(endEntry.raw).endColumn : 1;
|
|
220
222
|
const relativePath = path.relative(process.cwd(), info.file);
|
|
221
223
|
return `${relativePath}:${startLine}:${startColumn}/${endLine}:${endColumn}`;
|
|
222
224
|
}
|
|
@@ -1,11 +1,24 @@
|
|
|
1
|
+
// analysis/utils.ts
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import * as ts from 'typescript';
|
|
4
|
+
// Lines that are only punctuation should still participate in duplicate matching/expansion,
|
|
5
|
+
// but should NOT be treated as "fully duplicate lines" on their own.
|
|
6
|
+
export function isPunctuationOnlyLine(line) {
|
|
7
|
+
const trimmed = line.trim();
|
|
8
|
+
if (!trimmed)
|
|
9
|
+
return false;
|
|
10
|
+
// Only these punctuation characters: {}();,<>[]
|
|
11
|
+
return /^[\{\}\(\);,<>[\]]+$/.test(trimmed);
|
|
12
|
+
}
|
|
3
13
|
export function normalizeLine(line) {
|
|
4
14
|
const trimmed = line.trim();
|
|
5
15
|
if (!trimmed)
|
|
6
16
|
return undefined;
|
|
7
|
-
|
|
8
|
-
|
|
17
|
+
// Keep punctuation-only lines in the duplicate fingerprint (so blocks that include them match),
|
|
18
|
+
// but they'll be excluded from single-line duplicate violations in duplicates.ts.
|
|
19
|
+
if (isPunctuationOnlyLine(trimmed)) {
|
|
20
|
+
return `__punct__:${trimmed}`;
|
|
21
|
+
}
|
|
9
22
|
const collapse = trimmed.replace(/\s+/g, ' ');
|
|
10
23
|
const noNumbers = collapse.replace(/\d+/g, '#');
|
|
11
24
|
return noNumbers.toLowerCase();
|