@useinsider/eslint-config 3.0.2 → 4.0.0-beta.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.
Files changed (57) hide show
  1. package/README.md +285 -64
  2. package/dist/builders/configFiles.d.ts +13 -0
  3. package/dist/builders/oxlint.d.ts +51 -0
  4. package/dist/builders/oxlintOwnership.d.ts +43 -0
  5. package/dist/cli.cjs +584 -27
  6. package/dist/cli.cjs.map +1 -1
  7. package/dist/configs/dependencies.d.ts +3 -0
  8. package/dist/{javascript-VBXqjVi8.cjs → javascript-DH8-N38w.cjs} +5 -5
  9. package/dist/{javascript-VBXqjVi8.cjs.map → javascript-DH8-N38w.cjs.map} +1 -1
  10. package/dist/{javascript-dom-5jRL_yKP.cjs → javascript-dom-CvtQ6_9W.cjs} +4 -4
  11. package/dist/{javascript-dom-5jRL_yKP.cjs.map → javascript-dom-CvtQ6_9W.cjs.map} +1 -1
  12. package/dist/{javascript-node-C1AU_4X2.cjs → javascript-node-BNsy3loY.cjs} +4 -4
  13. package/dist/{javascript-node-C1AU_4X2.cjs.map → javascript-node-BNsy3loY.cjs.map} +1 -1
  14. package/dist/{jest-CHzjzBtf.cjs → jest-4Es9Ggaa.cjs} +3 -3
  15. package/dist/{jest-CHzjzBtf.cjs.map → jest-4Es9Ggaa.cjs.map} +1 -1
  16. package/dist/main.cjs +452 -73
  17. package/dist/main.cjs.map +1 -1
  18. package/dist/main.d.ts +9 -2
  19. package/dist/main.js +435 -56
  20. package/dist/main.js.map +1 -1
  21. package/dist/oxlintOwnership-7hpdB3d1.cjs +199 -0
  22. package/dist/oxlintOwnership-7hpdB3d1.cjs.map +1 -0
  23. package/dist/oxlintOwnership-Cx0hgA_J.js +127 -0
  24. package/dist/oxlintOwnership-Cx0hgA_J.js.map +1 -0
  25. package/dist/removePluginsFromConfigs-D0oSUj2C.cjs.map +1 -1
  26. package/dist/removePluginsFromConfigs-DzqX2Ju2.js.map +1 -1
  27. package/dist/{stylistic-Dio0AM2E.cjs → stylistic-CoP3MT1Y.cjs} +4 -4
  28. package/dist/{stylistic-Dio0AM2E.cjs.map → stylistic-CoP3MT1Y.cjs.map} +1 -1
  29. package/dist/{typescript-C1icmOxY.cjs → typescript-D3VeLnpn.cjs} +7 -7
  30. package/dist/{typescript-C1icmOxY.cjs.map → typescript-D3VeLnpn.cjs.map} +1 -1
  31. package/dist/{typescript-dom-RUJn2WCZ.cjs → typescript-dom-iB_xBTAD.cjs} +4 -4
  32. package/dist/{typescript-dom-RUJn2WCZ.cjs.map → typescript-dom-iB_xBTAD.cjs.map} +1 -1
  33. package/dist/{typescript-node-CBEzcc56.cjs → typescript-node-Cq6qhj53.cjs} +4 -4
  34. package/dist/{typescript-node-CBEzcc56.cjs.map → typescript-node-Cq6qhj53.cjs.map} +1 -1
  35. package/dist/utils/ensureConfigStructure.d.ts +9 -4
  36. package/dist/utils/ensureOxlintConfig.d.ts +9 -0
  37. package/dist/utils/silenceRules.d.ts +9 -0
  38. package/dist/{vitest-BomI8gtu.cjs → vitest-BlGQswK2.cjs} +3 -3
  39. package/dist/{vitest-BomI8gtu.cjs.map → vitest-BlGQswK2.cjs.map} +1 -1
  40. package/dist/{vue2-azKwRgaJ.cjs → vue2-Da1i_oVn.cjs} +8 -8
  41. package/dist/{vue2-azKwRgaJ.cjs.map → vue2-Da1i_oVn.cjs.map} +1 -1
  42. package/dist/{vue2-options-api-ma3J-bvM.cjs → vue2-options-api-BSdrUm8H.cjs} +8 -8
  43. package/dist/{vue2-options-api-ma3J-bvM.cjs.map → vue2-options-api-BSdrUm8H.cjs.map} +1 -1
  44. package/dist/{vue2-typescript-CZSHzp7I.cjs → vue2-typescript-dXLRSGNP.cjs} +10 -10
  45. package/dist/{vue2-typescript-CZSHzp7I.cjs.map → vue2-typescript-dXLRSGNP.cjs.map} +1 -1
  46. package/dist/{vue2-typescript-options-api-B8OzZlje.cjs → vue2-typescript-options-api-LM7U5dWq.cjs} +10 -10
  47. package/dist/{vue2-typescript-options-api-B8OzZlje.cjs.map → vue2-typescript-options-api-LM7U5dWq.cjs.map} +1 -1
  48. package/dist/{vue3-Df5UQ1Ys.cjs → vue3-621EM2Ii.cjs} +8 -8
  49. package/dist/{vue3-Df5UQ1Ys.cjs.map → vue3-621EM2Ii.cjs.map} +1 -1
  50. package/dist/{vue3-options-api-Bx7yYHza.cjs → vue3-options-api-CC9_fAiq.cjs} +8 -8
  51. package/dist/{vue3-options-api-Bx7yYHza.cjs.map → vue3-options-api-CC9_fAiq.cjs.map} +1 -1
  52. package/dist/{vue3-typescript-C0O1JZlk.cjs → vue3-typescript-DONe-IKz.cjs} +10 -10
  53. package/dist/{vue3-typescript-C0O1JZlk.cjs.map → vue3-typescript-DONe-IKz.cjs.map} +1 -1
  54. package/dist/{vue3-typescript-options-api-cPal4O-K.cjs → vue3-typescript-options-api-D4pqpDFs.cjs} +10 -10
  55. package/dist/{vue3-typescript-options-api-cPal4O-K.cjs.map → vue3-typescript-options-api-D4pqpDFs.cjs.map} +1 -1
  56. package/package.json +8 -5
  57. package/dist/rolldown-runtime-VH7oDXx4.cjs +0 -28
package/dist/main.js CHANGED
@@ -1,10 +1,11 @@
1
+ import { a as isVueUnsafeRule, i as isRuleDisabled, n as VUE_LIKE_GLOBS, o as toOxlintPluginName, r as isOxlintOwned, s as toOxlintRuleName, t as OXLINT_CATEGORIES } from "./oxlintOwnership-Cx0hgA_J.js";
1
2
  import { t as merge } from "./merge-R5ZHqMsV.js";
2
3
  import { t as mergeAll } from "./mergeAllConfig-B2rSXCdn.js";
3
4
  import { defineConfig } from "eslint/config";
4
5
  import globals from "globals";
5
6
  import { existsSync, readFileSync, realpathSync } from "node:fs";
6
7
  import { createRequire } from "node:module";
7
- import { dirname, resolve } from "node:path";
8
+ import { dirname, join, resolve } from "node:path";
8
9
  import { fileURLToPath } from "node:url";
9
10
  import { AST_NODE_TYPES } from "@typescript-eslint/types";
10
11
  import { installPackage } from "@antfu/install-pkg";
@@ -29,6 +30,33 @@ var configFileRules = {
29
30
  "import-x/no-extraneous-dependencies": "off",
30
31
  "import-x/prefer-default-export": "off"
31
32
  };
33
+ var LINT_TOPOLOGY_NAME = "insider/lint-topology";
34
+ /**
35
+ * The three files `useInsider()` requires a consumer to own. They are always linted with
36
+ * {@link buildLintTopologyBlocks}, independently of the `configFiles` option.
37
+ */
38
+ var LINT_TOPOLOGY_GLOBS = [
39
+ "**/insider.lint.{ts,mts,cts,js,mjs,cjs}",
40
+ "**/eslint.config.{ts,mts,cts,js,mjs,cjs}",
41
+ "**/oxlint.config.{ts,mts,cts,js,mjs,cjs}"
42
+ ];
43
+ var lintTopologyRules = {
44
+ ...configFileRules,
45
+ "import-x/extensions": "off"
46
+ };
47
+ /**
48
+ * Relaxations the mandated three-file layout needs in order to lint clean. Emitted unconditionally
49
+ * because the layout exists whether or not the consumer passes `configFiles`.
50
+ * @example
51
+ * buildLintTopologyBlocks()[0].rules['import-x/extensions']; // 'off'
52
+ */
53
+ function buildLintTopologyBlocks() {
54
+ return [{
55
+ name: LINT_TOPOLOGY_NAME,
56
+ files: [...LINT_TOPOLOGY_GLOBS],
57
+ rules: { ...lintTopologyRules }
58
+ }];
59
+ }
32
60
  function mergeGlobs(userGlobs) {
33
61
  const seen = /* @__PURE__ */ new Set();
34
62
  const merged = [];
@@ -58,17 +86,148 @@ function buildConfigFilesBlocks(value) {
58
86
  function buildJqueryBlocks(entries) {
59
87
  if (!entries || entries.length === 0) return [];
60
88
  return entries.map((entry) => ({
61
- files: entry.files,
89
+ files: [...entry.files],
62
90
  languageOptions: { globals: { ...globals.jquery } }
63
91
  }));
64
92
  }
65
93
  //#endregion
94
+ //#region src/builders/oxlint.ts
95
+ var OXLINT_SUPPRESSION_NAME = "insider/oxlint-suppression";
96
+ var OXLINT_SFC_EXCEPTION_NAME = "insider/oxlint-suppression-sfc-exception";
97
+ var UNUSED_DIRECTIVE_BLOCK_NAME = "insider/unused-disable-directives";
98
+ var UNUSED_DIRECTIVE_SEVERITY = "warn";
99
+ var OXLINT_DIRECTIVE_OPTIONS = {
100
+ respectEslintDisableDirectives: false,
101
+ reportUnusedDisableDirectives: UNUSED_DIRECTIVE_SEVERITY
102
+ };
103
+ /**
104
+ * The always-on ESLint block that makes a stale `eslint-disable` visible. ESLint 9 defaults this to
105
+ * `warn`, but the split relies on it, so it is pinned rather than inherited.
106
+ * @example
107
+ * buildUnusedDirectiveBlocks()[0].linterOptions; // { reportUnusedDisableDirectives: 'warn' }
108
+ */
109
+ function buildUnusedDirectiveBlocks() {
110
+ return [{
111
+ name: UNUSED_DIRECTIVE_BLOCK_NAME,
112
+ linterOptions: { reportUnusedDisableDirectives: UNUSED_DIRECTIVE_SEVERITY }
113
+ }];
114
+ }
115
+ function ownedRuleNames(rules) {
116
+ return Object.keys(rules).filter((name) => isOxlintOwned(name) && !isRuleDisabled(rules[name]));
117
+ }
118
+ function translatableRuleNames(rules) {
119
+ return Object.keys(rules).filter(isOxlintOwned);
120
+ }
121
+ function toOffRecord(ruleNames) {
122
+ return Object.fromEntries(ruleNames.map((name) => [name, "off"]));
123
+ }
124
+ function suppressionFor(block) {
125
+ if (!block.rules) return [];
126
+ const owned = ownedRuleNames(block.rules);
127
+ if (owned.length === 0) return [];
128
+ const files = block.files ? { files: [...block.files] } : {};
129
+ const blockIgnores = block.ignores ? [...block.ignores] : [];
130
+ const vueSafe = owned.filter((name) => !isVueUnsafeRule(name));
131
+ const vueUnsafe = owned.filter((name) => isVueUnsafeRule(name));
132
+ const suppressions = [];
133
+ if (vueSafe.length > 0) suppressions.push({
134
+ name: OXLINT_SUPPRESSION_NAME,
135
+ ...files,
136
+ ...blockIgnores.length > 0 ? { ignores: blockIgnores } : {},
137
+ rules: toOffRecord(vueSafe)
138
+ });
139
+ if (vueUnsafe.length > 0) suppressions.push({
140
+ name: OXLINT_SFC_EXCEPTION_NAME,
141
+ ...files,
142
+ ignores: [...blockIgnores, ...VUE_LIKE_GLOBS],
143
+ rules: toOffRecord(vueUnsafe)
144
+ });
145
+ return suppressions;
146
+ }
147
+ /**
148
+ * ESLint blocks that turn off every rule the emitted oxlint config takes over. Rules oxlint cannot
149
+ * check inside an SFC are suppressed everywhere except `.vue`/`.svelte`/`.astro`, so a Vue consumer
150
+ * keeps them.
151
+ * @example
152
+ * buildOxlintSuppressionBlocks([{ files: ['src/**\/*.ts'], rules: { 'no-var': 'error' } }]);
153
+ * // [{ files: ['src/**\/*.ts'], rules: { 'no-var': 'off' } }]
154
+ */
155
+ function buildOxlintSuppressionBlocks(blocks) {
156
+ return blocks.flatMap(suppressionFor);
157
+ }
158
+ function translateRules(rules, plugins) {
159
+ return translatableRuleNames(rules).reduce((accumulator, name) => {
160
+ plugins.add(toOxlintPluginName(name));
161
+ accumulator[toOxlintRuleName(name)] = rules[name];
162
+ return accumulator;
163
+ }, {});
164
+ }
165
+ function anchorOxlintGlob(glob) {
166
+ return glob.startsWith("**") || glob.startsWith("./") || glob.startsWith("/") ? glob : `./${glob}`;
167
+ }
168
+ function anchorGlobs(globs) {
169
+ return globs.map(anchorOxlintGlob);
170
+ }
171
+ function anchorOxlintIgnorePattern(pattern) {
172
+ const negated = pattern.startsWith("!");
173
+ const body = negated ? pattern.slice(1) : pattern;
174
+ const anchored = body.startsWith("/") || body.startsWith("**") ? body : `/${body}`;
175
+ return negated ? `!${anchored}` : anchored;
176
+ }
177
+ function overrideFor(block, plugins) {
178
+ if (!block.files || !block.rules) return [];
179
+ const translated = translateRules(block.rules, plugins);
180
+ if (Object.keys(translated).length === 0) return [];
181
+ return [{
182
+ files: anchorGlobs(block.files),
183
+ ...block.ignores && block.ignores.length > 0 ? { excludeFiles: anchorGlobs(block.ignores) } : {},
184
+ rules: translated
185
+ }];
186
+ }
187
+ /**
188
+ * The oxlint half of the split, derived from the same emitted ESLint blocks the suppression layer
189
+ * reads. Every oxlint category is pinned off so the config enables exactly the preset's own rules
190
+ * and nothing oxlint would otherwise turn on by default.
191
+ * @example
192
+ * buildOxlintConfig([{ files: ['src/**\/*.ts'], rules: { 'no-var': 'error' } }], ['dist']);
193
+ * // { plugins: ['eslint'], categories: { correctness: 'off', ... }, ignorePatterns: ['/dist'], ... }
194
+ */
195
+ function buildOxlintConfig(blocks, ignores) {
196
+ const plugins = /* @__PURE__ */ new Set(["eslint"]);
197
+ const overrides = blocks.flatMap((block) => overrideFor(block, plugins));
198
+ const globalRules = blocks.filter((block) => !block.files && block.rules).reduce((accumulator, block) => ({
199
+ ...accumulator,
200
+ ...translateRules(block.rules, plugins)
201
+ }), {});
202
+ return {
203
+ plugins: [...plugins].sort((left, right) => left.localeCompare(right)),
204
+ categories: Object.fromEntries(OXLINT_CATEGORIES.map((name) => [name, "off"])),
205
+ ...ignores && ignores.length > 0 ? { ignorePatterns: ignores.map(anchorOxlintIgnorePattern) } : {},
206
+ options: { ...OXLINT_DIRECTIVE_OPTIONS },
207
+ rules: globalRules,
208
+ overrides
209
+ };
210
+ }
211
+ //#endregion
66
212
  //#region src/utils/ensureConfigStructure.ts
67
- var CONFIG_FILE_REGEX = /eslint\.config\.[mc]?[jt]s(?:$|\?)/;
68
- var OPT_OUT_ENV = "INSIDER_SKIP_CONFIG_VALIDATION";
213
+ var ANCHOR_FILE_REGEX = /(?:insider\.lint|eslint\.config)\.[mc]?[jt]s(?:$|\?)/;
214
+ var OPT_OUT_ENV$1 = "INSIDER_SKIP_CONFIG_VALIDATION";
69
215
  var FILE_URL_PREFIX = "file://";
70
216
  var TARGET_CALLEE_NAME = "useInsider";
217
+ var ESLINT_RENDERER_NAME = "useInsiderESLint";
218
+ var OXC_RENDERER_NAME = "useInsiderOXC";
71
219
  var INSIDER_IMPORT_SOURCE = "@useinsider/eslint-config";
220
+ var MODULE_EXTENSIONS = [
221
+ "ts",
222
+ "mts",
223
+ "cts",
224
+ "js",
225
+ "mjs",
226
+ "cjs"
227
+ ];
228
+ var RENDERER_EXPORT = `export const ${`{ ${ESLINT_RENDERER_NAME}, ${OXC_RENDERER_NAME} }`} = ${TARGET_CALLEE_NAME}({ ... })`;
229
+ var LINT_MODULE_FILENAMES = MODULE_EXTENSIONS.map((ext) => `insider.lint.${ext}`);
230
+ var ESLINT_CONFIG_FILENAMES = MODULE_EXTENSIONS.map((ext) => `eslint.config.${ext}`);
72
231
  var bypassWarnEmitted = false;
73
232
  function computeSelfModulePath() {
74
233
  const raw = fileURLToPath(import.meta.url);
@@ -98,24 +257,24 @@ function locOf(node) {
98
257
  column: node.loc.start.column
99
258
  };
100
259
  }
101
- function validateCallExpression(callExpr) {
260
+ function validateCallExpression(callExpr, expectedCallee) {
102
261
  const { callee } = callExpr;
103
- if (callee.type !== AST_NODE_TYPES.Identifier || callee.name !== TARGET_CALLEE_NAME) return [{
262
+ if (callee.type !== AST_NODE_TYPES.Identifier || callee.name !== expectedCallee) return [{
104
263
  ...locOf(callExpr),
105
- reason: "Default export must call `useInsider(...)` directly."
264
+ reason: `Default export must call \`${expectedCallee}(...)\` directly.`
106
265
  }];
107
266
  return [];
108
267
  }
109
- function validateExportedCall(callee, fallbackLoc) {
268
+ function validateExportedCall(callee, fallbackLoc, expectedCallee) {
110
269
  let target = callee;
111
270
  if (target.type === AST_NODE_TYPES.AwaitExpression) target = target.argument;
112
- if (target.type === AST_NODE_TYPES.CallExpression) return validateCallExpression(target);
271
+ if (target.type === AST_NODE_TYPES.CallExpression) return validateCallExpression(target, expectedCallee);
113
272
  if (target.type === AST_NODE_TYPES.ArrayExpression) return [{
114
273
  ...locOf(target),
115
- reason: "Default export must not wrap `useInsider(...)` in an array. Use the typed options object instead."
274
+ reason: `Default export must not wrap \`${expectedCallee}(...)\` in an array.`
116
275
  }];
117
276
  if (target.type === AST_NODE_TYPES.LogicalExpression || target.type === AST_NODE_TYPES.ConditionalExpression) {
118
- const reason = ["Default export must not gate `useInsider(...)` behind a logical/conditional expression.", "Use the typed options object instead."].join(" ");
277
+ const reason = [`Default export must not gate \`${expectedCallee}(...)\` behind a logical/conditional expression.`, "Use the typed options object instead."].join(" ");
119
278
  return [{
120
279
  ...locOf(target),
121
280
  reason
@@ -123,7 +282,7 @@ function validateExportedCall(callee, fallbackLoc) {
123
282
  }
124
283
  return [{
125
284
  ...fallbackLoc,
126
- reason: "Default export must be a direct call to `useInsider(...)`."
285
+ reason: `Default export must be a direct call to \`${expectedCallee}(...)\`.`
127
286
  }];
128
287
  }
129
288
  function collectInsiderBindings(node, bindings, isInsiderSource) {
@@ -144,7 +303,7 @@ function classifyExpressionStatement(node, insiderBindings) {
144
303
  }
145
304
  };
146
305
  }
147
- function classifyStatement(node, exportSeen, insiderBindings) {
306
+ function classifyStatement(node, exportSeen, insiderBindings, expectedCallee) {
148
307
  if (node.type === AST_NODE_TYPES.ImportDeclaration || node.type === AST_NODE_TYPES.VariableDeclaration) return { type: "ignore" };
149
308
  if (node.type === AST_NODE_TYPES.ExpressionStatement) return classifyExpressionStatement(node, insiderBindings);
150
309
  if (node.type === AST_NODE_TYPES.ExportDefaultDeclaration) {
@@ -158,13 +317,13 @@ function classifyStatement(node, exportSeen, insiderBindings) {
158
317
  const declaration = node.declaration;
159
318
  return {
160
319
  type: "export",
161
- violations: validateExportedCall(declaration, locOf(node))
320
+ violations: validateExportedCall(declaration, locOf(node), expectedCallee)
162
321
  };
163
322
  }
164
323
  const reason = [
165
324
  `Disallowed top-level statement \`${node.type}\`.`,
166
325
  "Only imports, variable declarations, helper calls from `@useinsider/eslint-config`,",
167
- "and one `export default useInsider(...)` are allowed."
326
+ `and one \`export default ${expectedCallee}()\` are allowed.`
168
327
  ].join(" ");
169
328
  return {
170
329
  type: "disallowed",
@@ -179,18 +338,112 @@ function validateProgramStructure(program, configPath = "", options = {}) {
179
338
  const insiderBindings = /* @__PURE__ */ new Set();
180
339
  const resolver = options.isInsiderImportSource ?? defaultIsInsiderImportSource;
181
340
  const isInsiderSource = (source) => resolver(source, configPath);
341
+ const expectedCallee = options.expectedCallee ?? ESLINT_RENDERER_NAME;
182
342
  let exportSeen = false;
183
343
  program.body.forEach((node) => {
184
344
  if (node.type === AST_NODE_TYPES.ImportDeclaration) collectInsiderBindings(node, insiderBindings, isInsiderSource);
185
- const result = classifyStatement(node, exportSeen, insiderBindings);
345
+ const result = classifyStatement(node, exportSeen, insiderBindings, expectedCallee);
186
346
  if (result.type === "ignore") return;
187
347
  if (result.type === "export") {
188
348
  exportSeen = true;
189
349
  violations.push(...result.violations);
190
350
  return;
191
351
  }
192
- if (result.type === "duplicateExport") {
193
- violations.push(result.violation);
352
+ violations.push(result.violation);
353
+ });
354
+ if (!exportSeen) violations.push({
355
+ line: 1,
356
+ column: 0,
357
+ reason: `Missing \`export default ${options.expectedCallee ?? ESLINT_RENDERER_NAME}()\`.`
358
+ });
359
+ return violations;
360
+ }
361
+ function validateRendererPattern(pattern) {
362
+ const bound = /* @__PURE__ */ new Set();
363
+ const violations = [];
364
+ pattern.properties.forEach((property) => {
365
+ if (property.type !== AST_NODE_TYPES.Property || property.computed || !property.shorthand) {
366
+ violations.push({
367
+ ...locOf(property),
368
+ reason: `Destructure \`${ESLINT_RENDERER_NAME}\` and \`${OXC_RENDERER_NAME}\` by name, without renaming or spreading.`
369
+ });
370
+ return;
371
+ }
372
+ bound.add(property.key.name);
373
+ });
374
+ [ESLINT_RENDERER_NAME, OXC_RENDERER_NAME].forEach((name) => {
375
+ if (!bound.has(name)) violations.push({
376
+ ...locOf(pattern),
377
+ reason: `Missing \`${name}\` in the destructured \`${TARGET_CALLEE_NAME}(...)\` result.`
378
+ });
379
+ });
380
+ return violations;
381
+ }
382
+ function validateRendererExport(node) {
383
+ const { declaration } = node;
384
+ if (!declaration || declaration.type !== AST_NODE_TYPES.VariableDeclaration || declaration.kind !== "const") return [{
385
+ ...locOf(node),
386
+ reason: `Renderer export must be \`${RENDERER_EXPORT};\`.`
387
+ }];
388
+ if (declaration.declarations.length !== 1) return [{
389
+ ...locOf(declaration),
390
+ reason: "Renderer export must declare exactly one binding."
391
+ }];
392
+ const [declarator] = declaration.declarations;
393
+ if (declarator.id.type !== AST_NODE_TYPES.ObjectPattern) return [{
394
+ ...locOf(declarator.id),
395
+ reason: `Renderer export must destructure \`${ESLINT_RENDERER_NAME}\` and \`${OXC_RENDERER_NAME}\`.`
396
+ }];
397
+ const patternViolations = validateRendererPattern(declarator.id);
398
+ const { init } = declarator;
399
+ if (!init) return [...patternViolations, {
400
+ ...locOf(declarator),
401
+ reason: `Renderer export must be initialized by a direct \`${TARGET_CALLEE_NAME}(...)\` call.`
402
+ }];
403
+ return [...patternViolations, ...validateExportedCall(init, locOf(declarator), TARGET_CALLEE_NAME)];
404
+ }
405
+ function classifyLintModuleStatement(node, exportSeen, insiderBindings) {
406
+ if (node.type === AST_NODE_TYPES.ImportDeclaration || node.type === AST_NODE_TYPES.VariableDeclaration) return { type: "ignore" };
407
+ if (node.type === AST_NODE_TYPES.ExpressionStatement) return classifyExpressionStatement(node, insiderBindings);
408
+ if (node.type === AST_NODE_TYPES.ExportNamedDeclaration) {
409
+ if (exportSeen) return {
410
+ type: "duplicateExport",
411
+ violation: {
412
+ ...locOf(node),
413
+ reason: "Only one export is allowed."
414
+ }
415
+ };
416
+ return {
417
+ type: "export",
418
+ violations: validateRendererExport(node)
419
+ };
420
+ }
421
+ const reason = [
422
+ `Disallowed top-level statement \`${node.type}\`.`,
423
+ "Only imports, variable declarations, helper calls from `@useinsider/eslint-config`,",
424
+ `and one \`${RENDERER_EXPORT}\` are allowed.`
425
+ ].join(" ");
426
+ return {
427
+ type: "disallowed",
428
+ violation: {
429
+ ...locOf(node),
430
+ reason
431
+ }
432
+ };
433
+ }
434
+ function validateLintModuleStructure(program, configPath = "", options = {}) {
435
+ const violations = [];
436
+ const insiderBindings = /* @__PURE__ */ new Set();
437
+ const resolver = options.isInsiderImportSource ?? defaultIsInsiderImportSource;
438
+ const isInsiderSource = (source) => resolver(source, configPath);
439
+ let exportSeen = false;
440
+ program.body.forEach((node) => {
441
+ if (node.type === AST_NODE_TYPES.ImportDeclaration) collectInsiderBindings(node, insiderBindings, isInsiderSource);
442
+ const result = classifyLintModuleStatement(node, exportSeen, insiderBindings);
443
+ if (result.type === "ignore") return;
444
+ if (result.type === "export") {
445
+ exportSeen = true;
446
+ violations.push(...result.violations);
194
447
  return;
195
448
  }
196
449
  violations.push(result.violation);
@@ -198,7 +451,7 @@ function validateProgramStructure(program, configPath = "", options = {}) {
198
451
  if (!exportSeen) violations.push({
199
452
  line: 1,
200
453
  column: 0,
201
- reason: "Missing `export default useInsider(...)`."
454
+ reason: `Missing \`${RENDERER_EXPORT};\`.`
202
455
  });
203
456
  return violations;
204
457
  }
@@ -222,7 +475,7 @@ function extractMatchesFromStack(frames) {
222
475
  const matches = /* @__PURE__ */ new Set();
223
476
  frames.forEach((frame) => {
224
477
  const name = normalizeFrameFilename(frame.getFileName());
225
- if (name && CONFIG_FILE_REGEX.test(name)) matches.add(name);
478
+ if (name && ANCHOR_FILE_REGEX.test(name)) matches.add(name);
226
479
  });
227
480
  return Array.from(matches);
228
481
  }
@@ -235,13 +488,16 @@ function argvCandidateAt(argv, index) {
235
488
  function extractConfigFromArgv(argv) {
236
489
  for (let i = 0; i < argv.length; i += 1) {
237
490
  const candidate = argvCandidateAt(argv, i);
238
- if (candidate && CONFIG_FILE_REGEX.test(candidate)) return candidate;
491
+ if (candidate && ANCHOR_FILE_REGEX.test(candidate)) return candidate;
239
492
  }
240
493
  return null;
241
494
  }
495
+ var LINT_MODULE_REGEX = /insider\.lint\.[mc]?[jt]s(?:$|\?)/;
242
496
  function getConsumerConfigPath(frames = readStackFrames(), argv = process.argv) {
243
497
  const stackMatches = extractMatchesFromStack(frames);
244
498
  if (stackMatches.length === 1) return stackMatches[0];
499
+ const lintModules = stackMatches.filter((match) => LINT_MODULE_REGEX.test(match));
500
+ if (lintModules.length === 1) return lintModules[0];
245
501
  const fromArgv = extractConfigFromArgv(argv);
246
502
  if (fromArgv) return fromArgv;
247
503
  return null;
@@ -251,27 +507,46 @@ function captureConsumerConfigDir(configPath) {
251
507
  consumerConfigDir = configPath === null ? null : dirname(resolve(configPath));
252
508
  }
253
509
  /**
254
- * Directory holding the consumer's `eslint.config.*`, captured by {@link ensureConfigStructure}.
255
- * This is the anchor `tsconfigPaths` entries are written against; it falls back to `process.cwd()`
256
- * when the config file could not be located.
510
+ * Directory holding the consumer's `insider.lint.*` module and its sibling linter configs, captured
511
+ * by {@link ensureConfigStructure}. This is the anchor `tsconfigPaths` entries are written against,
512
+ * and where {@link import('./ensureOxlintConfig').ensureOxlintConfig} looks for the oxlint config;
513
+ * it falls back to `process.cwd()` when the module could not be located.
257
514
  * @example
258
- * // consumer config at /repo/eslint.config.mjs, `eslint` launched from /repo/app
515
+ * // consumer module at /repo/insider.lint.ts, `eslint` launched from /repo/app
259
516
  * getConsumerConfigDir(); // '/repo'
260
517
  */
261
518
  function getConsumerConfigDir() {
262
519
  return consumerConfigDir ?? process.cwd();
263
520
  }
521
+ var EXPECTED_LAYOUT = [
522
+ "Expected layout (three sibling files):",
523
+ "",
524
+ " // insider.lint.ts",
525
+ " import { useInsider } from '@useinsider/eslint-config';",
526
+ "",
527
+ ` ${RENDERER_EXPORT};`,
528
+ "",
529
+ " // eslint.config.ts",
530
+ ` import { ${ESLINT_RENDERER_NAME} } from './insider.lint.ts';`,
531
+ "",
532
+ ` export default ${ESLINT_RENDERER_NAME}();`,
533
+ "",
534
+ " // oxlint.config.ts",
535
+ ` import { ${OXC_RENDERER_NAME} } from './insider.lint.ts';`,
536
+ "",
537
+ ` export default await ${OXC_RENDERER_NAME}();`
538
+ ].join("\n");
264
539
  function formatReport(configPath, violations) {
265
540
  const header = ["@useinsider/eslint-config: protected configuration structure violated in:", ` ${configPath}`].join("\n");
266
541
  const lines = violations.map((violation) => ` - [${violation.line}:${violation.column}] ${violation.reason}`);
267
542
  const footer = [
268
543
  "",
269
- "Allowed structure:",
270
- " - import statements",
271
- " - const/let/var declarations",
272
- " - exactly one `export default useInsider({ ... })`",
544
+ "Alongside the export, only import statements, const/let/var declarations and helper calls",
545
+ "imported from `@useinsider/eslint-config` are allowed.",
273
546
  "",
274
- "Emergency bypass: set INSIDER_SKIP_CONFIG_VALIDATION=true (do not commit)."
547
+ EXPECTED_LAYOUT,
548
+ "",
549
+ `Emergency bypass: set ${OPT_OUT_ENV$1}=true (do not commit).`
275
550
  ].join("\n");
276
551
  return `${header}\n${lines.join("\n")}\n${footer}`;
277
552
  }
@@ -312,16 +587,50 @@ function parseConsumerSource(configPath, source) {
312
587
  return fail(`@useinsider/eslint-config: failed to parse consumer config at ${configPath}: ${message}`);
313
588
  }
314
589
  }
315
- function runValidation(configPath) {
316
- if (!configPath) fail(["@useinsider/eslint-config: unable to locate consumer `eslint.config.*` file for structure validation.", "If this is an unexpected environment, set INSIDER_SKIP_CONFIG_VALIDATION=true (do not commit)."].join("\n"));
317
- const violations = validateProgramStructure(parseConsumerSource(configPath, readConsumerSource(configPath)), configPath);
590
+ function resolveSibling(directory, filenames) {
591
+ const found = filenames.find((filename) => existsSync(resolve(directory, filename)));
592
+ return found ? resolve(directory, found) : null;
593
+ }
594
+ function validateFile(filePath, validator, options) {
595
+ const violations = validator(parseConsumerSource(filePath, readConsumerSource(filePath)), filePath, options);
318
596
  if (violations.length === 0) return;
319
- fail(formatReport(configPath, violations));
597
+ fail(formatReport(filePath, violations));
598
+ }
599
+ function runValidation(anchorPath) {
600
+ if (!anchorPath) fail([
601
+ "@useinsider/eslint-config: unable to locate the consumer `insider.lint.*` module for structure validation.",
602
+ "",
603
+ EXPECTED_LAYOUT,
604
+ "",
605
+ `If this is an unexpected environment, set ${OPT_OUT_ENV$1}=true (do not commit).`
606
+ ].join("\n"));
607
+ const directory = dirname(resolve(anchorPath));
608
+ const lintModulePath = resolveSibling(directory, LINT_MODULE_FILENAMES);
609
+ if (!lintModulePath) fail([
610
+ `@useinsider/eslint-config: no \`insider.lint.*\` module found in ${directory}.`,
611
+ "",
612
+ `\`${TARGET_CALLEE_NAME}()\` now returns the two renderers instead of a flat-config array,`,
613
+ "so the config it is called from must be a module both linters can import.",
614
+ "",
615
+ EXPECTED_LAYOUT,
616
+ "",
617
+ `Emergency bypass: set ${OPT_OUT_ENV$1}=true (do not commit).`
618
+ ].join("\n"));
619
+ validateFile(lintModulePath, validateLintModuleStructure, {});
620
+ const eslintConfigPath = resolveSibling(directory, ESLINT_CONFIG_FILENAMES);
621
+ if (!eslintConfigPath) fail([
622
+ `@useinsider/eslint-config: no \`eslint.config.*\` file found in ${directory}.`,
623
+ "",
624
+ EXPECTED_LAYOUT,
625
+ "",
626
+ `Emergency bypass: set ${OPT_OUT_ENV$1}=true (do not commit).`
627
+ ].join("\n"));
628
+ validateFile(eslintConfigPath, validateProgramStructure, { expectedCallee: ESLINT_RENDERER_NAME });
320
629
  }
321
630
  function ensureConfigStructure(overridePath) {
322
631
  const configPath = overridePath === void 0 ? getConsumerConfigPath() : overridePath;
323
632
  captureConsumerConfigDir(configPath);
324
- if (process.env[OPT_OUT_ENV] === "true") {
633
+ if (process.env[OPT_OUT_ENV$1] === "true") {
325
634
  emitBypassWarning();
326
635
  return;
327
636
  }
@@ -465,7 +774,7 @@ async function buildScopeBlocks(scope, entries) {
465
774
  const preset = (await scopeMap[scope]()).default;
466
775
  return entries.map((entry) => {
467
776
  const block = clonePreset(preset);
468
- block.files = entry.files;
777
+ block.files = [...entry.files];
469
778
  if (tsScopes.has(scope)) {
470
779
  const { tsconfigPaths } = entry;
471
780
  if (tsconfigPaths) applyTypescriptOverrides(block, { tsconfigPaths });
@@ -499,7 +808,7 @@ async function buildStrictBlocks(value) {
499
808
  const { default: strictConfig } = await import("./strict-BHw_cKrv.js");
500
809
  const clone = { ...strictConfig };
501
810
  if (clone.rules) clone.rules = { ...clone.rules };
502
- if (isFileScoped) clone.files = value;
811
+ if (isFileScoped) clone.files = [...value];
503
812
  else delete clone.files;
504
813
  return [clone];
505
814
  }
@@ -508,7 +817,11 @@ async function buildStrictBlocks(value) {
508
817
  var turnOff = false;
509
818
  var scanned = false;
510
819
  var silencedDependencies = /* @__PURE__ */ new Set();
511
- var fixedDependencies = ["eslint", "globals"];
820
+ var fixedDependencies = [
821
+ "eslint",
822
+ "globals",
823
+ "oxlint"
824
+ ];
512
825
  var require = createRequire(import.meta.url);
513
826
  function getPackageJSONPath(currentDir) {
514
827
  const fileLocation = resolve(currentDir, "package.json");
@@ -564,6 +877,46 @@ function silenceDependencyWarning(input) {
564
877
  input.forEach((dependency) => silencedDependencies.add(String(dependency)));
565
878
  }
566
879
  //#endregion
880
+ //#region src/utils/ensureOxlintConfig.ts
881
+ var OPT_OUT_ENV = "INSIDER_SKIP_CONFIG_VALIDATION";
882
+ var OXLINT_CONFIG_FILENAMES = ["oxlint.config.ts", "oxlint.config.mts"];
883
+ function lintModuleSpecifier(directory) {
884
+ return `./${LINT_MODULE_FILENAMES.find((filename) => existsSync(join(directory, filename))) ?? "insider.lint.ts"}`;
885
+ }
886
+ function formatMessage(directory) {
887
+ return [
888
+ "@useinsider/eslint-config: no oxlint config found in",
889
+ ` ${directory}`,
890
+ "",
891
+ "useInsiderESLint() turns off every rule oxlint owns, so running ESLint alone would silently",
892
+ "drop those checks. Create the sibling oxlint config:",
893
+ "",
894
+ " // oxlint.config.ts",
895
+ ` import { useInsiderOXC } from '${lintModuleSpecifier(directory)}';`,
896
+ "",
897
+ " export default await useInsiderOXC();",
898
+ "",
899
+ "and run both linters:",
900
+ "",
901
+ " \"lint\": \"oxlint --disable-nested-config && eslint .\"",
902
+ "",
903
+ `Expected one of: ${OXLINT_CONFIG_FILENAMES.join(", ")}.`,
904
+ `Emergency bypass: set ${OPT_OUT_ENV}=true (do not commit).`
905
+ ].join("\n");
906
+ }
907
+ /**
908
+ * Fails when the consumer enabled the ESLint-side oxlint suppression without a sibling oxlint
909
+ * config to run the suppressed rules. Resolved against the directory
910
+ * {@link getConsumerConfigDir} captured during `useInsider()`.
911
+ * @example
912
+ * ensureOxlintConfig(); // throws unless oxlint.config.{ts,mts} sits beside insider.lint.*
913
+ */
914
+ function ensureOxlintConfig(directory = getConsumerConfigDir()) {
915
+ if (process.env[OPT_OUT_ENV] === "true") return;
916
+ if (OXLINT_CONFIG_FILENAMES.some((filename) => existsSync(join(directory, filename)))) return;
917
+ throw new Error(formatMessage(directory));
918
+ }
919
+ //#endregion
567
920
  //#region src/utils/silenceRules.ts
568
921
  var globallySilencedRules = /* @__PURE__ */ new Set();
569
922
  /**
@@ -583,14 +936,25 @@ function silenceRule(config) {
583
936
  const [, ...restConfig] = Array.isArray(config) ? config : [];
584
937
  return ["warn", ...restConfig];
585
938
  }
939
+ /**
940
+ * Reports a silenced rule name that no emitted block declares, which is how a typo in
941
+ * `silenceRules` stays visible. The caller passes the union of every block's rule keys: a single
942
+ * block is not evidence, because the emission is many narrow blocks and a correctly spelled rule is
943
+ * absent from nearly all of them.
944
+ * @example
945
+ * warnUnknownSilencedRules(['no-console'], ['no-unknown-rule']); // warns once
946
+ */
947
+ function warnUnknownSilencedRules(knownRules, silencedRules) {
948
+ const known = new Set(knownRules);
949
+ new Set(silencedRules).forEach((rule) => {
950
+ if (known.has(rule)) return;
951
+ warn(`Unknown rule "${rule}". Please remove it from the silenced rules list.`);
952
+ });
953
+ }
586
954
  function filterSilencedRules(rules, silencedRules) {
587
955
  let targetSilencedRules = silencedRules || globallySilencedRules;
588
956
  if (Array.isArray(targetSilencedRules)) targetSilencedRules = new Set(targetSilencedRules);
589
957
  if (!rules || !targetSilencedRules.size) return rules;
590
- if (silencedRules) targetSilencedRules.forEach((rule) => {
591
- if (rules[rule]) return;
592
- warn(`Unknown rule "${rule}". Please remove it from the silenced rules list.`);
593
- });
594
958
  return Object.entries(rules).reduce((newRules, [key, value]) => {
595
959
  if (value) newRules[key] = targetSilencedRules.has(key) && !isDisabled(value) ? silenceRule(value) : value;
596
960
  return newRules;
@@ -626,6 +990,7 @@ async function collectScopeBlocks(config) {
626
990
  }, Promise.resolve([]));
627
991
  }
628
992
  function applySilenceRules(blocks, perCallSilence) {
993
+ if (perCallSilence) warnUnknownSilencedRules(blocks.flatMap((block) => Object.keys(block.rules ?? {})), perCallSilence);
629
994
  blocks.forEach((block) => {
630
995
  if (!block.rules) return;
631
996
  block.rules = perCallSilence ? filterSilencedRules(block.rules, perCallSilence) : filterSilencedRules(block.rules);
@@ -635,28 +1000,42 @@ function buildIgnoresBlocks(ignores) {
635
1000
  if (!ignores || ignores.length === 0) return [];
636
1001
  return [{ ignores: [...ignores] }];
637
1002
  }
638
- async function useInsider(config) {
639
- assertNoRemovedKeys(config);
640
- ensureConfigStructure();
641
- await ensureDependencies();
642
- const ignoresBlocks = buildIgnoresBlocks(config.ignores);
1003
+ async function buildBaseBlocks(config) {
643
1004
  const scopeBlocks = await collectScopeBlocks(config);
644
- const jqueryBlocks = buildJqueryBlocks(config.jquery);
645
- const configFilesBlocks = buildConfigFilesBlocks(config.configFiles);
646
1005
  const strictBlocks = await buildStrictBlocks(config.strict);
647
- const spellCheckerBlocks = buildSpellCheckerBlocks(config.spellChecker);
648
- const blocks = [
649
- ...ignoresBlocks,
1006
+ return [
1007
+ ...buildIgnoresBlocks(config.ignores),
1008
+ ...buildUnusedDirectiveBlocks(),
650
1009
  ...scopeBlocks,
651
- ...jqueryBlocks,
652
- ...configFilesBlocks,
1010
+ ...buildJqueryBlocks(config.jquery),
1011
+ ...buildConfigFilesBlocks(config.configFiles),
653
1012
  ...strictBlocks,
654
- ...spellCheckerBlocks
1013
+ ...buildLintTopologyBlocks(),
1014
+ ...buildSpellCheckerBlocks(config.spellChecker)
655
1015
  ];
1016
+ }
1017
+ async function renderEslint(config) {
1018
+ await ensureDependencies();
1019
+ ensureOxlintConfig();
1020
+ const baseBlocks = await buildBaseBlocks(config);
1021
+ const blocks = [...baseBlocks, ...buildOxlintSuppressionBlocks(baseBlocks)];
656
1022
  applySilenceRules(blocks, config.silenceRules);
657
1023
  return defineConfig(blocks);
658
1024
  }
1025
+ async function renderOxlint(config) {
1026
+ const baseBlocks = await buildBaseBlocks(config);
1027
+ applySilenceRules(baseBlocks, config.silenceRules);
1028
+ return buildOxlintConfig(baseBlocks, config.ignores);
1029
+ }
1030
+ function useInsider(config) {
1031
+ assertNoRemovedKeys(config);
1032
+ ensureConfigStructure();
1033
+ return {
1034
+ useInsiderESLint: () => renderEslint(config),
1035
+ useInsiderOXC: () => renderOxlint(config)
1036
+ };
1037
+ }
659
1038
  //#endregion
660
- export { ensureConfigStructure, merge, mergeAll, silenceDependencyWarning, silenceRules, useInsider };
1039
+ export { ensureConfigStructure, ensureOxlintConfig, merge, mergeAll, silenceDependencyWarning, silenceRules, useInsider };
661
1040
 
662
1041
  //# sourceMappingURL=main.js.map