@rslint/core 0.6.5 → 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.
@@ -1,14 +1,28 @@
1
+ /** Go's final effective-ID selection after ignore/ownership resolution. */
2
+ export declare interface ActivateConfigsRequest {
3
+ protocolVersion: typeof CONFIG_DISCOVERY_PROTOCOL_VERSION;
4
+ transactionId: string;
5
+ effectiveConfigIds: string[];
6
+ }
7
+
8
+ /** Minimal activation payload returned across IPC/JSON-RPC to Go. */
9
+ export declare interface ActivateConfigsResponse {
10
+ transactionId: string;
11
+ eslintPluginEntries: ConfigModuleEslintPluginEntry[];
12
+ }
13
+
1
14
  /**
2
15
  * Derive, from normalized configs, the ESLint-plugin metadata the Go core
3
16
  * needs (`{prefix, ruleNames}` for placeholder rules) and the worker-pool
4
17
  * descriptors (only for configs that mount plugins — others stay
5
- * zero-overhead). Shared by the CLI (cli.ts) and the VS Code extension
6
- * (Rslint.ts) so both produce the identical `eslintPlugins` shape Go parses.
18
+ * zero-overhead). ConfigModuleHost invokes this only after Go selects the
19
+ * transaction's effective IDs, so discarded candidates cannot leak plugin
20
+ * placeholders or worker routes into the activated catalog.
7
21
  * ruleNames for a shared prefix are merged (set union) across configs: Go's
8
22
  * placeholder registry is a per-prefix superset, while the worker routes the
9
- * actual rules per-config. This is NOT a validation a genuinely conflicting
10
- * redefinition (same prefix, different plugin object) is caught at worker init
11
- * (plugin-loader throws ESLint's `Cannot redefine plugin` error), not here.
23
+ * actual rules per config. This is not the validation boundary: worker config
24
+ * loading rejects conflicting plugin definitions, including duplicate routing
25
+ * descriptors that mount different instances under the same prefix.
12
26
  */
13
27
  export declare function collectPluginMeta(configs: ReadonlyArray<{
14
28
  configPath: string;
@@ -23,13 +37,165 @@ export declare function collectPluginMeta(configs: ReadonlyArray<{
23
37
  };
24
38
 
25
39
  /**
26
- * Load a JS/TS config file.
27
- * - .js/.mjs: native import()
28
- * - .ts/.mts: native import() when Node.js has TypeScript support (>= 22.6),
29
- * otherwise fall back to jiti
40
+ * Logical protocol shared by the native CLI, programmatic API, and LSP config
41
+ * discovery adapters. The transports differ (framed IPC versus JSON-RPC), but
42
+ * every adapter sends these payloads to the same Node-side module host.
43
+ *
44
+ * Paths in requests identify values selected by Go. Node may native-normalize
45
+ * configPath for local I/O, but configDirectory is an opaque routing identity
46
+ * and must retain its exact spelling. Load responses correlate only by opaque
47
+ * candidate ID. Activation responses contain only the transaction identity and
48
+ * plugin metadata consumed by Go; Node-only preparation data never crosses the
49
+ * transport boundary.
50
+ */
51
+ export declare const CONFIG_DISCOVERY_PROTOCOL_VERSION: 1;
52
+
53
+ export declare interface ConfigEntry {
54
+ configDirectory: string;
55
+ entries: unknown[];
56
+ }
57
+
58
+ /**
59
+ * Node-local activation data derived from exactly the effective IDs selected
60
+ * by Go. It is exposed only to the prepare callback and is never serialized
61
+ * as the activation response.
62
+ */
63
+ export declare interface ConfigModuleActivationPlan {
64
+ transactionId: string;
65
+ configs: EffectiveConfigModule[];
66
+ eslintPluginEntries: ConfigModuleEslintPluginEntry[];
67
+ pluginConfigs: ConfigModulePluginDescriptor[];
68
+ }
69
+
70
+ export declare interface ConfigModuleCandidate {
71
+ /** Opaque, transaction-local identity allocated by the Go coordinator. */
72
+ id: string;
73
+ /** Absolute path to the JS/TS config module Node must execute. */
74
+ configPath: string;
75
+ /** Go-authoritative directory used for config matching and plugin routing. */
76
+ configDirectory: string;
77
+ }
78
+
79
+ export declare interface ConfigModuleEslintPluginEntry {
80
+ prefix: string;
81
+ ruleNames: string[];
82
+ }
83
+
84
+ /**
85
+ * Executes config modules on behalf of Go's discovery coordinator.
86
+ *
87
+ * A host may serve multiple concurrent discovery transactions. Operations in
88
+ * one transaction are serialized (frontier batches can safely build on earlier
89
+ * batches), while separate transactions remain independent. Call
90
+ * deleteSession after commit/abort so normalized entries do not remain live.
91
+ */
92
+ export declare class ConfigModuleHost {
93
+ #private;
94
+ constructor(options?: ConfigModuleHostOptions);
95
+ /** Load and normalize one discovery frontier without fail-fast evaluation. */
96
+ loadConfigs(request: LoadConfigsRequest, signal?: AbortSignal): Promise<LoadConfigsResponse>;
97
+ /**
98
+ * Protocol-facing activation transaction.
99
+ *
100
+ * Plugin workers re-import plugin-bearing config modules. The optional
101
+ * prepare callback therefore runs between two source-fingerprint checks.
102
+ * A caller cannot publish the normalized activation until every effective
103
+ * source still matches the bytes from which that activation was derived.
104
+ */
105
+ activateConfigs(request: ActivateConfigsRequest, signal?: AbortSignal, prepare?: (plan: ConfigModuleActivationPlan) => Promise<void>): Promise<ActivateConfigsResponse>;
106
+ /** Drop all normalized module state for a committed or aborted transaction. */
107
+ deleteSession(transactionId: string): boolean;
108
+ }
109
+
110
+ export declare interface ConfigModuleHostOptions {
111
+ /** Test/embedding seam; production defaults to loadConfigFile. */
112
+ loadCached?: ConfigModuleLoader;
113
+ /** Test/embedding seam; production defaults to loadConfigFileFresh. */
114
+ loadFresh?: ConfigModuleLoader;
115
+ /** Test/embedding seam; production defaults to fs.promises.readFile. */
116
+ readSource?: ConfigSourceReader;
117
+ }
118
+
119
+ declare type ConfigModuleLoader = (configPath: string) => Promise<unknown>;
120
+
121
+ export declare type ConfigModuleLoadMode = 'cached' | 'fresh';
122
+
123
+ export declare type ConfigModuleLoadResult = LoadedConfigModuleResult | FailedConfigModuleResult;
124
+
125
+ export declare type ConfigModulePluginDescriptor = PluginConfigDescriptor;
126
+
127
+ declare type ConfigSourceReader = (configPath: string) => Promise<Uint8Array>;
128
+
129
+ export declare interface EffectiveConfigModule {
130
+ id: string;
131
+ configPath: string;
132
+ configDirectory: string;
133
+ entries: Record<string, unknown>[];
134
+ sourceFingerprint: string;
135
+ }
136
+
137
+ export declare interface FailedConfigModuleResult {
138
+ id: string;
139
+ status: 'failed';
140
+ error: {
141
+ message: string;
142
+ code?: string;
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Filter loaded nested config candidates whose directory is covered by an
148
+ * ancestor config's global ignores. Candidate discovery and module loading are
149
+ * intentionally independent from this effective-catalog step.
150
+ *
151
+ * `forceIncludeConfigDirectories` is for explicit CLI file targets: ESLint
152
+ * resolves the nearest config for an explicit file even when parent directory
153
+ * traversal would have been blocked.
154
+ */
155
+ export declare function filterConfigsByParentIgnores<T extends ConfigEntry>(configEntries: T[], forceIncludeConfigDirectories?: Set<string>): T[];
156
+
157
+ /**
158
+ * Load a selected JS/TS config file.
159
+ * TypeScript modules use native import when Node.js supports type stripping
160
+ * and otherwise fall back to jiti. Other module formats use native import.
30
161
  */
31
162
  export declare function loadConfigFile(configPath: string): Promise<unknown>;
32
163
 
164
+ /**
165
+ * Load a config without reusing the config module from a previous reload.
166
+ * TypeScript loading selects native stripping or jiti before evaluation so a
167
+ * runtime exception from the config is propagated without executing it again.
168
+ */
169
+ export declare function loadConfigFileFresh(configPath: string): Promise<unknown>;
170
+
171
+ export declare interface LoadConfigsRequest {
172
+ protocolVersion: typeof CONFIG_DISCOVERY_PROTOCOL_VERSION;
173
+ /** Isolates batches belonging to one discovery transaction. */
174
+ transactionId: string;
175
+ /**
176
+ * `cached` preserves one-shot CLI module-import semantics. `fresh` is used
177
+ * by long-lived API and editor refreshes; it cache-busts the entry module,
178
+ * while static transitive imports retain Node's normal module cache.
179
+ */
180
+ loadMode: ConfigModuleLoadMode;
181
+ /** Serialize module evaluation when the CLI requested --singleThreaded. */
182
+ singleThreaded?: boolean;
183
+ candidates: ConfigModuleCandidate[];
184
+ }
185
+
186
+ export declare interface LoadConfigsResponse {
187
+ transactionId: string;
188
+ /** Results are in exactly the same order as request.candidates. */
189
+ results: ConfigModuleLoadResult[];
190
+ }
191
+
192
+ export declare interface LoadedConfigModuleResult {
193
+ id: string;
194
+ status: 'loaded';
195
+ /** Serializable normalizeConfig output. Live plugin objects never cross. */
196
+ entries: Record<string, unknown>[];
197
+ }
198
+
33
199
  /**
34
200
  * Validate and strip non-serializable fields from the config.
35
201
  */
@@ -1,161 +1,3 @@
1
- import node_path from "node:path";
2
- import { pathToFileURL } from "node:url";
3
- const NATIVE_PLUGINS = [
4
- "@typescript-eslint",
5
- 'import',
6
- 'jest',
7
- 'jsx-a11y',
8
- 'promise',
9
- 'react',
10
- 'react-hooks',
11
- 'unicorn'
12
- ];
13
- const NATIVE_PLUGIN_DECL_ALIASES = [
14
- 'eslint-plugin-import',
15
- 'eslint-plugin-jest',
16
- 'eslint-plugin-jsx-a11y',
17
- 'eslint-plugin-promise',
18
- 'eslint-plugin-react-hooks',
19
- 'eslint-plugin-unicorn'
20
- ];
21
- const NATIVE_PLUGIN_RESERVED_NAMES = new Set([
22
- ...NATIVE_PLUGINS,
23
- ...NATIVE_PLUGIN_DECL_ALIASES
24
- ]);
25
- function defineConfig(config) {
26
- return config;
27
- }
28
- function globalIgnores(ignorePatterns) {
29
- if (!Array.isArray(ignorePatterns)) throw new TypeError('ignorePatterns must be an array');
30
- if (0 === ignorePatterns.length) throw new TypeError('ignorePatterns must contain at least one pattern');
31
- return {
32
- ignores: ignorePatterns
33
- };
34
- }
35
- function selectPluginSource(entry) {
36
- if (null == entry || 'object' != typeof entry) return null;
37
- const e = entry;
38
- if (null != e.plugins && 'object' == typeof e.plugins && !Array.isArray(e.plugins)) return e.plugins;
39
- return null;
40
- }
41
- function unwrapPluginModule(mod) {
42
- if (null == mod || 'object' != typeof mod) return null;
43
- const m = mod;
44
- if (null != m.default && 'object' == typeof m.default) return m.default;
45
- return mod;
46
- }
47
- async function loadConfigFile(configPath) {
48
- const ext = node_path.extname(configPath);
49
- if ('.js' === ext || '.mjs' === ext) {
50
- const mod = await import(pathToFileURL(configPath).href);
51
- return mod.default ?? mod;
52
- }
53
- if ('.ts' === ext || '.mts' === ext) {
54
- const useNative = Boolean(process.features.typescript);
55
- if (useNative) {
56
- const mod = await import(pathToFileURL(configPath).href);
57
- return mod.default ?? mod;
58
- }
59
- const jiti = await loadJiti(configPath);
60
- if (jiti) {
61
- const resolved = await jiti.import(configPath);
62
- return extractDefault(resolved);
63
- }
64
- throw new Error(`Failed to load TypeScript config file: ${configPath}\nTo load .ts 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`);
65
- }
66
- throw new Error(`Unsupported config file extension: ${ext}`);
67
- }
68
- async function loadJiti(configPath) {
69
- try {
70
- const { createJiti } = await import("jiti");
71
- return createJiti(node_path.dirname(configPath), {
72
- interopDefault: true
73
- });
74
- } catch {
75
- return null;
76
- }
77
- }
78
- function extractDefault(mod) {
79
- if ('object' == typeof mod && null !== mod && 'default' in mod) return mod.default;
80
- return mod;
81
- }
82
- function normalizeConfig(config) {
83
- if (!Array.isArray(config)) throw new Error(`rslint config must export an array (flat config format), got ${typeof config}`);
84
- return config.filter((entry, index)=>{
85
- if (null == entry || 'object' != typeof entry) {
86
- console.warn(`[rslint] Config entry at index ${index} is not an object (got ${null === entry ? 'null' : typeof entry}), skipping.`);
87
- return false;
88
- }
89
- return true;
90
- }).map((entry, index)=>{
91
- if (null != entry.files && !Array.isArray(entry.files)) throw new Error(`[rslint] Config entry at index ${index}: "files" must be an array, got ${typeof entry.files}`);
92
- if (null != entry.ignores && !Array.isArray(entry.ignores)) throw new Error(`[rslint] Config entry at index ${index}: "ignores" must be an array, got ${typeof entry.ignores}`);
93
- const pluginSource = selectPluginSource(entry);
94
- const eslintPluginMeta = {};
95
- const pluginPrefixes = [];
96
- if (null != pluginSource) for (const prefix of Object.keys(pluginSource)){
97
- 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.`);
98
- const plugin = unwrapPluginModule(pluginSource[prefix]);
99
- const pluginRules = plugin?.rules;
100
- if (null == pluginRules || 'object' != typeof pluginRules) throw new Error(`[rslint] Config entry at index ${index}: plugins["${prefix}"] must expose a "rules" object.`);
101
- eslintPluginMeta[prefix] = {
102
- ruleNames: Object.keys(pluginRules).sort()
103
- };
104
- pluginPrefixes.push(prefix);
105
- }
106
- const stringPlugins = Array.isArray(entry.plugins) ? entry.plugins.filter((p)=>'string' == typeof p) : [];
107
- const plugins = [
108
- ...new Set([
109
- ...stringPlugins,
110
- ...pluginPrefixes
111
- ])
112
- ];
113
- return {
114
- files: entry.files,
115
- ignores: entry.ignores,
116
- languageOptions: entry.languageOptions,
117
- rules: entry.rules,
118
- plugins,
119
- settings: entry.settings,
120
- ...pluginPrefixes.length > 0 ? {
121
- eslintPlugins: eslintPluginMeta
122
- } : {}
123
- };
124
- });
125
- }
126
- function collectPluginMeta(configs) {
127
- const isPluginMetaMap = (v)=>null !== v && 'object' == typeof v;
128
- const byPrefix = new Map();
129
- const pluginConfigs = [];
130
- for (const c of configs){
131
- let hasPlugins = false;
132
- for (const entry of c.entries){
133
- if (null === entry || 'object' != typeof entry || !('eslintPlugins' in entry)) continue;
134
- const ep = entry.eslintPlugins;
135
- if (isPluginMetaMap(ep)) for (const [prefix, meta] of Object.entries(ep)){
136
- hasPlugins = true;
137
- const existing = byPrefix.get(prefix);
138
- if (existing) {
139
- for (const name of meta.ruleNames)if (!existing.includes(name)) existing.push(name);
140
- } else byPrefix.set(prefix, [
141
- ...meta.ruleNames
142
- ]);
143
- }
144
- }
145
- if (hasPlugins) pluginConfigs.push({
146
- configPath: c.configPath,
147
- configDirectory: c.configDirectory
148
- });
149
- }
150
- const eslintPluginEntries = [
151
- ...byPrefix.entries()
152
- ].map(([prefix, ruleNames])=>({
153
- prefix,
154
- ruleNames
155
- }));
156
- return {
157
- eslintPluginEntries,
158
- pluginConfigs
159
- };
160
- }
161
- export { collectPluginMeta, defineConfig, globalIgnores, loadConfigFile, normalizeConfig };
1
+ var config_loader_CONFIG_DISCOVERY_PROTOCOL_VERSION = 1;
2
+ export { ConfigModuleHost, collectPluginMeta, filterConfigsByParentIgnores, loadConfigFile, loadConfigFileFresh, normalizeConfig } from "./519.js";
3
+ export { config_loader_CONFIG_DISCOVERY_PROTOCOL_VERSION as CONFIG_DISCOVERY_PROTOCOL_VERSION };
@@ -40,12 +40,6 @@ declare interface Comment {
40
40
  loc: SourceLocation;
41
41
  }
42
42
 
43
- /**
44
- * Types shared between the eslint-plugin host and its lint workers.
45
- *
46
- * Wire-format / IPC frame types (the Go↔Node frame contract) live in
47
- * `src/ipc/protocol.ts` — the single source the CLI host consumes.
48
- */
49
43
  /**
50
44
  * Per-config descriptor handed to the worker pool. Each worker imports
51
45
  * every descriptor's `configPath` once at init, then routes per-file
@@ -55,10 +49,11 @@ declare interface Comment {
55
49
  * it as a Map key for per-file dispatch.
56
50
  */
57
51
  export declare interface ConfigDescriptor {
58
- /** Absolute filesystem path of the rslint config file (`rslint.config.{js,mjs,ts,mts}`). */
52
+ /** Absolute filesystem path of the selected JS/TS config file. */
59
53
  configPath: string;
60
- /** Absolute filesystem path of the directory holding the config file.
61
- * Matches the `ConfigKey` Go emits per file during plugin-lint dispatch. */
54
+ /** Go-authoritative absolute matching/routing directory. In explicit mode
55
+ * this can differ from the config file's parent and MUST byte-match the
56
+ * `ConfigKey` Go emits during plugin-lint dispatch. */
62
57
  configDirectory: string;
63
58
  }
64
59
 
@@ -101,6 +96,8 @@ export declare interface Diagnostic {
101
96
  * doesn't need to re-validate Go's serialization.
102
97
  */
103
98
  export declare interface EslintPluginLintRequest {
99
+ /** LSP config/worker generation. Omitted by the CLI's single-host path. */
100
+ generation?: string;
104
101
  files: ReadonlyArray<{
105
102
  path: string;
106
103
  /**
@@ -119,8 +116,8 @@ export declare interface EslintPluginLintRequest {
119
116
  languageOptions?: unknown;
120
117
  settings?: Record<string, unknown>;
121
118
  /**
122
- * The owning config's directory in the SAME form the host used as
123
- * its `ConfigDescriptor.configDirectory` (CLI: fs path; LSP: URI).
119
+ * The owning config's absolute filesystem directory in the same form the
120
+ * host used as its `ConfigDescriptor.configDirectory`.
124
121
  * The worker uses it to pick the right `LoadedPlugins`. Empty when
125
122
  * no JS config governs the file.
126
123
  */
@@ -192,6 +189,21 @@ declare interface Fix {
192
189
  text: string;
193
190
  }
194
191
 
192
+ /**
193
+ * Types shared between the eslint-plugin host and its lint workers.
194
+ *
195
+ * Wire-format / IPC frame types (the Go↔Node frame contract) live in
196
+ * `src/ipc/protocol.ts` — the single source the CLI host consumes.
197
+ */
198
+ /**
199
+ * ESLint-compatible access values carried to the plugin worker. This mirrors
200
+ * the public config authoring type without coupling the worker build project to
201
+ * the config-loader build project.
202
+ */
203
+ declare type GlobalAccess = boolean | null | 'true' | 'false' | 'readonly' | 'readable' | 'writable' | 'writeable' | 'off';
204
+
205
+ declare type GlobalsConfig = Record<string, GlobalAccess>;
206
+
195
207
  /** Minimal node shape — the fixer only needs `range`. */
196
208
  declare interface HasRange {
197
209
  range: [number, number];
@@ -223,7 +235,7 @@ declare interface LanguageOptions {
223
235
  ecmaVersion?: number | 'latest';
224
236
  /** Module / script / commonjs — top-level in ESLint v10. Same rationale as `ecmaVersion`. */
225
237
  sourceType?: 'module' | 'script' | 'commonjs';
226
- globals?: Record<string, 'readonly' | 'writable' | 'off'>;
238
+ globals?: GlobalsConfig;
227
239
  /**
228
240
  * Parser-specific extras. Only `ecmaFeatures` lives here in v10
229
241
  * (rules that gate on `jsx` / `globalReturn` / `impliedStrict` read
@@ -283,7 +295,7 @@ export declare interface LintFileRequest {
283
295
  languageOptions?: {
284
296
  ecmaVersion?: number | 'latest';
285
297
  sourceType?: 'module' | 'script' | 'commonjs';
286
- globals?: Record<string, 'readonly' | 'writable' | 'off'>;
298
+ globals?: GlobalsConfig;
287
299
  parserOptions?: {
288
300
  ecmaFeatures?: {
289
301
  jsx?: boolean;
@@ -23169,9 +23169,11 @@ function scope_factory_analyze(ast, opts) {
23169
23169
  childVisitorKeys: VISITOR_KEYS
23170
23170
  });
23171
23171
  }
23172
+ const syntheticGlobals = new WeakSet();
23172
23173
  function ensureGlobal(gs, name, mode) {
23173
23174
  const existing = gs.set?.get(name);
23174
23175
  if (existing) {
23176
+ if (!syntheticGlobals.has(existing)) return existing;
23175
23177
  existing.writeable = 'writable' === mode;
23176
23178
  existing.eslintImplicitGlobalSetting = mode;
23177
23179
  return existing;
@@ -23185,6 +23187,7 @@ function ensureGlobal(gs, name, mode) {
23185
23187
  eslintImplicitGlobalSetting: mode,
23186
23188
  scope: gs
23187
23189
  };
23190
+ syntheticGlobals.add(v);
23188
23191
  gs.variables.push(v);
23189
23192
  gs.set?.set(name, v);
23190
23193
  return v;
@@ -23222,28 +23225,50 @@ function seedGlobals(scopeManager, globals) {
23222
23225
  const gs = sm.globalScope;
23223
23226
  if (!gs) return;
23224
23227
  if (!globals) return void resolveThroughReferences(gs);
23225
- for (const [name, mode] of Object.entries(globals)){
23226
- if ('off' === mode) {
23227
- if (gs.set?.has(name)) {
23228
- const v = gs.set.get(name);
23229
- if (Array.isArray(v.references) && v.references.length > 0) {
23230
- const through = gs.through ?? (gs.through = []);
23231
- for (const ref of v.references){
23232
- ref.resolved = null;
23233
- through.push(ref);
23228
+ for (const [name, access] of Object.entries(globals)){
23229
+ const mode = normalizeGlobalAccess(access);
23230
+ if (null != mode) {
23231
+ if ('off' === mode) {
23232
+ const v = gs.set?.get(name);
23233
+ if (v && syntheticGlobals.has(v)) {
23234
+ if (Array.isArray(v.references) && v.references.length > 0) {
23235
+ const through = gs.through ?? (gs.through = []);
23236
+ for (const ref of v.references){
23237
+ ref.resolved = null;
23238
+ through.push(ref);
23239
+ }
23240
+ v.references = [];
23234
23241
  }
23235
- v.references = [];
23242
+ const idx = gs.variables.indexOf(v);
23243
+ if (idx >= 0) gs.variables.splice(idx, 1);
23244
+ gs.set?.delete(name);
23236
23245
  }
23237
- const idx = gs.variables.indexOf(v);
23238
- if (idx >= 0) gs.variables.splice(idx, 1);
23239
- gs.set.delete(name);
23246
+ continue;
23240
23247
  }
23241
- continue;
23248
+ ensureGlobal(gs, name, mode);
23242
23249
  }
23243
- ensureGlobal(gs, name, mode);
23244
23250
  }
23245
23251
  resolveThroughReferences(gs);
23246
23252
  }
23253
+ function normalizeGlobalAccess(access) {
23254
+ switch(access){
23255
+ case true:
23256
+ case 'true':
23257
+ case 'writable':
23258
+ case 'writeable':
23259
+ return 'writable';
23260
+ case false:
23261
+ case null:
23262
+ case 'false':
23263
+ case 'readonly':
23264
+ case 'readable':
23265
+ return 'readonly';
23266
+ case 'off':
23267
+ return 'off';
23268
+ default:
23269
+ return;
23270
+ }
23271
+ }
23247
23272
  function esquery_esm_min_e(e, t) {
23248
23273
  (null == t || t > e.length) && (t = e.length);
23249
23274
  for(var r = 0, n = Array(t); r < t; r++)n[r] = e[r];
@@ -26681,7 +26706,6 @@ function unwrapPluginModule(mod) {
26681
26706
  }
26682
26707
  async function importConfigFile(configFilePath) {
26683
26708
  const ext = node_path.extname(configFilePath);
26684
- if ('.js' === ext || '.mjs' === ext || '.cjs' === ext) return import(pathToFileURL(configFilePath).href);
26685
26709
  if ('.ts' === ext || '.mts' === ext || '.cts' === ext) {
26686
26710
  const useNative = Boolean(process.features.typescript);
26687
26711
  if (useNative) return import(pathToFileURL(configFilePath).href);
@@ -26697,7 +26721,7 @@ async function importConfigFile(configFilePath) {
26697
26721
  const resolved = await jiti.import(configFilePath);
26698
26722
  return resolved;
26699
26723
  }
26700
- throw new Error(`Unsupported config file extension: ${ext}`);
26724
+ return import(pathToFileURL(configFilePath).href);
26701
26725
  }
26702
26726
  const MIN_NODE_MAJOR = 20;
26703
26727
  class PluginLoaderError extends Error {
@@ -26752,25 +26776,35 @@ async function loadPluginsFromConfigs(configs) {
26752
26776
  for(let i = 0; i < configs.length; i++){
26753
26777
  const dir = configs[i].configDirectory;
26754
26778
  const existing = out.get(dir);
26755
- out.set(dir, existing ? mergeLoadedPlugins(existing, loadedList[i], dir) : loadedList[i]);
26779
+ out.set(dir, existing ? mergeLoadedPlugins(existing, loadedList[i], dir, configs[i].configPath) : loadedList[i]);
26756
26780
  }
26757
26781
  return out;
26758
26782
  }
26759
- function mergeLoadedPlugins(a, b, dir) {
26783
+ function mergeLoadedPlugins(a, b, dir, configPath) {
26784
+ const plugins = [
26785
+ ...a.plugins
26786
+ ];
26787
+ const pluginsByPrefix = new Map(plugins.map((loaded)=>[
26788
+ loaded.prefix,
26789
+ loaded
26790
+ ]));
26791
+ for (const loaded of b.plugins){
26792
+ const existing = pluginsByPrefix.get(loaded.prefix);
26793
+ if (existing) {
26794
+ if (existing.plugin !== loaded.plugin) throw new PluginLoaderError(configPath, `Cannot redefine plugin "${loaded.prefix}" for routing key "${dir}".`);
26795
+ continue;
26796
+ }
26797
+ pluginsByPrefix.set(loaded.prefix, loaded);
26798
+ plugins.push(loaded);
26799
+ }
26760
26800
  const rules = new Map(a.rules);
26761
26801
  for (const [key, rule] of b.rules){
26762
26802
  const existing = rules.get(key);
26763
- if (void 0 !== existing && existing !== rule) {
26764
- process.stderr.write(`[rslint] plugin rule "${key}" is defined by more than one config in the same directory (${dir}); keeping the first. Deduplicate the rslint.config.* files for this directory.\n`);
26765
- continue;
26766
- }
26803
+ if (void 0 !== existing && existing !== rule) throw new PluginLoaderError(configPath, `[rslint] plugin rule "${key}" is defined by different plugin instances for the same config key (${dir})`);
26767
26804
  rules.set(key, rule);
26768
26805
  }
26769
26806
  return {
26770
- plugins: [
26771
- ...a.plugins,
26772
- ...b.plugins
26773
- ],
26807
+ plugins,
26774
26808
  rules
26775
26809
  };
26776
26810
  }