@emeryld/manager 1.4.12 → 1.4.15
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,150 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { DEFAULT_LIMITS } from '../format-checker/config.js';
|
|
4
|
+
import { analyzeFileContent } from '../format-checker/scan/index.js';
|
|
5
|
+
import { collectDuplicateViolations } from '../format-checker/scan/duplicates.js';
|
|
6
|
+
function asPositiveInt(value, fallback) {
|
|
7
|
+
const num = Number(value);
|
|
8
|
+
if (!Number.isFinite(num))
|
|
9
|
+
return fallback;
|
|
10
|
+
const floored = Math.floor(num);
|
|
11
|
+
return floored <= 0 ? fallback : floored;
|
|
12
|
+
}
|
|
13
|
+
function asNonNegativeInt(value, fallback) {
|
|
14
|
+
const num = Number(value);
|
|
15
|
+
if (!Number.isFinite(num))
|
|
16
|
+
return fallback;
|
|
17
|
+
const floored = Math.floor(num);
|
|
18
|
+
return floored < 0 ? fallback : floored;
|
|
19
|
+
}
|
|
20
|
+
function buildLimits(rawOptions) {
|
|
21
|
+
const options = rawOptions && typeof rawOptions === 'object' ? rawOptions : {};
|
|
22
|
+
return {
|
|
23
|
+
...BASE_LIMITS,
|
|
24
|
+
maxFunctionLength: asPositiveInt(options.maxFunctionLength, BASE_LIMITS.maxFunctionLength),
|
|
25
|
+
maxMethodLength: asPositiveInt(options.maxMethodLength, BASE_LIMITS.maxMethodLength),
|
|
26
|
+
maxIndentationDepth: asPositiveInt(options.maxIndentationDepth, BASE_LIMITS.maxIndentationDepth),
|
|
27
|
+
maxFunctionsPerFile: asPositiveInt(options.maxFunctionsPerFile, BASE_LIMITS.maxFunctionsPerFile),
|
|
28
|
+
maxComponentsPerFile: asPositiveInt(options.maxComponentsPerFile, BASE_LIMITS.maxComponentsPerFile),
|
|
29
|
+
maxFileLength: asPositiveInt(options.maxFileLength, BASE_LIMITS.maxFileLength),
|
|
30
|
+
maxDuplicateLineOccurrences: asPositiveInt(options.maxDuplicateLineOccurrences, BASE_LIMITS.maxDuplicateLineOccurrences),
|
|
31
|
+
minDuplicateLines: asNonNegativeInt(options.minDuplicateLines, BASE_LIMITS.minDuplicateLines),
|
|
32
|
+
exportOnly: typeof options.exportOnly === 'boolean'
|
|
33
|
+
? options.exportOnly
|
|
34
|
+
: BASE_LIMITS.exportOnly,
|
|
35
|
+
indentationWidth: asPositiveInt(options.indentationWidth, BASE_LIMITS.indentationWidth),
|
|
36
|
+
reportingMode: BASE_LIMITS.reportingMode,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function loadWorkspaceDefaults() {
|
|
40
|
+
const settingsPath = path.join(process.cwd(), '.vscode', 'settings.json');
|
|
41
|
+
if (!existsSync(settingsPath))
|
|
42
|
+
return DEFAULT_LIMITS;
|
|
43
|
+
try {
|
|
44
|
+
const raw = readFileSync(settingsPath, 'utf-8');
|
|
45
|
+
const json = JSON.parse(raw);
|
|
46
|
+
const settings = json?.['manager.formatChecker'];
|
|
47
|
+
if (!settings || typeof settings !== 'object')
|
|
48
|
+
return DEFAULT_LIMITS;
|
|
49
|
+
return {
|
|
50
|
+
...DEFAULT_LIMITS,
|
|
51
|
+
maxFunctionLength: asPositiveInt(settings.maxFunctionLength, DEFAULT_LIMITS.maxFunctionLength),
|
|
52
|
+
maxMethodLength: asPositiveInt(settings.maxMethodLength, DEFAULT_LIMITS.maxMethodLength),
|
|
53
|
+
maxIndentationDepth: asPositiveInt(settings.maxIndentationDepth, DEFAULT_LIMITS.maxIndentationDepth),
|
|
54
|
+
maxFunctionsPerFile: asPositiveInt(settings.maxFunctionsPerFile, DEFAULT_LIMITS.maxFunctionsPerFile),
|
|
55
|
+
maxComponentsPerFile: asPositiveInt(settings.maxComponentsPerFile, DEFAULT_LIMITS.maxComponentsPerFile),
|
|
56
|
+
maxFileLength: asPositiveInt(settings.maxFileLength, DEFAULT_LIMITS.maxFileLength),
|
|
57
|
+
maxDuplicateLineOccurrences: asPositiveInt(settings.maxDuplicateLineOccurrences, DEFAULT_LIMITS.maxDuplicateLineOccurrences),
|
|
58
|
+
minDuplicateLines: asNonNegativeInt(settings.minDuplicateLines, DEFAULT_LIMITS.minDuplicateLines),
|
|
59
|
+
exportOnly: typeof settings.exportOnly === 'boolean'
|
|
60
|
+
? settings.exportOnly
|
|
61
|
+
: DEFAULT_LIMITS.exportOnly,
|
|
62
|
+
indentationWidth: asPositiveInt(settings.indentationWidth, DEFAULT_LIMITS.indentationWidth),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return DEFAULT_LIMITS;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const BASE_LIMITS = loadWorkspaceDefaults();
|
|
70
|
+
function parseDetailRange(detail) {
|
|
71
|
+
if (typeof detail !== 'string')
|
|
72
|
+
return undefined;
|
|
73
|
+
const match = /^(\d+):(\d+)\/(\d+):(\d+)$/.exec(detail);
|
|
74
|
+
if (!match)
|
|
75
|
+
return undefined;
|
|
76
|
+
return {
|
|
77
|
+
startLine: Number(match[1]),
|
|
78
|
+
startColumn: Number(match[2]),
|
|
79
|
+
endLine: Number(match[3]),
|
|
80
|
+
endColumn: Number(match[4]),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function locFromViolation(violation) {
|
|
84
|
+
const parsed = parseDetailRange(violation.detail);
|
|
85
|
+
if (parsed) {
|
|
86
|
+
return {
|
|
87
|
+
start: { line: parsed.startLine, column: Math.max(0, parsed.startColumn - 1) },
|
|
88
|
+
end: { line: parsed.endLine, column: Math.max(0, parsed.endColumn - 1) },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const line = typeof violation.line === 'number' && violation.line > 0 ? violation.line : 1;
|
|
92
|
+
return {
|
|
93
|
+
start: { line, column: 0 },
|
|
94
|
+
end: { line, column: 0 },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const rule = {
|
|
98
|
+
meta: {
|
|
99
|
+
type: 'suggestion',
|
|
100
|
+
docs: {
|
|
101
|
+
description: 'Runs @emeryld/manager format-checker heuristics as ESLint diagnostics.',
|
|
102
|
+
},
|
|
103
|
+
schema: [
|
|
104
|
+
{
|
|
105
|
+
type: 'object',
|
|
106
|
+
additionalProperties: false,
|
|
107
|
+
properties: {
|
|
108
|
+
maxFunctionLength: { type: 'integer', minimum: 1 },
|
|
109
|
+
maxMethodLength: { type: 'integer', minimum: 1 },
|
|
110
|
+
maxIndentationDepth: { type: 'integer', minimum: 1 },
|
|
111
|
+
maxFunctionsPerFile: { type: 'integer', minimum: 1 },
|
|
112
|
+
maxComponentsPerFile: { type: 'integer', minimum: 1 },
|
|
113
|
+
maxFileLength: { type: 'integer', minimum: 1 },
|
|
114
|
+
maxDuplicateLineOccurrences: { type: 'integer', minimum: 1 },
|
|
115
|
+
minDuplicateLines: { type: 'integer', minimum: 0 },
|
|
116
|
+
exportOnly: { type: 'boolean' },
|
|
117
|
+
indentationWidth: { type: 'integer', minimum: 1 },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
create(context) {
|
|
123
|
+
const filename = context.getFilename?.() ?? '<text>';
|
|
124
|
+
if (!filename || filename === '<input>' || filename === '<text>')
|
|
125
|
+
return {};
|
|
126
|
+
if (filename.endsWith('.d.ts'))
|
|
127
|
+
return {};
|
|
128
|
+
const limits = buildLimits(context.options?.[0]);
|
|
129
|
+
return {
|
|
130
|
+
Program() {
|
|
131
|
+
const sourceCode = context.getSourceCode();
|
|
132
|
+
const duplicates = new Map();
|
|
133
|
+
const fileSnapshots = new Map();
|
|
134
|
+
const violations = analyzeFileContent(filename, sourceCode.text, limits, duplicates, fileSnapshots);
|
|
135
|
+
const duplicateViolations = collectDuplicateViolations(duplicates, limits, fileSnapshots);
|
|
136
|
+
for (const violation of [...violations, ...duplicateViolations]) {
|
|
137
|
+
context.report({
|
|
138
|
+
loc: locFromViolation(violation),
|
|
139
|
+
message: `format-checker (${violation.type}): ${violation.message}`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
export default {
|
|
147
|
+
rules: {
|
|
148
|
+
'format-checker': rule,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
@@ -16,14 +16,7 @@ export async function analyzeFiles(files, limits) {
|
|
|
16
16
|
violations.push(...collectDuplicateViolations(duplicateMap, limits, fileSnapshots));
|
|
17
17
|
return violations;
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
let content;
|
|
21
|
-
try {
|
|
22
|
-
content = await readFile(filePath, 'utf-8');
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
19
|
+
export function analyzeFileContent(filePath, content, limits, duplicates, fileSnapshots) {
|
|
27
20
|
const lines = content.split(/\r?\n/);
|
|
28
21
|
const importFlags = markImportLines(lines);
|
|
29
22
|
const normalizedLines = lines.map((line, index) => ({
|
|
@@ -114,3 +107,13 @@ async function analyzeSingleFile(filePath, limits, duplicates, fileSnapshots) {
|
|
|
114
107
|
});
|
|
115
108
|
return violations;
|
|
116
109
|
}
|
|
110
|
+
async function analyzeSingleFile(filePath, limits, duplicates, fileSnapshots) {
|
|
111
|
+
let content;
|
|
112
|
+
try {
|
|
113
|
+
content = await readFile(filePath, 'utf-8');
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
return analyzeFileContent(filePath, content, limits, duplicates, fileSnapshots);
|
|
119
|
+
}
|