@rslint/core 0.6.4 → 0.7.0
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/bin/rslint.js +23 -0
- package/dist/0~engine.js +109 -19
- package/dist/519.js +601 -0
- package/dist/770.js +18 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +42 -92
- package/dist/config-loader.d.ts +175 -9
- package/dist/config-loader.js +3 -161
- package/dist/eslint-plugin/index.d.ts +25 -13
- package/dist/eslint-plugin/index.js +92 -29
- package/dist/eslint-plugin/lint-worker.js +61 -27
- package/dist/eslint-plugin/types.d.ts +19 -9
- package/dist/index.d.ts +51 -21
- package/dist/index.js +1331 -91
- package/dist/internal.d.ts +38 -3
- package/dist/internal.js +81 -35
- package/dist/service.d.ts +90 -4
- package/dist/service.js +116 -10
- package/package.json +14 -14
- package/bin/rslint.cjs +0 -48
- package/dist/207.js +0 -897
package/dist/519.js
ADDED
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
import node_path from "node:path";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import picomatch from "picomatch";
|
|
7
|
+
const NATIVE_PLUGINS = [
|
|
8
|
+
"@typescript-eslint",
|
|
9
|
+
'import',
|
|
10
|
+
'jest',
|
|
11
|
+
'jsx-a11y',
|
|
12
|
+
'promise',
|
|
13
|
+
'react',
|
|
14
|
+
'react-hooks',
|
|
15
|
+
'unicorn'
|
|
16
|
+
];
|
|
17
|
+
const NATIVE_PLUGIN_DECL_ALIASES = [
|
|
18
|
+
'eslint-plugin-import',
|
|
19
|
+
'eslint-plugin-jest',
|
|
20
|
+
'eslint-plugin-jsx-a11y',
|
|
21
|
+
'eslint-plugin-promise',
|
|
22
|
+
'eslint-plugin-react-hooks',
|
|
23
|
+
'eslint-plugin-unicorn'
|
|
24
|
+
];
|
|
25
|
+
const NATIVE_PLUGIN_RESERVED_NAMES = new Set([
|
|
26
|
+
...NATIVE_PLUGINS,
|
|
27
|
+
...NATIVE_PLUGIN_DECL_ALIASES
|
|
28
|
+
]);
|
|
29
|
+
function defineConfig(config) {
|
|
30
|
+
return config;
|
|
31
|
+
}
|
|
32
|
+
function define_config_globalIgnores(ignorePatterns) {
|
|
33
|
+
if (!Array.isArray(ignorePatterns)) throw new TypeError('ignorePatterns must be an array');
|
|
34
|
+
if (0 === ignorePatterns.length) throw new TypeError('ignorePatterns must contain at least one pattern');
|
|
35
|
+
return {
|
|
36
|
+
ignores: ignorePatterns
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function selectPluginSource(entry) {
|
|
40
|
+
if (null == entry || 'object' != typeof entry) return null;
|
|
41
|
+
const e = entry;
|
|
42
|
+
if (null != e.plugins && 'object' == typeof e.plugins && !Array.isArray(e.plugins)) return e.plugins;
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function unwrapPluginModule(mod) {
|
|
46
|
+
if (null == mod || 'object' != typeof mod) return null;
|
|
47
|
+
const m = mod;
|
|
48
|
+
if (null != m.default && 'object' == typeof m.default) return m.default;
|
|
49
|
+
return mod;
|
|
50
|
+
}
|
|
51
|
+
let freshConfigLoadNonce = 0;
|
|
52
|
+
function isRecord(value) {
|
|
53
|
+
return null !== value && 'object' == typeof value && !Array.isArray(value);
|
|
54
|
+
}
|
|
55
|
+
async function loadConfigFile(configPath) {
|
|
56
|
+
const ext = node_path.extname(configPath);
|
|
57
|
+
if ('.ts' === ext || '.mts' === ext || '.cts' === ext) {
|
|
58
|
+
const useNative = Boolean(process.features.typescript);
|
|
59
|
+
if (useNative) {
|
|
60
|
+
const mod = await import(pathToFileURL(configPath).href);
|
|
61
|
+
return mod.default ?? mod;
|
|
62
|
+
}
|
|
63
|
+
const jiti = await loadJiti(configPath);
|
|
64
|
+
if (jiti) {
|
|
65
|
+
const resolved = await jiti.import(configPath);
|
|
66
|
+
return extractDefault(resolved);
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`Failed to load TypeScript config file: ${configPath}\nTo load .ts/.mts/.cts config files, either:\n 1. Use Node.js >= 22.6 (with native TypeScript support)\n 2. Install jiti as a dependency: npm install -D jiti`);
|
|
69
|
+
}
|
|
70
|
+
const mod = await import(pathToFileURL(configPath).href);
|
|
71
|
+
return mod.default ?? mod;
|
|
72
|
+
}
|
|
73
|
+
async function loadConfigFileFresh(configPath) {
|
|
74
|
+
const ext = node_path.extname(configPath);
|
|
75
|
+
const requireFromConfig = createRequire(pathToFileURL(configPath));
|
|
76
|
+
const evictCommonJSCache = ()=>{
|
|
77
|
+
try {
|
|
78
|
+
Reflect.deleteProperty(requireFromConfig.cache, requireFromConfig.resolve(configPath));
|
|
79
|
+
} catch {}
|
|
80
|
+
};
|
|
81
|
+
evictCommonJSCache();
|
|
82
|
+
if (('.ts' === ext || '.mts' === ext || '.cts' === ext) && !process.features.typescript) return loadConfigFile(configPath);
|
|
83
|
+
return importConfigFresh(configPath);
|
|
84
|
+
}
|
|
85
|
+
async function importConfigFresh(configPath) {
|
|
86
|
+
const url = pathToFileURL(configPath);
|
|
87
|
+
url.searchParams.set('rslint', String(freshConfigLoadNonce++));
|
|
88
|
+
const mod = await import(url.href);
|
|
89
|
+
return mod.default ?? mod;
|
|
90
|
+
}
|
|
91
|
+
async function loadJiti(configPath) {
|
|
92
|
+
try {
|
|
93
|
+
const { createJiti } = await import("jiti");
|
|
94
|
+
return createJiti(node_path.dirname(configPath), {
|
|
95
|
+
interopDefault: true
|
|
96
|
+
});
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function extractDefault(mod) {
|
|
102
|
+
if ('object' == typeof mod && null !== mod && 'default' in mod) return mod.default;
|
|
103
|
+
return mod;
|
|
104
|
+
}
|
|
105
|
+
function normalizeConfig(config) {
|
|
106
|
+
if (!Array.isArray(config)) throw new Error(`rslint config must export an array (flat config format), got ${typeof config}`);
|
|
107
|
+
for(let index = 0; index < config.length; index++)if (!Object.prototype.hasOwnProperty.call(config, index)) throw new Error(`[rslint] Config entry at index ${index}: unexpected undefined config`);
|
|
108
|
+
return config.map((rawEntry, index)=>{
|
|
109
|
+
if (null === rawEntry) throw new Error(`[rslint] Config entry at index ${index}: unexpected null config`);
|
|
110
|
+
if (Array.isArray(rawEntry)) throw new Error(`[rslint] Config entry at index ${index}: unexpected array`);
|
|
111
|
+
if (!isRecord(rawEntry)) throw new Error(`[rslint] Config entry at index ${index} must be an object, got ${typeof rawEntry}`);
|
|
112
|
+
const entry = rawEntry;
|
|
113
|
+
const hasFiles = Object.prototype.hasOwnProperty.call(entry, 'files');
|
|
114
|
+
if (hasFiles && !Array.isArray(entry.files)) throw new Error(`[rslint] Config entry at index ${index}: "files" must be an array, got ${typeof entry.files}`);
|
|
115
|
+
if (hasFiles && Array.isArray(entry.files) && 0 === entry.files.length) throw new Error(`[rslint] Config entry at index ${index}: "files" must be a non-empty array`);
|
|
116
|
+
if (hasFiles && Array.isArray(entry.files) && entry.files.some((pattern)=>'string' != typeof pattern && (!Array.isArray(pattern) || pattern.some((nestedPattern)=>'string' != typeof nestedPattern)))) throw new Error(`[rslint] Config entry at index ${index}: "files" must contain only strings or arrays of strings`);
|
|
117
|
+
const hasIgnores = Object.prototype.hasOwnProperty.call(entry, 'ignores');
|
|
118
|
+
if (hasIgnores && !Array.isArray(entry.ignores)) throw new Error(`[rslint] Config entry at index ${index}: "ignores" must be an array, got ${typeof entry.ignores}`);
|
|
119
|
+
if (Array.isArray(entry.ignores) && entry.ignores.some((pattern)=>'string' != typeof pattern)) throw new Error(`[rslint] Config entry at index ${index}: "ignores" must contain only strings`);
|
|
120
|
+
for (const key of [
|
|
121
|
+
'rules',
|
|
122
|
+
'languageOptions',
|
|
123
|
+
'settings'
|
|
124
|
+
])if (Object.prototype.hasOwnProperty.call(entry, key) && (null === entry[key] || 'object' != typeof entry[key] || Array.isArray(entry[key]))) throw new Error(`[rslint] Config entry at index ${index}: "${key}" must be an object`);
|
|
125
|
+
if (isRecord(entry.rules)) validateRules(entry.rules, index);
|
|
126
|
+
const languageOptions = isRecord(entry.languageOptions) ? entry.languageOptions : void 0;
|
|
127
|
+
if (languageOptions && Object.prototype.hasOwnProperty.call(languageOptions, 'globals')) validateGlobals(languageOptions.globals, index);
|
|
128
|
+
const hasPlugins = Object.prototype.hasOwnProperty.call(entry, 'plugins');
|
|
129
|
+
if (hasPlugins && (null === entry.plugins || 'object' != typeof entry.plugins)) throw new Error(`[rslint] Config entry at index ${index}: "plugins" must be an object or an array of strings`);
|
|
130
|
+
if (Array.isArray(entry.plugins) && entry.plugins.some((plugin)=>'string' != typeof plugin)) throw new Error(`[rslint] Config entry at index ${index}: "plugins" must contain only strings`);
|
|
131
|
+
if (Object.prototype.hasOwnProperty.call(entry, 'name') && 'string' != typeof entry.name) throw new Error(`[rslint] Config entry at index ${index}: "name" must be a string`);
|
|
132
|
+
const pluginSource = selectPluginSource(entry);
|
|
133
|
+
const eslintPluginMeta = {};
|
|
134
|
+
const pluginPrefixes = [];
|
|
135
|
+
if (null != pluginSource) for (const prefix of Object.keys(pluginSource)){
|
|
136
|
+
if (NATIVE_PLUGIN_RESERVED_NAMES.has(prefix)) throw new Error(`[rslint] Config entry at index ${index}: plugins prefix "${prefix}" collides with the built-in plugin of the same name; choose a different prefix.`);
|
|
137
|
+
const plugin = unwrapPluginModule(pluginSource[prefix]);
|
|
138
|
+
const pluginRules = plugin?.rules;
|
|
139
|
+
if (null == pluginRules || 'object' != typeof pluginRules) throw new Error(`[rslint] Config entry at index ${index}: plugins["${prefix}"] must expose a "rules" object.`);
|
|
140
|
+
eslintPluginMeta[prefix] = {
|
|
141
|
+
ruleNames: Object.keys(pluginRules).sort()
|
|
142
|
+
};
|
|
143
|
+
pluginPrefixes.push(prefix);
|
|
144
|
+
}
|
|
145
|
+
const stringPlugins = Array.isArray(entry.plugins) ? entry.plugins.filter((p)=>'string' == typeof p) : [];
|
|
146
|
+
const plugins = [
|
|
147
|
+
...new Set([
|
|
148
|
+
...stringPlugins,
|
|
149
|
+
...pluginPrefixes
|
|
150
|
+
])
|
|
151
|
+
];
|
|
152
|
+
const authoredNonGlobalKey = Object.keys(entry).some((key)=>'name' !== key && 'ignores' !== key);
|
|
153
|
+
const serializesNonGlobalKey = hasFiles || void 0 !== entry.languageOptions || void 0 !== entry.rules || hasPlugins || void 0 !== entry.settings;
|
|
154
|
+
const needsNonGlobalShapeMarker = authoredNonGlobalKey && !serializesNonGlobalKey;
|
|
155
|
+
return {
|
|
156
|
+
...void 0 !== entry.name ? {
|
|
157
|
+
name: entry.name
|
|
158
|
+
} : {},
|
|
159
|
+
...hasFiles ? {
|
|
160
|
+
files: entry.files
|
|
161
|
+
} : {},
|
|
162
|
+
...void 0 !== entry.ignores ? {
|
|
163
|
+
ignores: entry.ignores
|
|
164
|
+
} : {},
|
|
165
|
+
...void 0 !== entry.languageOptions ? {
|
|
166
|
+
languageOptions: entry.languageOptions
|
|
167
|
+
} : {},
|
|
168
|
+
...void 0 !== entry.rules ? {
|
|
169
|
+
rules: entry.rules
|
|
170
|
+
} : {},
|
|
171
|
+
...hasPlugins ? {
|
|
172
|
+
plugins
|
|
173
|
+
} : {},
|
|
174
|
+
...void 0 !== entry.settings ? {
|
|
175
|
+
settings: entry.settings
|
|
176
|
+
} : needsNonGlobalShapeMarker ? {
|
|
177
|
+
settings: {}
|
|
178
|
+
} : {},
|
|
179
|
+
...pluginPrefixes.length > 0 ? {
|
|
180
|
+
eslintPlugins: eslintPluginMeta
|
|
181
|
+
} : {}
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const GLOBAL_ACCESS_VALUES = new Set([
|
|
186
|
+
true,
|
|
187
|
+
'true',
|
|
188
|
+
'writable',
|
|
189
|
+
'writeable',
|
|
190
|
+
false,
|
|
191
|
+
'false',
|
|
192
|
+
'readonly',
|
|
193
|
+
'readable',
|
|
194
|
+
null,
|
|
195
|
+
'off'
|
|
196
|
+
]);
|
|
197
|
+
const RULE_SEVERITIES = new Set([
|
|
198
|
+
'off',
|
|
199
|
+
'warn',
|
|
200
|
+
'error',
|
|
201
|
+
0,
|
|
202
|
+
1,
|
|
203
|
+
2
|
|
204
|
+
]);
|
|
205
|
+
function validateRules(rules, entryIndex) {
|
|
206
|
+
for (const [name, value] of Object.entries(rules)){
|
|
207
|
+
const severity = Array.isArray(value) ? value[0] : value;
|
|
208
|
+
if (Array.isArray(value) && 0 === value.length || !RULE_SEVERITIES.has(severity)) throw new Error(`[rslint] Config entry at index ${entryIndex}: rule "${name}" must use severity 'off', 'warn', 'error', 0, 1, or 2`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function validateGlobals(value, entryIndex) {
|
|
212
|
+
if (null === value || 'object' != typeof value || Array.isArray(value)) throw new Error(`[rslint] Config entry at index ${entryIndex}: "languageOptions.globals" must be an object`);
|
|
213
|
+
for (const [name, access] of Object.entries(value)){
|
|
214
|
+
if (name !== name.trim()) throw new Error(`[rslint] Config entry at index ${entryIndex}: global "${name}" has leading or trailing whitespace`);
|
|
215
|
+
if (!GLOBAL_ACCESS_VALUES.has(access)) throw new Error(`[rslint] Config entry at index ${entryIndex}: global "${name}" must be "readonly", "writable", or "off"`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function collectPluginMeta(configs) {
|
|
219
|
+
const isPluginMetaMap = (v)=>null !== v && 'object' == typeof v;
|
|
220
|
+
const byPrefix = new Map();
|
|
221
|
+
const pluginConfigs = [];
|
|
222
|
+
for (const c of configs){
|
|
223
|
+
let hasPlugins = false;
|
|
224
|
+
for (const entry of c.entries){
|
|
225
|
+
if (null === entry || 'object' != typeof entry || !('eslintPlugins' in entry)) continue;
|
|
226
|
+
const ep = entry.eslintPlugins;
|
|
227
|
+
if (isPluginMetaMap(ep)) for (const [prefix, meta] of Object.entries(ep)){
|
|
228
|
+
hasPlugins = true;
|
|
229
|
+
const existing = byPrefix.get(prefix);
|
|
230
|
+
if (existing) {
|
|
231
|
+
for (const name of meta.ruleNames)if (!existing.includes(name)) existing.push(name);
|
|
232
|
+
} else byPrefix.set(prefix, [
|
|
233
|
+
...meta.ruleNames
|
|
234
|
+
]);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (hasPlugins) pluginConfigs.push({
|
|
238
|
+
configPath: c.configPath,
|
|
239
|
+
configDirectory: c.configDirectory
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
const eslintPluginEntries = [
|
|
243
|
+
...byPrefix.entries()
|
|
244
|
+
].map(([prefix, ruleNames])=>({
|
|
245
|
+
prefix,
|
|
246
|
+
ruleNames
|
|
247
|
+
}));
|
|
248
|
+
return {
|
|
249
|
+
eslintPluginEntries,
|
|
250
|
+
pluginConfigs
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
class ConfigSourceChangedError extends Error {
|
|
254
|
+
code = 'CONFIG_CHANGED_DURING_LOAD';
|
|
255
|
+
constructor(configPath, phase = 'load'){
|
|
256
|
+
super('load' === phase ? `config changed while it was being loaded: ${configPath}` : `config changed while its plugin host was being prepared: ${configPath}`);
|
|
257
|
+
this.name = 'ConfigSourceChangedError';
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function assertNotAborted(signal) {
|
|
261
|
+
if (!signal?.aborted) return;
|
|
262
|
+
if (signal.reason instanceof Error) throw signal.reason;
|
|
263
|
+
throw new Error('config module operation was aborted');
|
|
264
|
+
}
|
|
265
|
+
function protocolError(message) {
|
|
266
|
+
return new Error(`config module protocol: ${message}`);
|
|
267
|
+
}
|
|
268
|
+
function isConfigEntry(value) {
|
|
269
|
+
return null !== value && 'object' == typeof value && !Array.isArray(value);
|
|
270
|
+
}
|
|
271
|
+
function normalizeCandidate(candidate) {
|
|
272
|
+
if (!candidate || 'object' != typeof candidate) throw protocolError('candidate must be an object');
|
|
273
|
+
if ('string' != typeof candidate.id || 0 === candidate.id.length) throw protocolError('candidate id must be a non-empty string');
|
|
274
|
+
if ('string' != typeof candidate.configPath || !node_path.isAbsolute(candidate.configPath)) throw protocolError(`candidate ${JSON.stringify(candidate.id)} configPath must be absolute`);
|
|
275
|
+
if ('string' != typeof candidate.configDirectory || !node_path.isAbsolute(candidate.configDirectory)) throw protocolError(`candidate ${JSON.stringify(candidate.id)} configDirectory must be absolute`);
|
|
276
|
+
return {
|
|
277
|
+
id: candidate.id,
|
|
278
|
+
configPath: node_path.normalize(candidate.configPath),
|
|
279
|
+
configDirectory: candidate.configDirectory
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function cloneEntries(entriesJSON) {
|
|
283
|
+
const entries = JSON.parse(entriesJSON);
|
|
284
|
+
if (!Array.isArray(entries) || !entries.every(isConfigEntry)) throw protocolError('stored config entries are malformed');
|
|
285
|
+
return entries;
|
|
286
|
+
}
|
|
287
|
+
function clonePluginEntries(entries) {
|
|
288
|
+
return entries.map(({ prefix, ruleNames })=>({
|
|
289
|
+
prefix,
|
|
290
|
+
ruleNames: [
|
|
291
|
+
...ruleNames
|
|
292
|
+
]
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
function cloneStoredResult(result) {
|
|
296
|
+
if ('loaded' === result.status) return {
|
|
297
|
+
id: result.id,
|
|
298
|
+
status: 'loaded',
|
|
299
|
+
entries: cloneEntries(result.entriesJSON)
|
|
300
|
+
};
|
|
301
|
+
return {
|
|
302
|
+
id: result.id,
|
|
303
|
+
status: 'failed',
|
|
304
|
+
error: {
|
|
305
|
+
...result.error
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function errorPayload(error, fallbackCode) {
|
|
310
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
311
|
+
let code = fallbackCode;
|
|
312
|
+
if (null !== error && 'object' == typeof error && 'code' in error) {
|
|
313
|
+
const candidateCode = error.code;
|
|
314
|
+
if ('string' == typeof candidateCode && candidateCode.length > 0) code = candidateCode;
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
message,
|
|
318
|
+
...code ? {
|
|
319
|
+
code
|
|
320
|
+
} : {}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
class ConfigModuleHost {
|
|
324
|
+
#loadCached;
|
|
325
|
+
#loadFresh;
|
|
326
|
+
#readSource;
|
|
327
|
+
#sessions = new Map();
|
|
328
|
+
constructor(options = {}){
|
|
329
|
+
this.#loadCached = options.loadCached ?? loadConfigFile;
|
|
330
|
+
this.#loadFresh = options.loadFresh ?? loadConfigFileFresh;
|
|
331
|
+
this.#readSource = options.readSource ?? readFile;
|
|
332
|
+
}
|
|
333
|
+
async loadConfigs(request, signal) {
|
|
334
|
+
this.#validateRequest(request);
|
|
335
|
+
const candidates = request.candidates.map(normalizeCandidate);
|
|
336
|
+
const session = this.#getOrCreateSession(request.transactionId, request.loadMode, true === request.singleThreaded);
|
|
337
|
+
return this.#enqueue(session, async ()=>{
|
|
338
|
+
assertNotAborted(signal);
|
|
339
|
+
this.#validateCandidateBatch(session, candidates);
|
|
340
|
+
const newCandidates = candidates.filter(({ id })=>!session.candidatesById.has(id));
|
|
341
|
+
let loaded;
|
|
342
|
+
if (request.singleThreaded) {
|
|
343
|
+
loaded = [];
|
|
344
|
+
for (const candidate of newCandidates)loaded.push(await this.#loadCandidate(candidate, session.loadMode));
|
|
345
|
+
} else loaded = await Promise.all(newCandidates.map(async (candidate)=>{
|
|
346
|
+
const result = await this.#loadCandidate(candidate, session.loadMode);
|
|
347
|
+
return result;
|
|
348
|
+
}));
|
|
349
|
+
assertNotAborted(signal);
|
|
350
|
+
for(let index = 0; index < newCandidates.length; index++){
|
|
351
|
+
const candidate = newCandidates[index];
|
|
352
|
+
const result = loaded[index];
|
|
353
|
+
session.candidatesById.set(candidate.id, {
|
|
354
|
+
candidate,
|
|
355
|
+
result
|
|
356
|
+
});
|
|
357
|
+
session.idByConfigPath.set(candidate.configPath, candidate.id);
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
transactionId: request.transactionId,
|
|
361
|
+
results: candidates.map(({ id })=>{
|
|
362
|
+
const stored = session.candidatesById.get(id);
|
|
363
|
+
if (!stored) throw protocolError(`candidate ${JSON.stringify(id)} was not loaded`);
|
|
364
|
+
return cloneStoredResult(stored.result);
|
|
365
|
+
})
|
|
366
|
+
};
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
async #summarizeEffectiveConfigs(transactionId, effectiveConfigIds, signal) {
|
|
370
|
+
const session = this.#sessions.get(transactionId);
|
|
371
|
+
if (!session) throw protocolError(`unknown transaction ${JSON.stringify(transactionId)}`);
|
|
372
|
+
return this.#enqueue(session, async ()=>{
|
|
373
|
+
assertNotAborted(signal);
|
|
374
|
+
const stored = this.#effectiveCandidates(session, effectiveConfigIds);
|
|
375
|
+
await this.#verifyFingerprints(stored, 'load', signal);
|
|
376
|
+
const configs = stored.map(({ candidate, result })=>({
|
|
377
|
+
id: candidate.id,
|
|
378
|
+
configPath: candidate.configPath,
|
|
379
|
+
configDirectory: candidate.configDirectory,
|
|
380
|
+
entries: cloneEntries(result.entriesJSON),
|
|
381
|
+
sourceFingerprint: result.sourceFingerprint
|
|
382
|
+
}));
|
|
383
|
+
const { eslintPluginEntries, pluginConfigs } = collectPluginMeta(configs);
|
|
384
|
+
return {
|
|
385
|
+
transactionId,
|
|
386
|
+
configs,
|
|
387
|
+
eslintPluginEntries: clonePluginEntries(eslintPluginEntries),
|
|
388
|
+
pluginConfigs: pluginConfigs.map((descriptor)=>({
|
|
389
|
+
...descriptor
|
|
390
|
+
}))
|
|
391
|
+
};
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
async activateConfigs(request, signal, prepare) {
|
|
395
|
+
this.#validateActivationRequest(request);
|
|
396
|
+
const plan = await this.#summarizeEffectiveConfigs(request.transactionId, request.effectiveConfigIds, signal);
|
|
397
|
+
const response = {
|
|
398
|
+
transactionId: plan.transactionId,
|
|
399
|
+
eslintPluginEntries: clonePluginEntries(plan.eslintPluginEntries)
|
|
400
|
+
};
|
|
401
|
+
assertNotAborted(signal);
|
|
402
|
+
await prepare?.(plan);
|
|
403
|
+
assertNotAborted(signal);
|
|
404
|
+
await this.#verifyEffectiveConfigs(request, signal);
|
|
405
|
+
return response;
|
|
406
|
+
}
|
|
407
|
+
async #verifyEffectiveConfigs(request, signal) {
|
|
408
|
+
this.#validateActivationRequest(request);
|
|
409
|
+
const session = this.#sessions.get(request.transactionId);
|
|
410
|
+
if (!session) throw protocolError(`unknown transaction ${JSON.stringify(request.transactionId)}`);
|
|
411
|
+
await this.#enqueue(session, async ()=>{
|
|
412
|
+
assertNotAborted(signal);
|
|
413
|
+
const stored = this.#effectiveCandidates(session, request.effectiveConfigIds);
|
|
414
|
+
await this.#verifyFingerprints(stored, 'plugin-prepare', signal);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
deleteSession(transactionId) {
|
|
418
|
+
return this.#sessions.delete(transactionId);
|
|
419
|
+
}
|
|
420
|
+
#validateRequest(request) {
|
|
421
|
+
if (!request || 'object' != typeof request) throw protocolError('load request must be an object');
|
|
422
|
+
if (1 !== request.protocolVersion) throw protocolError(`unsupported version ${String(request.protocolVersion)}; expected 1`);
|
|
423
|
+
if ('string' != typeof request.transactionId || 0 === request.transactionId.length) throw protocolError('transactionId must be a non-empty string');
|
|
424
|
+
if ('cached' !== request.loadMode && 'fresh' !== request.loadMode) throw protocolError(`unsupported loadMode ${String(request.loadMode)}`);
|
|
425
|
+
if (void 0 !== request.singleThreaded && 'boolean' != typeof request.singleThreaded) throw protocolError('singleThreaded must be a boolean when provided');
|
|
426
|
+
if (!Array.isArray(request.candidates)) throw protocolError('candidates must be an array');
|
|
427
|
+
}
|
|
428
|
+
#validateActivationRequest(request) {
|
|
429
|
+
if (!request || 'object' != typeof request) throw protocolError('activation request must be an object');
|
|
430
|
+
if (1 !== request.protocolVersion) throw protocolError(`unsupported version ${String(request.protocolVersion)}; expected 1`);
|
|
431
|
+
if ('string' != typeof request.transactionId || 0 === request.transactionId.length) throw protocolError('transactionId must be a non-empty string');
|
|
432
|
+
if (!Array.isArray(request.effectiveConfigIds)) throw protocolError('effectiveConfigIds must be an array');
|
|
433
|
+
}
|
|
434
|
+
#getOrCreateSession(transactionId, loadMode, singleThreaded) {
|
|
435
|
+
const existing = this.#sessions.get(transactionId);
|
|
436
|
+
if (existing) {
|
|
437
|
+
if (existing.loadMode !== loadMode) throw protocolError(`transaction ${JSON.stringify(transactionId)} cannot change loadMode from ${existing.loadMode} to ${loadMode}`);
|
|
438
|
+
if (existing.singleThreaded !== singleThreaded) throw protocolError(`transaction ${JSON.stringify(transactionId)} cannot change singleThreaded mode`);
|
|
439
|
+
return existing;
|
|
440
|
+
}
|
|
441
|
+
const session = {
|
|
442
|
+
loadMode,
|
|
443
|
+
singleThreaded,
|
|
444
|
+
candidatesById: new Map(),
|
|
445
|
+
idByConfigPath: new Map(),
|
|
446
|
+
operation: Promise.resolve()
|
|
447
|
+
};
|
|
448
|
+
this.#sessions.set(transactionId, session);
|
|
449
|
+
return session;
|
|
450
|
+
}
|
|
451
|
+
#validateCandidateBatch(session, candidates) {
|
|
452
|
+
const ids = new Set();
|
|
453
|
+
const paths = new Map();
|
|
454
|
+
for (const candidate of candidates){
|
|
455
|
+
if (ids.has(candidate.id)) throw protocolError(`candidate batch contains duplicate id ${JSON.stringify(candidate.id)}`);
|
|
456
|
+
ids.add(candidate.id);
|
|
457
|
+
const batchOwner = paths.get(candidate.configPath);
|
|
458
|
+
if (batchOwner && batchOwner !== candidate.id) throw protocolError(`candidate IDs ${JSON.stringify(batchOwner)} and ${JSON.stringify(candidate.id)} use the same configPath`);
|
|
459
|
+
paths.set(candidate.configPath, candidate.id);
|
|
460
|
+
const existing = session.candidatesById.get(candidate.id);
|
|
461
|
+
if (existing && (existing.candidate.configPath !== candidate.configPath || existing.candidate.configDirectory !== candidate.configDirectory)) throw protocolError(`candidate id ${JSON.stringify(candidate.id)} changed path or directory within one transaction`);
|
|
462
|
+
const pathOwner = session.idByConfigPath.get(candidate.configPath);
|
|
463
|
+
if (pathOwner && pathOwner !== candidate.id) throw protocolError(`candidate IDs ${JSON.stringify(pathOwner)} and ${JSON.stringify(candidate.id)} use the same configPath`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
#effectiveCandidates(session, effectiveConfigIds) {
|
|
467
|
+
const seen = new Set();
|
|
468
|
+
return effectiveConfigIds.map((id)=>{
|
|
469
|
+
if (seen.has(id)) throw protocolError(`effective config list contains duplicate id ${JSON.stringify(id)}`);
|
|
470
|
+
seen.add(id);
|
|
471
|
+
const candidate = session.candidatesById.get(id);
|
|
472
|
+
if (!candidate) throw protocolError(`unknown effective config id ${JSON.stringify(id)}`);
|
|
473
|
+
if ('loaded' !== candidate.result.status) throw protocolError(`effective config ${JSON.stringify(id)} did not load successfully`);
|
|
474
|
+
return {
|
|
475
|
+
candidate: candidate.candidate,
|
|
476
|
+
result: candidate.result
|
|
477
|
+
};
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
async #verifyFingerprints(stored, phase, signal) {
|
|
481
|
+
const currentFingerprints = await Promise.all(stored.map(async ({ candidate })=>{
|
|
482
|
+
const fingerprint = await this.#fingerprint(candidate.configPath);
|
|
483
|
+
return fingerprint;
|
|
484
|
+
}));
|
|
485
|
+
assertNotAborted(signal);
|
|
486
|
+
for(let index = 0; index < stored.length; index++)if (currentFingerprints[index] !== stored[index].result.sourceFingerprint) throw new ConfigSourceChangedError(stored[index].candidate.configPath, phase);
|
|
487
|
+
}
|
|
488
|
+
async #loadCandidate(candidate, loadMode) {
|
|
489
|
+
let failureCode = 'load';
|
|
490
|
+
try {
|
|
491
|
+
const sourceFingerprint = await this.#fingerprint(candidate.configPath);
|
|
492
|
+
const rawConfig = await ('fresh' === loadMode ? this.#loadFresh(candidate.configPath) : this.#loadCached(candidate.configPath));
|
|
493
|
+
failureCode = 'invalid';
|
|
494
|
+
const normalized = normalizeConfig(rawConfig);
|
|
495
|
+
const entriesJSON = JSON.stringify(normalized);
|
|
496
|
+
const afterFingerprint = await this.#fingerprint(candidate.configPath);
|
|
497
|
+
if (sourceFingerprint !== afterFingerprint) throw new ConfigSourceChangedError(candidate.configPath);
|
|
498
|
+
return {
|
|
499
|
+
id: candidate.id,
|
|
500
|
+
status: 'loaded',
|
|
501
|
+
entriesJSON,
|
|
502
|
+
sourceFingerprint: afterFingerprint
|
|
503
|
+
};
|
|
504
|
+
} catch (error) {
|
|
505
|
+
return {
|
|
506
|
+
id: candidate.id,
|
|
507
|
+
status: 'failed',
|
|
508
|
+
error: errorPayload(error, failureCode)
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
async #fingerprint(configPath) {
|
|
513
|
+
const contents = await this.#readSource(configPath);
|
|
514
|
+
return `${contents.byteLength}:${createHash('sha256').update(contents).digest('hex')}`;
|
|
515
|
+
}
|
|
516
|
+
async #enqueue(session, operation) {
|
|
517
|
+
const result = session.operation.then(operation, operation);
|
|
518
|
+
session.operation = result.then(()=>void 0, ()=>void 0);
|
|
519
|
+
const value = await result;
|
|
520
|
+
return value;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function isGlobalIgnoreEntry(entry) {
|
|
524
|
+
if (null === entry || 'object' != typeof entry || Array.isArray(entry)) return false;
|
|
525
|
+
const ignores = 'ignores' in entry ? entry.ignores : void 0;
|
|
526
|
+
if (!Array.isArray(ignores) || 0 === ignores.length || ignores.some((pattern)=>'string' != typeof pattern)) return false;
|
|
527
|
+
return Object.keys(entry).every((key)=>'ignores' === key || 'name' === key);
|
|
528
|
+
}
|
|
529
|
+
function getGlobalIgnores(entries) {
|
|
530
|
+
const patterns = [];
|
|
531
|
+
for (const entry of entries)if (isGlobalIgnoreEntry(entry)) patterns.push(...entry.ignores);
|
|
532
|
+
return patterns;
|
|
533
|
+
}
|
|
534
|
+
function normalizeGlobPattern(pattern) {
|
|
535
|
+
const normalized = node_path.posix.normalize(pattern.replaceAll('\\', '/'));
|
|
536
|
+
return '.' === normalized ? '' : normalized.replace(/^\.\//, '');
|
|
537
|
+
}
|
|
538
|
+
function isDirIgnoredByPatterns(dirPath, patterns, parentConfigDir) {
|
|
539
|
+
const relDir = node_path.relative(parentConfigDir, dirPath);
|
|
540
|
+
if (!isRelativeChildPath(relDir)) return false;
|
|
541
|
+
const segments = relDir.split(node_path.sep).filter(Boolean);
|
|
542
|
+
let current = '';
|
|
543
|
+
for (const segment of segments){
|
|
544
|
+
current = current ? `${current}/${segment}` : segment;
|
|
545
|
+
let ignored = false;
|
|
546
|
+
for (const rawPattern of patterns){
|
|
547
|
+
if (!rawPattern) continue;
|
|
548
|
+
const negated = rawPattern.startsWith('!');
|
|
549
|
+
if (negated !== ignored) continue;
|
|
550
|
+
const pattern = normalizeGlobPattern(negated ? rawPattern.slice(1) : rawPattern);
|
|
551
|
+
if (!pattern) continue;
|
|
552
|
+
const isMatch = picomatch(pattern, {
|
|
553
|
+
dot: true
|
|
554
|
+
});
|
|
555
|
+
if (isMatch(current) || isMatch(`${current}/`)) ignored = !negated;
|
|
556
|
+
}
|
|
557
|
+
if (ignored) return true;
|
|
558
|
+
}
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
function isRelativeChildPath(relPath) {
|
|
562
|
+
return '' !== relPath && '..' !== relPath && !relPath.startsWith(`..${node_path.sep}`) && !node_path.isAbsolute(relPath);
|
|
563
|
+
}
|
|
564
|
+
function isSameOrChildDirectory(parentDir, childDir) {
|
|
565
|
+
const parent = node_path.normalize(parentDir);
|
|
566
|
+
const child = node_path.normalize(childDir);
|
|
567
|
+
if (parent === child) return true;
|
|
568
|
+
const prefix = parent.endsWith(node_path.sep) ? parent : `${parent}${node_path.sep}`;
|
|
569
|
+
return child.startsWith(prefix);
|
|
570
|
+
}
|
|
571
|
+
function normalizeConfigDirectory(configDirectory) {
|
|
572
|
+
let dir = configDirectory;
|
|
573
|
+
const root = node_path.parse(dir).root;
|
|
574
|
+
while(dir.length > root.length && /[/\\]$/.test(dir))dir = dir.slice(0, -1);
|
|
575
|
+
return node_path.normalize(dir);
|
|
576
|
+
}
|
|
577
|
+
function filterConfigsByParentIgnores(configEntries, forceIncludeConfigDirectories = new Set()) {
|
|
578
|
+
if (configEntries.length <= 1) return configEntries;
|
|
579
|
+
const forceIncludedDirs = new Set([
|
|
580
|
+
...forceIncludeConfigDirectories
|
|
581
|
+
].map(normalizeConfigDirectory));
|
|
582
|
+
const normalizedEntries = configEntries.map((config)=>({
|
|
583
|
+
config,
|
|
584
|
+
directory: normalizeConfigDirectory(config.configDirectory)
|
|
585
|
+
})).sort((a, b)=>a.directory.length - b.directory.length);
|
|
586
|
+
const result = [];
|
|
587
|
+
for (const entry of normalizedEntries){
|
|
588
|
+
let ignored = false;
|
|
589
|
+
let nearestParent;
|
|
590
|
+
for (const parent of result)if (isSameOrChildDirectory(parent.directory, entry.directory)) {
|
|
591
|
+
if (void 0 === nearestParent || parent.directory.length > nearestParent.directory.length) nearestParent = parent;
|
|
592
|
+
}
|
|
593
|
+
if (void 0 !== nearestParent) {
|
|
594
|
+
const globalIgnores = getGlobalIgnores(nearestParent.config.entries);
|
|
595
|
+
ignored = globalIgnores.length > 0 && isDirIgnoredByPatterns(entry.directory, globalIgnores, nearestParent.directory);
|
|
596
|
+
}
|
|
597
|
+
if (!ignored || forceIncludedDirs.has(entry.directory)) result.push(entry);
|
|
598
|
+
}
|
|
599
|
+
return result.map(({ config })=>config);
|
|
600
|
+
}
|
|
601
|
+
export { ConfigModuleHost, collectPluginMeta, defineConfig, define_config_globalIgnores as globalIgnores, filterConfigsByParentIgnores, loadConfigFile, loadConfigFileFresh, normalizeConfig };
|
package/dist/770.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
const resolve_binary_require = createRequire(import.meta.url);
|
|
3
|
+
function resolveRslintBinary() {
|
|
4
|
+
const arch = process.arch;
|
|
5
|
+
const tuples = 'linux' === process.platform ? [
|
|
6
|
+
`linux-${arch}-gnu`,
|
|
7
|
+
`linux-${arch}-musl`
|
|
8
|
+
] : 'win32' === process.platform ? [
|
|
9
|
+
`win32-${arch}-msvc`
|
|
10
|
+
] : [
|
|
11
|
+
`${process.platform}-${arch}`
|
|
12
|
+
];
|
|
13
|
+
for (const tuple of tuples)try {
|
|
14
|
+
return resolve_binary_require.resolve(`@rslint/native-${tuple}/bin`);
|
|
15
|
+
} catch {}
|
|
16
|
+
throw new Error(`rslint: no native binary for ${process.platform}-${arch} (looked for @rslint/native-{${tuples.join(',')}})`);
|
|
17
|
+
}
|
|
18
|
+
export { resolveRslintBinary };
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
export declare function run(binPath: string, argv: string[], startTime: number): Promise<number>;
|
|
2
2
|
|
|
3
|
+
export declare function runCLI({ argv, }?: RunCLIOptions): Promise<void>;
|
|
4
|
+
|
|
5
|
+
export declare type RunCLIOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* The command-line arguments to parse, matching the shape of Node.js `process.argv`
|
|
8
|
+
* @default process.argv
|
|
9
|
+
*/
|
|
10
|
+
argv?: string[];
|
|
11
|
+
};
|
|
12
|
+
|
|
3
13
|
export { }
|