@useinsider/eslint-config 3.0.2 → 4.0.0-beta.2
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/README.md +376 -64
- package/dist/builders/configFiles.d.ts +13 -0
- package/dist/builders/oxlint.d.ts +51 -0
- package/dist/builders/oxlintOwnership.d.ts +43 -0
- package/dist/builders/scopeBlocks.d.ts +6 -0
- package/dist/cli.cjs +1028 -86
- package/dist/cli.cjs.map +1 -1
- package/dist/configs/dependencies.d.ts +3 -0
- package/dist/{javascript-VBXqjVi8.cjs → javascript-DH8-N38w.cjs} +5 -5
- package/dist/{javascript-VBXqjVi8.cjs.map → javascript-DH8-N38w.cjs.map} +1 -1
- package/dist/{javascript-dom-5jRL_yKP.cjs → javascript-dom-CvtQ6_9W.cjs} +4 -4
- package/dist/{javascript-dom-5jRL_yKP.cjs.map → javascript-dom-CvtQ6_9W.cjs.map} +1 -1
- package/dist/{javascript-node-C1AU_4X2.cjs → javascript-node-BNsy3loY.cjs} +4 -4
- package/dist/{javascript-node-C1AU_4X2.cjs.map → javascript-node-BNsy3loY.cjs.map} +1 -1
- package/dist/{jest-CHzjzBtf.cjs → jest-4Es9Ggaa.cjs} +3 -3
- package/dist/{jest-CHzjzBtf.cjs.map → jest-4Es9Ggaa.cjs.map} +1 -1
- package/dist/main.cjs +763 -78
- package/dist/main.cjs.map +1 -1
- package/dist/main.d.ts +9 -2
- package/dist/main.js +746 -61
- package/dist/main.js.map +1 -1
- package/dist/oxlintOwnership-7hpdB3d1.cjs +199 -0
- package/dist/oxlintOwnership-7hpdB3d1.cjs.map +1 -0
- package/dist/oxlintOwnership-Cx0hgA_J.js +127 -0
- package/dist/oxlintOwnership-Cx0hgA_J.js.map +1 -0
- package/dist/removePluginsFromConfigs-D0oSUj2C.cjs.map +1 -1
- package/dist/removePluginsFromConfigs-DzqX2Ju2.js.map +1 -1
- package/dist/{stylistic-Dio0AM2E.cjs → stylistic-CoP3MT1Y.cjs} +4 -4
- package/dist/{stylistic-Dio0AM2E.cjs.map → stylistic-CoP3MT1Y.cjs.map} +1 -1
- package/dist/{typescript-C1icmOxY.cjs → typescript-D3VeLnpn.cjs} +7 -7
- package/dist/{typescript-C1icmOxY.cjs.map → typescript-D3VeLnpn.cjs.map} +1 -1
- package/dist/{typescript-dom-RUJn2WCZ.cjs → typescript-dom-iB_xBTAD.cjs} +4 -4
- package/dist/{typescript-dom-RUJn2WCZ.cjs.map → typescript-dom-iB_xBTAD.cjs.map} +1 -1
- package/dist/{typescript-node-CBEzcc56.cjs → typescript-node-Cq6qhj53.cjs} +4 -4
- package/dist/{typescript-node-CBEzcc56.cjs.map → typescript-node-Cq6qhj53.cjs.map} +1 -1
- package/dist/utils/ensureConfigStructure.d.ts +15 -6
- package/dist/utils/ensureOxlintConfig.d.ts +10 -0
- package/dist/utils/layoutDiagnosis.d.ts +63 -0
- package/dist/utils/offerMigration.d.ts +42 -0
- package/dist/utils/silenceRules.d.ts +9 -0
- package/dist/{vitest-BomI8gtu.cjs → vitest-BlGQswK2.cjs} +3 -3
- package/dist/{vitest-BomI8gtu.cjs.map → vitest-BlGQswK2.cjs.map} +1 -1
- package/dist/{vue2-azKwRgaJ.cjs → vue2-Da1i_oVn.cjs} +8 -8
- package/dist/{vue2-azKwRgaJ.cjs.map → vue2-Da1i_oVn.cjs.map} +1 -1
- package/dist/{vue2-options-api-ma3J-bvM.cjs → vue2-options-api-BSdrUm8H.cjs} +8 -8
- package/dist/{vue2-options-api-ma3J-bvM.cjs.map → vue2-options-api-BSdrUm8H.cjs.map} +1 -1
- package/dist/{vue2-typescript-CZSHzp7I.cjs → vue2-typescript-dXLRSGNP.cjs} +10 -10
- package/dist/{vue2-typescript-CZSHzp7I.cjs.map → vue2-typescript-dXLRSGNP.cjs.map} +1 -1
- package/dist/{vue2-typescript-options-api-B8OzZlje.cjs → vue2-typescript-options-api-LM7U5dWq.cjs} +10 -10
- package/dist/{vue2-typescript-options-api-B8OzZlje.cjs.map → vue2-typescript-options-api-LM7U5dWq.cjs.map} +1 -1
- package/dist/{vue3-Df5UQ1Ys.cjs → vue3-621EM2Ii.cjs} +8 -8
- package/dist/{vue3-Df5UQ1Ys.cjs.map → vue3-621EM2Ii.cjs.map} +1 -1
- package/dist/{vue3-options-api-Bx7yYHza.cjs → vue3-options-api-CC9_fAiq.cjs} +8 -8
- package/dist/{vue3-options-api-Bx7yYHza.cjs.map → vue3-options-api-CC9_fAiq.cjs.map} +1 -1
- package/dist/{vue3-typescript-C0O1JZlk.cjs → vue3-typescript-DONe-IKz.cjs} +10 -10
- package/dist/{vue3-typescript-C0O1JZlk.cjs.map → vue3-typescript-DONe-IKz.cjs.map} +1 -1
- package/dist/{vue3-typescript-options-api-cPal4O-K.cjs → vue3-typescript-options-api-D4pqpDFs.cjs} +10 -10
- package/dist/{vue3-typescript-options-api-cPal4O-K.cjs.map → vue3-typescript-options-api-D4pqpDFs.cjs.map} +1 -1
- package/package.json +8 -5
- package/dist/rolldown-runtime-VH7oDXx4.cjs +0 -28
package/dist/main.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
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 { basename, dirname, join, resolve } from "node:path";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
9
10
|
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
10
12
|
import { installPackage } from "@antfu/install-pkg";
|
|
11
13
|
import semver from "semver";
|
|
12
14
|
import { warn } from "node:console";
|
|
@@ -29,6 +31,33 @@ var configFileRules = {
|
|
|
29
31
|
"import-x/no-extraneous-dependencies": "off",
|
|
30
32
|
"import-x/prefer-default-export": "off"
|
|
31
33
|
};
|
|
34
|
+
var LINT_TOPOLOGY_NAME = "insider/lint-topology";
|
|
35
|
+
/**
|
|
36
|
+
* The three files `useInsider()` requires a consumer to own. They are always linted with
|
|
37
|
+
* {@link buildLintTopologyBlocks}, independently of the `configFiles` option.
|
|
38
|
+
*/
|
|
39
|
+
var LINT_TOPOLOGY_GLOBS = [
|
|
40
|
+
"**/insider.lint.{ts,mts,cts,js,mjs,cjs}",
|
|
41
|
+
"**/eslint.config.{ts,mts,cts,js,mjs,cjs}",
|
|
42
|
+
"**/oxlint.config.{ts,mts,cts,js,mjs,cjs}"
|
|
43
|
+
];
|
|
44
|
+
var lintTopologyRules = {
|
|
45
|
+
...configFileRules,
|
|
46
|
+
"import-x/extensions": "off"
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Relaxations the mandated three-file layout needs in order to lint clean. Emitted unconditionally
|
|
50
|
+
* because the layout exists whether or not the consumer passes `configFiles`.
|
|
51
|
+
* @example
|
|
52
|
+
* buildLintTopologyBlocks()[0].rules['import-x/extensions']; // 'off'
|
|
53
|
+
*/
|
|
54
|
+
function buildLintTopologyBlocks() {
|
|
55
|
+
return [{
|
|
56
|
+
name: LINT_TOPOLOGY_NAME,
|
|
57
|
+
files: [...LINT_TOPOLOGY_GLOBS],
|
|
58
|
+
rules: { ...lintTopologyRules }
|
|
59
|
+
}];
|
|
60
|
+
}
|
|
32
61
|
function mergeGlobs(userGlobs) {
|
|
33
62
|
const seen = /* @__PURE__ */ new Set();
|
|
34
63
|
const merged = [];
|
|
@@ -58,17 +87,459 @@ function buildConfigFilesBlocks(value) {
|
|
|
58
87
|
function buildJqueryBlocks(entries) {
|
|
59
88
|
if (!entries || entries.length === 0) return [];
|
|
60
89
|
return entries.map((entry) => ({
|
|
61
|
-
files: entry.files,
|
|
90
|
+
files: [...entry.files],
|
|
62
91
|
languageOptions: { globals: { ...globals.jquery } }
|
|
63
92
|
}));
|
|
64
93
|
}
|
|
65
94
|
//#endregion
|
|
66
|
-
//#region src/
|
|
67
|
-
var
|
|
95
|
+
//#region src/builders/oxlint.ts
|
|
96
|
+
var OXLINT_SUPPRESSION_NAME = "insider/oxlint-suppression";
|
|
97
|
+
var OXLINT_SFC_EXCEPTION_NAME = "insider/oxlint-suppression-sfc-exception";
|
|
98
|
+
var UNUSED_DIRECTIVE_BLOCK_NAME = "insider/unused-disable-directives";
|
|
99
|
+
var UNUSED_DIRECTIVE_SEVERITY = "warn";
|
|
100
|
+
var OXLINT_DIRECTIVE_OPTIONS = {
|
|
101
|
+
respectEslintDisableDirectives: false,
|
|
102
|
+
reportUnusedDisableDirectives: UNUSED_DIRECTIVE_SEVERITY
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* The always-on ESLint block that makes a stale `eslint-disable` visible. ESLint 9 defaults this to
|
|
106
|
+
* `warn`, but the split relies on it, so it is pinned rather than inherited.
|
|
107
|
+
* @example
|
|
108
|
+
* buildUnusedDirectiveBlocks()[0].linterOptions; // { reportUnusedDisableDirectives: 'warn' }
|
|
109
|
+
*/
|
|
110
|
+
function buildUnusedDirectiveBlocks() {
|
|
111
|
+
return [{
|
|
112
|
+
name: UNUSED_DIRECTIVE_BLOCK_NAME,
|
|
113
|
+
linterOptions: { reportUnusedDisableDirectives: UNUSED_DIRECTIVE_SEVERITY }
|
|
114
|
+
}];
|
|
115
|
+
}
|
|
116
|
+
function ownedRuleNames(rules) {
|
|
117
|
+
return Object.keys(rules).filter((name) => isOxlintOwned(name) && !isRuleDisabled(rules[name]));
|
|
118
|
+
}
|
|
119
|
+
function translatableRuleNames(rules) {
|
|
120
|
+
return Object.keys(rules).filter(isOxlintOwned);
|
|
121
|
+
}
|
|
122
|
+
function toOffRecord(ruleNames) {
|
|
123
|
+
return Object.fromEntries(ruleNames.map((name) => [name, "off"]));
|
|
124
|
+
}
|
|
125
|
+
function suppressionFor(block) {
|
|
126
|
+
if (!block.rules) return [];
|
|
127
|
+
const owned = ownedRuleNames(block.rules);
|
|
128
|
+
if (owned.length === 0) return [];
|
|
129
|
+
const files = block.files ? { files: [...block.files] } : {};
|
|
130
|
+
const blockIgnores = block.ignores ? [...block.ignores] : [];
|
|
131
|
+
const vueSafe = owned.filter((name) => !isVueUnsafeRule(name));
|
|
132
|
+
const vueUnsafe = owned.filter((name) => isVueUnsafeRule(name));
|
|
133
|
+
const suppressions = [];
|
|
134
|
+
if (vueSafe.length > 0) suppressions.push({
|
|
135
|
+
name: OXLINT_SUPPRESSION_NAME,
|
|
136
|
+
...files,
|
|
137
|
+
...blockIgnores.length > 0 ? { ignores: blockIgnores } : {},
|
|
138
|
+
rules: toOffRecord(vueSafe)
|
|
139
|
+
});
|
|
140
|
+
if (vueUnsafe.length > 0) suppressions.push({
|
|
141
|
+
name: OXLINT_SFC_EXCEPTION_NAME,
|
|
142
|
+
...files,
|
|
143
|
+
ignores: [...blockIgnores, ...VUE_LIKE_GLOBS],
|
|
144
|
+
rules: toOffRecord(vueUnsafe)
|
|
145
|
+
});
|
|
146
|
+
return suppressions;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* ESLint blocks that turn off every rule the emitted oxlint config takes over. Rules oxlint cannot
|
|
150
|
+
* check inside an SFC are suppressed everywhere except `.vue`/`.svelte`/`.astro`, so a Vue consumer
|
|
151
|
+
* keeps them.
|
|
152
|
+
* @example
|
|
153
|
+
* buildOxlintSuppressionBlocks([{ files: ['src/**\/*.ts'], rules: { 'no-var': 'error' } }]);
|
|
154
|
+
* // [{ files: ['src/**\/*.ts'], rules: { 'no-var': 'off' } }]
|
|
155
|
+
*/
|
|
156
|
+
function buildOxlintSuppressionBlocks(blocks) {
|
|
157
|
+
return blocks.flatMap(suppressionFor);
|
|
158
|
+
}
|
|
159
|
+
function translateRules(rules, plugins) {
|
|
160
|
+
return translatableRuleNames(rules).reduce((accumulator, name) => {
|
|
161
|
+
plugins.add(toOxlintPluginName(name));
|
|
162
|
+
accumulator[toOxlintRuleName(name)] = rules[name];
|
|
163
|
+
return accumulator;
|
|
164
|
+
}, {});
|
|
165
|
+
}
|
|
166
|
+
function anchorOxlintGlob(glob) {
|
|
167
|
+
return glob.startsWith("**") || glob.startsWith("./") || glob.startsWith("/") ? glob : `./${glob}`;
|
|
168
|
+
}
|
|
169
|
+
function anchorGlobs(globs) {
|
|
170
|
+
return globs.map(anchorOxlintGlob);
|
|
171
|
+
}
|
|
172
|
+
function anchorOxlintIgnorePattern(pattern) {
|
|
173
|
+
const negated = pattern.startsWith("!");
|
|
174
|
+
const body = negated ? pattern.slice(1) : pattern;
|
|
175
|
+
const anchored = body.startsWith("/") || body.startsWith("**") ? body : `/${body}`;
|
|
176
|
+
return negated ? `!${anchored}` : anchored;
|
|
177
|
+
}
|
|
178
|
+
function overrideFor(block, plugins) {
|
|
179
|
+
if (!block.files || !block.rules) return [];
|
|
180
|
+
const translated = translateRules(block.rules, plugins);
|
|
181
|
+
if (Object.keys(translated).length === 0) return [];
|
|
182
|
+
return [{
|
|
183
|
+
files: anchorGlobs(block.files),
|
|
184
|
+
...block.ignores && block.ignores.length > 0 ? { excludeFiles: anchorGlobs(block.ignores) } : {},
|
|
185
|
+
rules: translated
|
|
186
|
+
}];
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The oxlint half of the split, derived from the same emitted ESLint blocks the suppression layer
|
|
190
|
+
* reads. Every oxlint category is pinned off so the config enables exactly the preset's own rules
|
|
191
|
+
* and nothing oxlint would otherwise turn on by default.
|
|
192
|
+
* @example
|
|
193
|
+
* buildOxlintConfig([{ files: ['src/**\/*.ts'], rules: { 'no-var': 'error' } }], ['dist']);
|
|
194
|
+
* // { plugins: ['eslint'], categories: { correctness: 'off', ... }, ignorePatterns: ['/dist'], ... }
|
|
195
|
+
*/
|
|
196
|
+
function buildOxlintConfig(blocks, ignores) {
|
|
197
|
+
const plugins = /* @__PURE__ */ new Set(["eslint"]);
|
|
198
|
+
const overrides = blocks.flatMap((block) => overrideFor(block, plugins));
|
|
199
|
+
const globalRules = blocks.filter((block) => !block.files && block.rules).reduce((accumulator, block) => ({
|
|
200
|
+
...accumulator,
|
|
201
|
+
...translateRules(block.rules, plugins)
|
|
202
|
+
}), {});
|
|
203
|
+
return {
|
|
204
|
+
plugins: [...plugins].sort((left, right) => left.localeCompare(right)),
|
|
205
|
+
categories: Object.fromEntries(OXLINT_CATEGORIES.map((name) => [name, "off"])),
|
|
206
|
+
...ignores && ignores.length > 0 ? { ignorePatterns: ignores.map(anchorOxlintIgnorePattern) } : {},
|
|
207
|
+
options: { ...OXLINT_DIRECTIVE_OPTIONS },
|
|
208
|
+
rules: globalRules,
|
|
209
|
+
overrides
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/utils/layoutDiagnosis.ts
|
|
214
|
+
var MODULE_EXTENSIONS = [
|
|
215
|
+
"ts",
|
|
216
|
+
"mts",
|
|
217
|
+
"cts",
|
|
218
|
+
"js",
|
|
219
|
+
"mjs",
|
|
220
|
+
"cjs"
|
|
221
|
+
];
|
|
222
|
+
var LINT_MODULE_FILENAMES = MODULE_EXTENSIONS.map((ext) => `insider.lint.${ext}`);
|
|
223
|
+
var ESLINT_CONFIG_FILENAMES = MODULE_EXTENSIONS.map((ext) => `eslint.config.${ext}`);
|
|
224
|
+
var OXLINT_CONFIG_FILENAMES = ["oxlint.config.ts", "oxlint.config.mts"];
|
|
68
225
|
var OPT_OUT_ENV = "INSIDER_SKIP_CONFIG_VALIDATION";
|
|
69
|
-
var
|
|
70
|
-
var
|
|
226
|
+
var MIGRATE_COMMAND = "npx @useinsider/eslint-config migrate";
|
|
227
|
+
var SCAFFOLD_COMMAND = "npx @useinsider/eslint-config";
|
|
71
228
|
var INSIDER_IMPORT_SOURCE = "@useinsider/eslint-config";
|
|
229
|
+
var TARGET_CALLEE_NAME = "useInsider";
|
|
230
|
+
var ESLINT_RENDERER_NAME = "useInsiderESLint";
|
|
231
|
+
var OXC_RENDERER_NAME = "useInsiderOXC";
|
|
232
|
+
var RENDERER_EXPORT = `export const ${`{ ${ESLINT_RENDERER_NAME}, ${OXC_RENDERER_NAME} }`} = ${TARGET_CALLEE_NAME}({ ... })`;
|
|
233
|
+
var EXPECTED_LAYOUT = [
|
|
234
|
+
"Expected layout (three sibling files):",
|
|
235
|
+
"",
|
|
236
|
+
" // insider.lint.ts",
|
|
237
|
+
` import { ${TARGET_CALLEE_NAME} } from '${INSIDER_IMPORT_SOURCE}';`,
|
|
238
|
+
"",
|
|
239
|
+
` ${RENDERER_EXPORT};`,
|
|
240
|
+
"",
|
|
241
|
+
" // eslint.config.ts",
|
|
242
|
+
` import { ${ESLINT_RENDERER_NAME} } from './insider.lint.ts';`,
|
|
243
|
+
"",
|
|
244
|
+
` export default ${ESLINT_RENDERER_NAME}();`,
|
|
245
|
+
"",
|
|
246
|
+
" // oxlint.config.ts",
|
|
247
|
+
` import { ${OXC_RENDERER_NAME} } from './insider.lint.ts';`,
|
|
248
|
+
"",
|
|
249
|
+
` export default await ${OXC_RENDERER_NAME}();`
|
|
250
|
+
].join("\n");
|
|
251
|
+
function findSibling(directory, filenames, fileExists) {
|
|
252
|
+
return filenames.find((filename) => fileExists(join(directory, filename))) ?? null;
|
|
253
|
+
}
|
|
254
|
+
var lazyRequire$1 = createRequire(import.meta.url);
|
|
255
|
+
function parseProgram(source) {
|
|
256
|
+
try {
|
|
257
|
+
const { parse } = lazyRequire$1("@typescript-eslint/parser");
|
|
258
|
+
return parse(source, {
|
|
259
|
+
sourceType: "module",
|
|
260
|
+
range: true,
|
|
261
|
+
loc: true
|
|
262
|
+
});
|
|
263
|
+
} catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function collectUseInsiderAliases(program) {
|
|
268
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
269
|
+
program.body.forEach((node) => {
|
|
270
|
+
if (node.type !== AST_NODE_TYPES.ImportDeclaration) return;
|
|
271
|
+
node.specifiers.forEach((specifier) => {
|
|
272
|
+
if (specifier.type === AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES.Identifier && specifier.imported.name === "useInsider") aliases.add(specifier.local.name);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
return aliases;
|
|
276
|
+
}
|
|
277
|
+
function callsAny(node, aliases) {
|
|
278
|
+
if (!node || typeof node !== "object") return false;
|
|
279
|
+
if (Array.isArray(node)) return node.some((child) => callsAny(child, aliases));
|
|
280
|
+
const candidate = node;
|
|
281
|
+
if (candidate.type === AST_NODE_TYPES.CallExpression) {
|
|
282
|
+
const { callee } = candidate;
|
|
283
|
+
if (callee.type === AST_NODE_TYPES.Identifier && aliases.has(callee.name)) return true;
|
|
284
|
+
}
|
|
285
|
+
return Object.entries(node).some(([key, value]) => key !== "parent" && callsAny(value, aliases));
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Whether a flat-config source is still the removed v3 shape: it imports `useInsider` and calls it
|
|
289
|
+
* itself, instead of importing a renderer from a sibling `insider.lint.*` module. Both the plain
|
|
290
|
+
* `export default useInsider({...})` form and the spread form are recognized.
|
|
291
|
+
* @example
|
|
292
|
+
* isV3EslintConfigSource("import { useInsider } from '@useinsider/eslint-config';\nexport default useInsider({});");
|
|
293
|
+
* // true
|
|
294
|
+
*/
|
|
295
|
+
function isV3EslintConfigSource(source) {
|
|
296
|
+
const program = parseProgram(source);
|
|
297
|
+
if (!program) return false;
|
|
298
|
+
const aliases = collectUseInsiderAliases(program);
|
|
299
|
+
if (aliases.size === 0) return false;
|
|
300
|
+
return program.body.some((node) => callsAny(node, aliases));
|
|
301
|
+
}
|
|
302
|
+
function classify(lintModule, eslintConfig, oxlintConfig, eslintConfigIsV3) {
|
|
303
|
+
if (lintModule && eslintConfig && oxlintConfig) return "complete";
|
|
304
|
+
if (!lintModule && !eslintConfig && !oxlintConfig) return "noConfig";
|
|
305
|
+
if (!lintModule && eslintConfig && eslintConfigIsV3) return "unmigratedV3";
|
|
306
|
+
return "partial";
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* What of the three-file layout is on disk in a directory, and which of the four guidance branches
|
|
310
|
+
* applies. `unmigratedV3` is the only branch the in-lint migration offer acts on, because it is the
|
|
311
|
+
* only one {@link import('../cli/migrate').runMigrate} can actually rewrite.
|
|
312
|
+
* @example
|
|
313
|
+
* diagnoseLayout('/repo').kind; // 'unmigratedV3' when eslint.config.js still calls useInsider()
|
|
314
|
+
*/
|
|
315
|
+
function diagnoseLayout(directory, options = {}) {
|
|
316
|
+
const fileExists = options.fileExists ?? existsSync;
|
|
317
|
+
const readSource = options.readSource ?? ((path) => readFileSync(path, "utf8"));
|
|
318
|
+
const lintModule = findSibling(directory, LINT_MODULE_FILENAMES, fileExists);
|
|
319
|
+
const eslintConfig = findSibling(directory, ESLINT_CONFIG_FILENAMES, fileExists);
|
|
320
|
+
const oxlintConfig = findSibling(directory, OXLINT_CONFIG_FILENAMES, fileExists);
|
|
321
|
+
let eslintConfigIsV3 = false;
|
|
322
|
+
if (eslintConfig && !lintModule) try {
|
|
323
|
+
eslintConfigIsV3 = isV3EslintConfigSource(readSource(join(directory, eslintConfig)));
|
|
324
|
+
} catch {
|
|
325
|
+
eslintConfigIsV3 = false;
|
|
326
|
+
}
|
|
327
|
+
const kind = classify(lintModule, eslintConfig, oxlintConfig, eslintConfigIsV3);
|
|
328
|
+
return {
|
|
329
|
+
kind,
|
|
330
|
+
directory,
|
|
331
|
+
lintModule,
|
|
332
|
+
eslintConfig,
|
|
333
|
+
oxlintConfig,
|
|
334
|
+
missing: [
|
|
335
|
+
lintModule ? null : "insider.lint.ts",
|
|
336
|
+
eslintConfig ? null : "eslint.config.ts",
|
|
337
|
+
oxlintConfig ? null : "oxlint.config.ts"
|
|
338
|
+
].filter((name) => name !== null),
|
|
339
|
+
autoMigratable: kind === "unmigratedV3"
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Import specifier for the consumer's lint module, naming the extension that is actually on disk so
|
|
344
|
+
* the suggested snippet resolves as printed.
|
|
345
|
+
* @example
|
|
346
|
+
* lintModuleSpecifier('/repo'); // './insider.lint.mts' when that is the file present
|
|
347
|
+
*/
|
|
348
|
+
function lintModuleSpecifier(directory, fileExists = existsSync) {
|
|
349
|
+
return `./${findSibling(directory, LINT_MODULE_FILENAMES, fileExists) ?? "insider.lint.ts"}`;
|
|
350
|
+
}
|
|
351
|
+
function inventoryLines(diagnosis) {
|
|
352
|
+
const v3Note = diagnosis.kind === "unmigratedV3" ? " (still calls `useInsider()` itself)" : "";
|
|
353
|
+
return [
|
|
354
|
+
diagnosis.lintModule ? ` present: ${diagnosis.lintModule}` : " MISSING: insider.lint.{ts,mts}",
|
|
355
|
+
diagnosis.eslintConfig ? ` present: ${diagnosis.eslintConfig}${v3Note}` : " MISSING: eslint.config.{ts,mts}",
|
|
356
|
+
diagnosis.oxlintConfig ? ` present: ${diagnosis.oxlintConfig}` : ` MISSING: ${OXLINT_CONFIG_FILENAMES.join(" or ")}`
|
|
357
|
+
];
|
|
358
|
+
}
|
|
359
|
+
function guidanceBody(diagnosis) {
|
|
360
|
+
if (diagnosis.kind === "unmigratedV3") return [
|
|
361
|
+
"`useInsider()` no longer returns a flat-config array, so the config it is called from has",
|
|
362
|
+
"to become a module both linters can import. The migration does that rewrite for you:",
|
|
363
|
+
"",
|
|
364
|
+
` ${MIGRATE_COMMAND}`
|
|
365
|
+
];
|
|
366
|
+
if (diagnosis.kind === "noConfig") return [
|
|
367
|
+
"There is no linter configuration here yet. Scaffold the three-file layout:",
|
|
368
|
+
"",
|
|
369
|
+
` ${SCAFFOLD_COMMAND}`
|
|
370
|
+
];
|
|
371
|
+
return [
|
|
372
|
+
`The layout is incomplete — missing ${diagnosis.missing.join(", ")}.`,
|
|
373
|
+
"Add the missing file, or let the migration regenerate the whole layout:",
|
|
374
|
+
"",
|
|
375
|
+
` ${MIGRATE_COMMAND}`
|
|
376
|
+
];
|
|
377
|
+
}
|
|
378
|
+
function detailLines(detail) {
|
|
379
|
+
if (!detail || detail.length === 0) return [];
|
|
380
|
+
return ["", ...detail];
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* The consumer-facing failure text: a site-specific headline, then the branch that matches what is
|
|
384
|
+
* on disk, then the layout dump, then the bypass. The command to run always comes before the dump,
|
|
385
|
+
* because a consumer who reads only the first screen still has to be told about `migrate`.
|
|
386
|
+
* @example
|
|
387
|
+
* formatLayoutGuidance({ headline: 'no `insider.lint.*` module found in /repo.', diagnosis });
|
|
388
|
+
*/
|
|
389
|
+
function formatLayoutGuidance(input) {
|
|
390
|
+
return [
|
|
391
|
+
`@useinsider/eslint-config: ${input.headline}`,
|
|
392
|
+
"",
|
|
393
|
+
...inventoryLines(input.diagnosis),
|
|
394
|
+
"",
|
|
395
|
+
...guidanceBody(input.diagnosis),
|
|
396
|
+
...detailLines(input.detail),
|
|
397
|
+
"",
|
|
398
|
+
EXPECTED_LAYOUT,
|
|
399
|
+
"",
|
|
400
|
+
`Emergency bypass: set ${OPT_OUT_ENV}=true (do not commit).`
|
|
401
|
+
].join("\n");
|
|
402
|
+
}
|
|
403
|
+
//#endregion
|
|
404
|
+
//#region src/utils/offerMigration.ts
|
|
405
|
+
var PACKAGE_NAME = "@useinsider/eslint-config";
|
|
406
|
+
var CLI_RELATIVE_PATH = "dist/cli.cjs";
|
|
407
|
+
var NO_PROMPT_ENV = "INSIDER_NO_PROMPT";
|
|
408
|
+
var IN_FLIGHT_ENV = "INSIDER_MIGRATION_IN_PROGRESS";
|
|
409
|
+
var MIGRATE_FROM_LINT_ARGS = ["migrate", "--from-lint"];
|
|
410
|
+
var BINARY_EXTENSIONS = /\.[mc]?js$/u;
|
|
411
|
+
function binaryName(argument) {
|
|
412
|
+
return basename(argument).replace(BINARY_EXTENSIONS, "");
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Which linter is hosting the current process. oxlint evaluates `oxlint.config.ts` inside the Node
|
|
416
|
+
* process that runs its own `bin/oxlint` wrapper, so `useInsider()` genuinely executes under both
|
|
417
|
+
* binaries and the two have to be told apart before anything is written to stdout.
|
|
418
|
+
* @example
|
|
419
|
+
* detectLintHost(['node', '/app/node_modules/oxlint/bin/oxlint', '.']); // 'oxlint'
|
|
420
|
+
*/
|
|
421
|
+
function detectLintHost(argv = process.argv) {
|
|
422
|
+
const names = argv.slice(1).map(binaryName);
|
|
423
|
+
if (names.includes("oxlint")) return "oxlint";
|
|
424
|
+
if (names.includes("eslint")) return "eslint";
|
|
425
|
+
return "unknown";
|
|
426
|
+
}
|
|
427
|
+
function defaultInteractive() {
|
|
428
|
+
return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
|
|
429
|
+
}
|
|
430
|
+
function skipReasonFor(env, argv, interactive) {
|
|
431
|
+
if (env[IN_FLIGHT_ENV] === "true") return "already-offered";
|
|
432
|
+
if (env.VITEST || env.NODE_ENV === "test") return "running-under-test";
|
|
433
|
+
if (env["INSIDER_SKIP_CONFIG_VALIDATION"] === "true" || env[NO_PROMPT_ENV] === "true") return "suppressed";
|
|
434
|
+
if (env.CI) return "running-in-ci";
|
|
435
|
+
if (detectLintHost(argv) === "oxlint") return "host-is-oxlint";
|
|
436
|
+
if (!interactive) return "no-tty";
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
function manifestNameAt(directory) {
|
|
440
|
+
const manifestPath = join(directory, "package.json");
|
|
441
|
+
if (!existsSync(manifestPath)) return null;
|
|
442
|
+
try {
|
|
443
|
+
return JSON.parse(readFileSync(manifestPath, "utf8")).name ?? null;
|
|
444
|
+
} catch {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function packageRootFrom(startDir) {
|
|
449
|
+
let current = startDir;
|
|
450
|
+
for (let depth = 0; depth < 10; depth += 1) {
|
|
451
|
+
if (manifestNameAt(current) === PACKAGE_NAME) return current;
|
|
452
|
+
const parent = dirname(current);
|
|
453
|
+
if (parent === current) return null;
|
|
454
|
+
current = parent;
|
|
455
|
+
}
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Absolute path to this package's own built CLI, or `null` when it is not on disk. Resolved by
|
|
460
|
+
* walking up from `startDir` — this module's own directory by default — rather than through
|
|
461
|
+
* `require.resolve`, so it works from `dist/` and from `src/` alike.
|
|
462
|
+
* @example
|
|
463
|
+
* resolveCliEntry(); // '/app/node_modules/@useinsider/eslint-config/dist/cli.cjs'
|
|
464
|
+
*/
|
|
465
|
+
function resolveCliEntry(startDir) {
|
|
466
|
+
const root = packageRootFrom(startDir ?? dirname(fileURLToPath(import.meta.url)));
|
|
467
|
+
if (!root) return null;
|
|
468
|
+
const entry = resolve(root, CLI_RELATIVE_PATH);
|
|
469
|
+
return existsSync(entry) ? entry : null;
|
|
470
|
+
}
|
|
471
|
+
function defaultRunCli(entry, args, cwd, env) {
|
|
472
|
+
const result = spawnSync(process.execPath, [entry, ...args], {
|
|
473
|
+
cwd,
|
|
474
|
+
env,
|
|
475
|
+
stdio: "inherit"
|
|
476
|
+
});
|
|
477
|
+
if (result.error) return 1;
|
|
478
|
+
return result.status ?? 1;
|
|
479
|
+
}
|
|
480
|
+
function defaultWarn(message) {
|
|
481
|
+
console.error(message);
|
|
482
|
+
}
|
|
483
|
+
function outdatedNotice(diagnosis) {
|
|
484
|
+
const config = diagnosis.eslintConfig ?? "eslint.config.*";
|
|
485
|
+
return [
|
|
486
|
+
`@useinsider/eslint-config: ${config} still uses the v3 single-file layout.`,
|
|
487
|
+
` ${join(diagnosis.directory, config)} calls \`useInsider()\` itself, and`,
|
|
488
|
+
` ${diagnosis.missing.join(" and ")} ${diagnosis.missing.length === 1 ? "is" : "are"} missing.`,
|
|
489
|
+
"",
|
|
490
|
+
"Starting the guided migration. It shows every change before applying it.",
|
|
491
|
+
`Skip it with ${NO_PROMPT_ENV}=true.`,
|
|
492
|
+
""
|
|
493
|
+
].join("\n");
|
|
494
|
+
}
|
|
495
|
+
var offerAttempted = false;
|
|
496
|
+
function withDefaults(deps) {
|
|
497
|
+
return {
|
|
498
|
+
env: deps.env ?? process.env,
|
|
499
|
+
argv: deps.argv ?? process.argv,
|
|
500
|
+
interactive: deps.interactive ?? defaultInteractive(),
|
|
501
|
+
resolveCliEntry: deps.resolveCliEntry ?? resolveCliEntry,
|
|
502
|
+
runCli: deps.runCli ?? defaultRunCli,
|
|
503
|
+
warn: deps.warn ?? defaultWarn
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Offers the guided migration for an outdated layout and runs it when the consumer accepts. Declines
|
|
508
|
+
* silently — returning a skip reason instead of prompting — whenever writing to stdout would be
|
|
509
|
+
* wrong: under oxlint, in CI, without a TTY, or when already inside a migration.
|
|
510
|
+
* @example
|
|
511
|
+
* offerMigration(diagnoseLayout('/repo')).accepted; // true after the consumer confirmed
|
|
512
|
+
*/
|
|
513
|
+
function offerMigration(diagnosis, deps = {}) {
|
|
514
|
+
const resolved = withDefaults(deps);
|
|
515
|
+
if (offerAttempted) return {
|
|
516
|
+
offered: false,
|
|
517
|
+
skipReason: "already-offered"
|
|
518
|
+
};
|
|
519
|
+
const skipReason = skipReasonFor(resolved.env, resolved.argv, resolved.interactive);
|
|
520
|
+
if (skipReason) return {
|
|
521
|
+
offered: false,
|
|
522
|
+
skipReason
|
|
523
|
+
};
|
|
524
|
+
const entry = resolved.resolveCliEntry();
|
|
525
|
+
if (!entry) return {
|
|
526
|
+
offered: false,
|
|
527
|
+
skipReason: "cli-not-found"
|
|
528
|
+
};
|
|
529
|
+
offerAttempted = true;
|
|
530
|
+
resolved.warn(outdatedNotice(diagnosis));
|
|
531
|
+
return {
|
|
532
|
+
offered: true,
|
|
533
|
+
accepted: resolved.runCli(entry, MIGRATE_FROM_LINT_ARGS, diagnosis.directory, {
|
|
534
|
+
...resolved.env,
|
|
535
|
+
[IN_FLIGHT_ENV]: "true"
|
|
536
|
+
}) === 0
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
//#endregion
|
|
540
|
+
//#region src/utils/ensureConfigStructure.ts
|
|
541
|
+
var ANCHOR_FILE_REGEX = /(?:insider\.lint|eslint\.config)\.[mc]?[jt]s(?:$|\?)/;
|
|
542
|
+
var FILE_URL_PREFIX = "file://";
|
|
72
543
|
var bypassWarnEmitted = false;
|
|
73
544
|
function computeSelfModulePath() {
|
|
74
545
|
const raw = fileURLToPath(import.meta.url);
|
|
@@ -83,7 +554,7 @@ function isPathSpecifier(source) {
|
|
|
83
554
|
return source.startsWith(".") || source.startsWith("/");
|
|
84
555
|
}
|
|
85
556
|
function defaultIsInsiderImportSource(source, configPath) {
|
|
86
|
-
if (source ===
|
|
557
|
+
if (source === "@useinsider/eslint-config") return true;
|
|
87
558
|
if (!configPath || !isPathSpecifier(source)) return false;
|
|
88
559
|
try {
|
|
89
560
|
const absolute = resolve(dirname(configPath), source);
|
|
@@ -98,24 +569,24 @@ function locOf(node) {
|
|
|
98
569
|
column: node.loc.start.column
|
|
99
570
|
};
|
|
100
571
|
}
|
|
101
|
-
function validateCallExpression(callExpr) {
|
|
572
|
+
function validateCallExpression(callExpr, expectedCallee) {
|
|
102
573
|
const { callee } = callExpr;
|
|
103
|
-
if (callee.type !== AST_NODE_TYPES.Identifier || callee.name !==
|
|
574
|
+
if (callee.type !== AST_NODE_TYPES.Identifier || callee.name !== expectedCallee) return [{
|
|
104
575
|
...locOf(callExpr),
|
|
105
|
-
reason:
|
|
576
|
+
reason: `Default export must call \`${expectedCallee}(...)\` directly.`
|
|
106
577
|
}];
|
|
107
578
|
return [];
|
|
108
579
|
}
|
|
109
|
-
function validateExportedCall(callee, fallbackLoc) {
|
|
580
|
+
function validateExportedCall(callee, fallbackLoc, expectedCallee) {
|
|
110
581
|
let target = callee;
|
|
111
582
|
if (target.type === AST_NODE_TYPES.AwaitExpression) target = target.argument;
|
|
112
|
-
if (target.type === AST_NODE_TYPES.CallExpression) return validateCallExpression(target);
|
|
583
|
+
if (target.type === AST_NODE_TYPES.CallExpression) return validateCallExpression(target, expectedCallee);
|
|
113
584
|
if (target.type === AST_NODE_TYPES.ArrayExpression) return [{
|
|
114
585
|
...locOf(target),
|
|
115
|
-
reason:
|
|
586
|
+
reason: `Default export must not wrap \`${expectedCallee}(...)\` in an array.`
|
|
116
587
|
}];
|
|
117
588
|
if (target.type === AST_NODE_TYPES.LogicalExpression || target.type === AST_NODE_TYPES.ConditionalExpression) {
|
|
118
|
-
const reason = [
|
|
589
|
+
const reason = [`Default export must not gate \`${expectedCallee}(...)\` behind a logical/conditional expression.`, "Use the typed options object instead."].join(" ");
|
|
119
590
|
return [{
|
|
120
591
|
...locOf(target),
|
|
121
592
|
reason
|
|
@@ -123,7 +594,7 @@ function validateExportedCall(callee, fallbackLoc) {
|
|
|
123
594
|
}
|
|
124
595
|
return [{
|
|
125
596
|
...fallbackLoc,
|
|
126
|
-
reason:
|
|
597
|
+
reason: `Default export must be a direct call to \`${expectedCallee}(...)\`.`
|
|
127
598
|
}];
|
|
128
599
|
}
|
|
129
600
|
function collectInsiderBindings(node, bindings, isInsiderSource) {
|
|
@@ -144,7 +615,7 @@ function classifyExpressionStatement(node, insiderBindings) {
|
|
|
144
615
|
}
|
|
145
616
|
};
|
|
146
617
|
}
|
|
147
|
-
function classifyStatement(node, exportSeen, insiderBindings) {
|
|
618
|
+
function classifyStatement(node, exportSeen, insiderBindings, expectedCallee) {
|
|
148
619
|
if (node.type === AST_NODE_TYPES.ImportDeclaration || node.type === AST_NODE_TYPES.VariableDeclaration) return { type: "ignore" };
|
|
149
620
|
if (node.type === AST_NODE_TYPES.ExpressionStatement) return classifyExpressionStatement(node, insiderBindings);
|
|
150
621
|
if (node.type === AST_NODE_TYPES.ExportDefaultDeclaration) {
|
|
@@ -158,13 +629,13 @@ function classifyStatement(node, exportSeen, insiderBindings) {
|
|
|
158
629
|
const declaration = node.declaration;
|
|
159
630
|
return {
|
|
160
631
|
type: "export",
|
|
161
|
-
violations: validateExportedCall(declaration, locOf(node))
|
|
632
|
+
violations: validateExportedCall(declaration, locOf(node), expectedCallee)
|
|
162
633
|
};
|
|
163
634
|
}
|
|
164
635
|
const reason = [
|
|
165
636
|
`Disallowed top-level statement \`${node.type}\`.`,
|
|
166
637
|
"Only imports, variable declarations, helper calls from `@useinsider/eslint-config`,",
|
|
167
|
-
|
|
638
|
+
`and one \`export default ${expectedCallee}()\` are allowed.`
|
|
168
639
|
].join(" ");
|
|
169
640
|
return {
|
|
170
641
|
type: "disallowed",
|
|
@@ -179,18 +650,112 @@ function validateProgramStructure(program, configPath = "", options = {}) {
|
|
|
179
650
|
const insiderBindings = /* @__PURE__ */ new Set();
|
|
180
651
|
const resolver = options.isInsiderImportSource ?? defaultIsInsiderImportSource;
|
|
181
652
|
const isInsiderSource = (source) => resolver(source, configPath);
|
|
653
|
+
const expectedCallee = options.expectedCallee ?? "useInsiderESLint";
|
|
182
654
|
let exportSeen = false;
|
|
183
655
|
program.body.forEach((node) => {
|
|
184
656
|
if (node.type === AST_NODE_TYPES.ImportDeclaration) collectInsiderBindings(node, insiderBindings, isInsiderSource);
|
|
185
|
-
const result = classifyStatement(node, exportSeen, insiderBindings);
|
|
657
|
+
const result = classifyStatement(node, exportSeen, insiderBindings, expectedCallee);
|
|
186
658
|
if (result.type === "ignore") return;
|
|
187
659
|
if (result.type === "export") {
|
|
188
660
|
exportSeen = true;
|
|
189
661
|
violations.push(...result.violations);
|
|
190
662
|
return;
|
|
191
663
|
}
|
|
192
|
-
|
|
193
|
-
|
|
664
|
+
violations.push(result.violation);
|
|
665
|
+
});
|
|
666
|
+
if (!exportSeen) violations.push({
|
|
667
|
+
line: 1,
|
|
668
|
+
column: 0,
|
|
669
|
+
reason: `Missing \`export default ${options.expectedCallee ?? "useInsiderESLint"}()\`.`
|
|
670
|
+
});
|
|
671
|
+
return violations;
|
|
672
|
+
}
|
|
673
|
+
function validateRendererPattern(pattern) {
|
|
674
|
+
const bound = /* @__PURE__ */ new Set();
|
|
675
|
+
const violations = [];
|
|
676
|
+
pattern.properties.forEach((property) => {
|
|
677
|
+
if (property.type !== AST_NODE_TYPES.Property || property.computed || !property.shorthand) {
|
|
678
|
+
violations.push({
|
|
679
|
+
...locOf(property),
|
|
680
|
+
reason: `Destructure \`${ESLINT_RENDERER_NAME}\` and \`${OXC_RENDERER_NAME}\` by name, without renaming or spreading.`
|
|
681
|
+
});
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
bound.add(property.key.name);
|
|
685
|
+
});
|
|
686
|
+
[ESLINT_RENDERER_NAME, OXC_RENDERER_NAME].forEach((name) => {
|
|
687
|
+
if (!bound.has(name)) violations.push({
|
|
688
|
+
...locOf(pattern),
|
|
689
|
+
reason: `Missing \`${name}\` in the destructured \`${TARGET_CALLEE_NAME}(...)\` result.`
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
return violations;
|
|
693
|
+
}
|
|
694
|
+
function validateRendererExport(node) {
|
|
695
|
+
const { declaration } = node;
|
|
696
|
+
if (!declaration || declaration.type !== AST_NODE_TYPES.VariableDeclaration || declaration.kind !== "const") return [{
|
|
697
|
+
...locOf(node),
|
|
698
|
+
reason: `Renderer export must be \`${RENDERER_EXPORT};\`.`
|
|
699
|
+
}];
|
|
700
|
+
if (declaration.declarations.length !== 1) return [{
|
|
701
|
+
...locOf(declaration),
|
|
702
|
+
reason: "Renderer export must declare exactly one binding."
|
|
703
|
+
}];
|
|
704
|
+
const [declarator] = declaration.declarations;
|
|
705
|
+
if (declarator.id.type !== AST_NODE_TYPES.ObjectPattern) return [{
|
|
706
|
+
...locOf(declarator.id),
|
|
707
|
+
reason: `Renderer export must destructure \`${ESLINT_RENDERER_NAME}\` and \`${OXC_RENDERER_NAME}\`.`
|
|
708
|
+
}];
|
|
709
|
+
const patternViolations = validateRendererPattern(declarator.id);
|
|
710
|
+
const { init } = declarator;
|
|
711
|
+
if (!init) return [...patternViolations, {
|
|
712
|
+
...locOf(declarator),
|
|
713
|
+
reason: `Renderer export must be initialized by a direct \`${TARGET_CALLEE_NAME}(...)\` call.`
|
|
714
|
+
}];
|
|
715
|
+
return [...patternViolations, ...validateExportedCall(init, locOf(declarator), TARGET_CALLEE_NAME)];
|
|
716
|
+
}
|
|
717
|
+
function classifyLintModuleStatement(node, exportSeen, insiderBindings) {
|
|
718
|
+
if (node.type === AST_NODE_TYPES.ImportDeclaration || node.type === AST_NODE_TYPES.VariableDeclaration) return { type: "ignore" };
|
|
719
|
+
if (node.type === AST_NODE_TYPES.ExpressionStatement) return classifyExpressionStatement(node, insiderBindings);
|
|
720
|
+
if (node.type === AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
721
|
+
if (exportSeen) return {
|
|
722
|
+
type: "duplicateExport",
|
|
723
|
+
violation: {
|
|
724
|
+
...locOf(node),
|
|
725
|
+
reason: "Only one export is allowed."
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
return {
|
|
729
|
+
type: "export",
|
|
730
|
+
violations: validateRendererExport(node)
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
const reason = [
|
|
734
|
+
`Disallowed top-level statement \`${node.type}\`.`,
|
|
735
|
+
"Only imports, variable declarations, helper calls from `@useinsider/eslint-config`,",
|
|
736
|
+
`and one \`${RENDERER_EXPORT}\` are allowed.`
|
|
737
|
+
].join(" ");
|
|
738
|
+
return {
|
|
739
|
+
type: "disallowed",
|
|
740
|
+
violation: {
|
|
741
|
+
...locOf(node),
|
|
742
|
+
reason
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
function validateLintModuleStructure(program, configPath = "", options = {}) {
|
|
747
|
+
const violations = [];
|
|
748
|
+
const insiderBindings = /* @__PURE__ */ new Set();
|
|
749
|
+
const resolver = options.isInsiderImportSource ?? defaultIsInsiderImportSource;
|
|
750
|
+
const isInsiderSource = (source) => resolver(source, configPath);
|
|
751
|
+
let exportSeen = false;
|
|
752
|
+
program.body.forEach((node) => {
|
|
753
|
+
if (node.type === AST_NODE_TYPES.ImportDeclaration) collectInsiderBindings(node, insiderBindings, isInsiderSource);
|
|
754
|
+
const result = classifyLintModuleStatement(node, exportSeen, insiderBindings);
|
|
755
|
+
if (result.type === "ignore") return;
|
|
756
|
+
if (result.type === "export") {
|
|
757
|
+
exportSeen = true;
|
|
758
|
+
violations.push(...result.violations);
|
|
194
759
|
return;
|
|
195
760
|
}
|
|
196
761
|
violations.push(result.violation);
|
|
@@ -198,7 +763,7 @@ function validateProgramStructure(program, configPath = "", options = {}) {
|
|
|
198
763
|
if (!exportSeen) violations.push({
|
|
199
764
|
line: 1,
|
|
200
765
|
column: 0,
|
|
201
|
-
reason:
|
|
766
|
+
reason: `Missing \`${RENDERER_EXPORT};\`.`
|
|
202
767
|
});
|
|
203
768
|
return violations;
|
|
204
769
|
}
|
|
@@ -222,7 +787,7 @@ function extractMatchesFromStack(frames) {
|
|
|
222
787
|
const matches = /* @__PURE__ */ new Set();
|
|
223
788
|
frames.forEach((frame) => {
|
|
224
789
|
const name = normalizeFrameFilename(frame.getFileName());
|
|
225
|
-
if (name &&
|
|
790
|
+
if (name && ANCHOR_FILE_REGEX.test(name)) matches.add(name);
|
|
226
791
|
});
|
|
227
792
|
return Array.from(matches);
|
|
228
793
|
}
|
|
@@ -235,13 +800,16 @@ function argvCandidateAt(argv, index) {
|
|
|
235
800
|
function extractConfigFromArgv(argv) {
|
|
236
801
|
for (let i = 0; i < argv.length; i += 1) {
|
|
237
802
|
const candidate = argvCandidateAt(argv, i);
|
|
238
|
-
if (candidate &&
|
|
803
|
+
if (candidate && ANCHOR_FILE_REGEX.test(candidate)) return candidate;
|
|
239
804
|
}
|
|
240
805
|
return null;
|
|
241
806
|
}
|
|
807
|
+
var LINT_MODULE_REGEX = /insider\.lint\.[mc]?[jt]s(?:$|\?)/;
|
|
242
808
|
function getConsumerConfigPath(frames = readStackFrames(), argv = process.argv) {
|
|
243
809
|
const stackMatches = extractMatchesFromStack(frames);
|
|
244
810
|
if (stackMatches.length === 1) return stackMatches[0];
|
|
811
|
+
const lintModules = stackMatches.filter((match) => LINT_MODULE_REGEX.test(match));
|
|
812
|
+
if (lintModules.length === 1) return lintModules[0];
|
|
245
813
|
const fromArgv = extractConfigFromArgv(argv);
|
|
246
814
|
if (fromArgv) return fromArgv;
|
|
247
815
|
return null;
|
|
@@ -251,11 +819,12 @@ function captureConsumerConfigDir(configPath) {
|
|
|
251
819
|
consumerConfigDir = configPath === null ? null : dirname(resolve(configPath));
|
|
252
820
|
}
|
|
253
821
|
/**
|
|
254
|
-
* Directory holding the consumer's `
|
|
255
|
-
* This is the anchor `tsconfigPaths` entries are written against
|
|
256
|
-
*
|
|
822
|
+
* Directory holding the consumer's `insider.lint.*` module and its sibling linter configs, captured
|
|
823
|
+
* by {@link ensureConfigStructure}. This is the anchor `tsconfigPaths` entries are written against,
|
|
824
|
+
* and where {@link import('./ensureOxlintConfig').ensureOxlintConfig} looks for the oxlint config;
|
|
825
|
+
* it falls back to `process.cwd()` when the module could not be located.
|
|
257
826
|
* @example
|
|
258
|
-
* // consumer
|
|
827
|
+
* // consumer module at /repo/insider.lint.ts, `eslint` launched from /repo/app
|
|
259
828
|
* getConsumerConfigDir(); // '/repo'
|
|
260
829
|
*/
|
|
261
830
|
function getConsumerConfigDir() {
|
|
@@ -266,12 +835,12 @@ function formatReport(configPath, violations) {
|
|
|
266
835
|
const lines = violations.map((violation) => ` - [${violation.line}:${violation.column}] ${violation.reason}`);
|
|
267
836
|
const footer = [
|
|
268
837
|
"",
|
|
269
|
-
"
|
|
270
|
-
"
|
|
271
|
-
"
|
|
272
|
-
|
|
838
|
+
"Alongside the export, only import statements, const/let/var declarations and helper calls",
|
|
839
|
+
"imported from `@useinsider/eslint-config` are allowed.",
|
|
840
|
+
"",
|
|
841
|
+
EXPECTED_LAYOUT,
|
|
273
842
|
"",
|
|
274
|
-
|
|
843
|
+
`Emergency bypass: set ${OPT_OUT_ENV}=true (do not commit).`
|
|
275
844
|
].join("\n");
|
|
276
845
|
return `${header}\n${lines.join("\n")}\n${footer}`;
|
|
277
846
|
}
|
|
@@ -312,21 +881,57 @@ function parseConsumerSource(configPath, source) {
|
|
|
312
881
|
return fail(`@useinsider/eslint-config: failed to parse consumer config at ${configPath}: ${message}`);
|
|
313
882
|
}
|
|
314
883
|
}
|
|
315
|
-
function
|
|
316
|
-
|
|
317
|
-
|
|
884
|
+
function resolveSibling(directory, filenames) {
|
|
885
|
+
const found = filenames.find((filename) => existsSync(resolve(directory, filename)));
|
|
886
|
+
return found ? resolve(directory, found) : null;
|
|
887
|
+
}
|
|
888
|
+
function validateFile(filePath, validator, options) {
|
|
889
|
+
const violations = validator(parseConsumerSource(filePath, readConsumerSource(filePath)), filePath, options);
|
|
318
890
|
if (violations.length === 0) return;
|
|
319
|
-
fail(formatReport(
|
|
891
|
+
fail(formatReport(filePath, violations));
|
|
320
892
|
}
|
|
321
|
-
|
|
893
|
+
var MIGRATED_EXIT_NOTICE = [
|
|
894
|
+
"@useinsider/eslint-config: migration finished.",
|
|
895
|
+
"",
|
|
896
|
+
"Re-run your lint command. This process already loaded the old configuration, so continuing now",
|
|
897
|
+
"would lint against a config that no longer exists on disk."
|
|
898
|
+
].join("\n");
|
|
899
|
+
var DECLINED_DETAIL = ["The guided migration was not completed, so nothing was written."];
|
|
900
|
+
function failWithGuidance(headline, diagnosis, detail) {
|
|
901
|
+
return fail(formatLayoutGuidance({
|
|
902
|
+
headline,
|
|
903
|
+
diagnosis,
|
|
904
|
+
detail
|
|
905
|
+
}));
|
|
906
|
+
}
|
|
907
|
+
function handleOutdatedLayout(headline, diagnosis, offer) {
|
|
908
|
+
if (!diagnosis.autoMigratable) return failWithGuidance(headline, diagnosis);
|
|
909
|
+
const outcome = offer(diagnosis);
|
|
910
|
+
if (!outcome.offered) return failWithGuidance(headline, diagnosis);
|
|
911
|
+
if (!outcome.accepted) return failWithGuidance(headline, diagnosis, DECLINED_DETAIL);
|
|
912
|
+
return fail(MIGRATED_EXIT_NOTICE);
|
|
913
|
+
}
|
|
914
|
+
function runValidation(anchorPath, deps = {}) {
|
|
915
|
+
const diagnose = deps.diagnose ?? ((directory) => diagnoseLayout(directory));
|
|
916
|
+
const offer = deps.offer ?? offerMigration;
|
|
917
|
+
if (!anchorPath) handleOutdatedLayout("unable to locate the consumer `insider.lint.*` module for structure validation.", diagnose(process.cwd()), offer);
|
|
918
|
+
const directory = dirname(resolve(anchorPath));
|
|
919
|
+
const lintModulePath = resolveSibling(directory, LINT_MODULE_FILENAMES);
|
|
920
|
+
if (!lintModulePath) handleOutdatedLayout(`no \`insider.lint.*\` module found in ${directory}.`, diagnose(directory), offer);
|
|
921
|
+
validateFile(lintModulePath, validateLintModuleStructure, {});
|
|
922
|
+
const eslintConfigPath = resolveSibling(directory, ESLINT_CONFIG_FILENAMES);
|
|
923
|
+
if (!eslintConfigPath) failWithGuidance(`no \`eslint.config.*\` file found in ${directory}.`, diagnose(directory), [`\`${TARGET_CALLEE_NAME}()\` returns renderers, not a flat-config array, so ESLint still`, `needs a config file whose default export is \`${ESLINT_RENDERER_NAME}()\`.`]);
|
|
924
|
+
validateFile(eslintConfigPath, validateProgramStructure, { expectedCallee: ESLINT_RENDERER_NAME });
|
|
925
|
+
}
|
|
926
|
+
function ensureConfigStructure(overridePath, deps = {}) {
|
|
322
927
|
const configPath = overridePath === void 0 ? getConsumerConfigPath() : overridePath;
|
|
323
928
|
captureConsumerConfigDir(configPath);
|
|
324
|
-
if (process.env[
|
|
929
|
+
if (process.env["INSIDER_SKIP_CONFIG_VALIDATION"] === "true") {
|
|
325
930
|
emitBypassWarning();
|
|
326
931
|
return;
|
|
327
932
|
}
|
|
328
933
|
try {
|
|
329
|
-
runValidation(configPath);
|
|
934
|
+
runValidation(configPath, deps);
|
|
330
935
|
} catch (error) {
|
|
331
936
|
if (error instanceof ConfigStructureExit) return;
|
|
332
937
|
throw error;
|
|
@@ -450,6 +1055,14 @@ function clonePreset(preset) {
|
|
|
450
1055
|
function isScopeName(key) {
|
|
451
1056
|
return Object.keys(scopeMap).includes(key);
|
|
452
1057
|
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Every scope name `useInsider()` accepts, for error messages that have to spell the options out.
|
|
1060
|
+
* @example
|
|
1061
|
+
* scopeNames().includes('typescriptNode'); // true
|
|
1062
|
+
*/
|
|
1063
|
+
function scopeNames() {
|
|
1064
|
+
return Object.keys(scopeMap);
|
|
1065
|
+
}
|
|
453
1066
|
function applyCommonEntryFields(block, entry) {
|
|
454
1067
|
if (entry.ignores && entry.ignores.length > 0) block.ignores = [...entry.ignores];
|
|
455
1068
|
if (entry.globals) {
|
|
@@ -465,7 +1078,7 @@ async function buildScopeBlocks(scope, entries) {
|
|
|
465
1078
|
const preset = (await scopeMap[scope]()).default;
|
|
466
1079
|
return entries.map((entry) => {
|
|
467
1080
|
const block = clonePreset(preset);
|
|
468
|
-
block.files = entry.files;
|
|
1081
|
+
block.files = [...entry.files];
|
|
469
1082
|
if (tsScopes.has(scope)) {
|
|
470
1083
|
const { tsconfigPaths } = entry;
|
|
471
1084
|
if (tsconfigPaths) applyTypescriptOverrides(block, { tsconfigPaths });
|
|
@@ -499,7 +1112,7 @@ async function buildStrictBlocks(value) {
|
|
|
499
1112
|
const { default: strictConfig } = await import("./strict-BHw_cKrv.js");
|
|
500
1113
|
const clone = { ...strictConfig };
|
|
501
1114
|
if (clone.rules) clone.rules = { ...clone.rules };
|
|
502
|
-
if (isFileScoped) clone.files = value;
|
|
1115
|
+
if (isFileScoped) clone.files = [...value];
|
|
503
1116
|
else delete clone.files;
|
|
504
1117
|
return [clone];
|
|
505
1118
|
}
|
|
@@ -508,7 +1121,11 @@ async function buildStrictBlocks(value) {
|
|
|
508
1121
|
var turnOff = false;
|
|
509
1122
|
var scanned = false;
|
|
510
1123
|
var silencedDependencies = /* @__PURE__ */ new Set();
|
|
511
|
-
var fixedDependencies = [
|
|
1124
|
+
var fixedDependencies = [
|
|
1125
|
+
"eslint",
|
|
1126
|
+
"globals",
|
|
1127
|
+
"oxlint"
|
|
1128
|
+
];
|
|
512
1129
|
var require = createRequire(import.meta.url);
|
|
513
1130
|
function getPackageJSONPath(currentDir) {
|
|
514
1131
|
const fileLocation = resolve(currentDir, "package.json");
|
|
@@ -564,6 +1181,42 @@ function silenceDependencyWarning(input) {
|
|
|
564
1181
|
input.forEach((dependency) => silencedDependencies.add(String(dependency)));
|
|
565
1182
|
}
|
|
566
1183
|
//#endregion
|
|
1184
|
+
//#region src/utils/ensureOxlintConfig.ts
|
|
1185
|
+
function formatMessage(directory) {
|
|
1186
|
+
return formatLayoutGuidance({
|
|
1187
|
+
headline: `no oxlint config found in ${directory}.`,
|
|
1188
|
+
diagnosis: diagnoseLayout(directory),
|
|
1189
|
+
detail: [
|
|
1190
|
+
"useInsiderESLint() turns off every rule oxlint owns, so running ESLint alone would silently",
|
|
1191
|
+
"drop those checks. Create the sibling oxlint config:",
|
|
1192
|
+
"",
|
|
1193
|
+
" // oxlint.config.ts",
|
|
1194
|
+
` import { useInsiderOXC } from '${lintModuleSpecifier(directory)}';`,
|
|
1195
|
+
"",
|
|
1196
|
+
" export default await useInsiderOXC();",
|
|
1197
|
+
"",
|
|
1198
|
+
"and run both linters:",
|
|
1199
|
+
"",
|
|
1200
|
+
" \"lint\": \"oxlint --disable-nested-config && eslint .\"",
|
|
1201
|
+
"",
|
|
1202
|
+
`oxlint discovers only ${OXLINT_CONFIG_FILENAMES.join(" and ")} — any other extension is`,
|
|
1203
|
+
"ignored without a word."
|
|
1204
|
+
]
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Fails when the consumer enabled the ESLint-side oxlint suppression without a sibling oxlint
|
|
1209
|
+
* config to run the suppressed rules. Resolved against the directory
|
|
1210
|
+
* {@link getConsumerConfigDir} captured during `useInsider()`.
|
|
1211
|
+
* @example
|
|
1212
|
+
* ensureOxlintConfig(); // throws unless oxlint.config.{ts,mts} sits beside insider.lint.*
|
|
1213
|
+
*/
|
|
1214
|
+
function ensureOxlintConfig(directory = getConsumerConfigDir()) {
|
|
1215
|
+
if (process.env["INSIDER_SKIP_CONFIG_VALIDATION"] === "true") return;
|
|
1216
|
+
if (OXLINT_CONFIG_FILENAMES.some((filename) => existsSync(join(directory, filename)))) return;
|
|
1217
|
+
throw new Error(formatMessage(directory));
|
|
1218
|
+
}
|
|
1219
|
+
//#endregion
|
|
567
1220
|
//#region src/utils/silenceRules.ts
|
|
568
1221
|
var globallySilencedRules = /* @__PURE__ */ new Set();
|
|
569
1222
|
/**
|
|
@@ -583,14 +1236,25 @@ function silenceRule(config) {
|
|
|
583
1236
|
const [, ...restConfig] = Array.isArray(config) ? config : [];
|
|
584
1237
|
return ["warn", ...restConfig];
|
|
585
1238
|
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Reports a silenced rule name that no emitted block declares, which is how a typo in
|
|
1241
|
+
* `silenceRules` stays visible. The caller passes the union of every block's rule keys: a single
|
|
1242
|
+
* block is not evidence, because the emission is many narrow blocks and a correctly spelled rule is
|
|
1243
|
+
* absent from nearly all of them.
|
|
1244
|
+
* @example
|
|
1245
|
+
* warnUnknownSilencedRules(['no-console'], ['no-unknown-rule']); // warns once
|
|
1246
|
+
*/
|
|
1247
|
+
function warnUnknownSilencedRules(knownRules, silencedRules) {
|
|
1248
|
+
const known = new Set(knownRules);
|
|
1249
|
+
new Set(silencedRules).forEach((rule) => {
|
|
1250
|
+
if (known.has(rule)) return;
|
|
1251
|
+
warn(`Unknown rule "${rule}". Please remove it from the silenced rules list.`);
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
586
1254
|
function filterSilencedRules(rules, silencedRules) {
|
|
587
1255
|
let targetSilencedRules = silencedRules || globallySilencedRules;
|
|
588
1256
|
if (Array.isArray(targetSilencedRules)) targetSilencedRules = new Set(targetSilencedRules);
|
|
589
1257
|
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
1258
|
return Object.entries(rules).reduce((newRules, [key, value]) => {
|
|
595
1259
|
if (value) newRules[key] = targetSilencedRules.has(key) && !isDisabled(value) ? silenceRule(value) : value;
|
|
596
1260
|
return newRules;
|
|
@@ -613,6 +1277,11 @@ function assertNoRemovedKeys(config) {
|
|
|
613
1277
|
const details = used.map((key) => ` - \`${key}\`: ${removedKeys.get(key)}`).join("\n");
|
|
614
1278
|
throw new Error(`@useinsider/eslint-config: removed option(s) passed to useInsider():\n${details}`);
|
|
615
1279
|
}
|
|
1280
|
+
function assertNoUnknownKeys(config) {
|
|
1281
|
+
const unknown = Object.keys(config).filter((key) => !reservedKeys.has(key) && !removedKeys.has(key) && !isScopeName(key));
|
|
1282
|
+
if (unknown.length === 0) return;
|
|
1283
|
+
throw new Error(`@useinsider/eslint-config: unknown option(s) passed to useInsider(): ${unknown.join(", ")}.\n Scopes: ${scopeNames().join(", ")}\n Other options: ${[...reservedKeys].join(", ")}`);
|
|
1284
|
+
}
|
|
616
1285
|
function isProcessableScopeKey(key) {
|
|
617
1286
|
return !reservedKeys.has(key) && isScopeName(key);
|
|
618
1287
|
}
|
|
@@ -626,6 +1295,7 @@ async function collectScopeBlocks(config) {
|
|
|
626
1295
|
}, Promise.resolve([]));
|
|
627
1296
|
}
|
|
628
1297
|
function applySilenceRules(blocks, perCallSilence) {
|
|
1298
|
+
if (perCallSilence) warnUnknownSilencedRules(blocks.flatMap((block) => Object.keys(block.rules ?? {})), perCallSilence);
|
|
629
1299
|
blocks.forEach((block) => {
|
|
630
1300
|
if (!block.rules) return;
|
|
631
1301
|
block.rules = perCallSilence ? filterSilencedRules(block.rules, perCallSilence) : filterSilencedRules(block.rules);
|
|
@@ -635,28 +1305,43 @@ function buildIgnoresBlocks(ignores) {
|
|
|
635
1305
|
if (!ignores || ignores.length === 0) return [];
|
|
636
1306
|
return [{ ignores: [...ignores] }];
|
|
637
1307
|
}
|
|
638
|
-
async function
|
|
639
|
-
assertNoRemovedKeys(config);
|
|
640
|
-
ensureConfigStructure();
|
|
641
|
-
await ensureDependencies();
|
|
642
|
-
const ignoresBlocks = buildIgnoresBlocks(config.ignores);
|
|
1308
|
+
async function buildBaseBlocks(config) {
|
|
643
1309
|
const scopeBlocks = await collectScopeBlocks(config);
|
|
644
|
-
const jqueryBlocks = buildJqueryBlocks(config.jquery);
|
|
645
|
-
const configFilesBlocks = buildConfigFilesBlocks(config.configFiles);
|
|
646
1310
|
const strictBlocks = await buildStrictBlocks(config.strict);
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
...
|
|
1311
|
+
return [
|
|
1312
|
+
...buildIgnoresBlocks(config.ignores),
|
|
1313
|
+
...buildUnusedDirectiveBlocks(),
|
|
650
1314
|
...scopeBlocks,
|
|
651
|
-
...
|
|
652
|
-
...
|
|
1315
|
+
...buildJqueryBlocks(config.jquery),
|
|
1316
|
+
...buildConfigFilesBlocks(config.configFiles),
|
|
653
1317
|
...strictBlocks,
|
|
654
|
-
...
|
|
1318
|
+
...buildLintTopologyBlocks(),
|
|
1319
|
+
...buildSpellCheckerBlocks(config.spellChecker)
|
|
655
1320
|
];
|
|
1321
|
+
}
|
|
1322
|
+
async function renderEslint(config) {
|
|
1323
|
+
await ensureDependencies();
|
|
1324
|
+
ensureOxlintConfig();
|
|
1325
|
+
const baseBlocks = await buildBaseBlocks(config);
|
|
1326
|
+
const blocks = [...baseBlocks, ...buildOxlintSuppressionBlocks(baseBlocks)];
|
|
656
1327
|
applySilenceRules(blocks, config.silenceRules);
|
|
657
1328
|
return defineConfig(blocks);
|
|
658
1329
|
}
|
|
1330
|
+
async function renderOxlint(config) {
|
|
1331
|
+
const baseBlocks = await buildBaseBlocks(config);
|
|
1332
|
+
applySilenceRules(baseBlocks, config.silenceRules);
|
|
1333
|
+
return buildOxlintConfig(baseBlocks, config.ignores);
|
|
1334
|
+
}
|
|
1335
|
+
function useInsider(config) {
|
|
1336
|
+
assertNoRemovedKeys(config);
|
|
1337
|
+
assertNoUnknownKeys(config);
|
|
1338
|
+
ensureConfigStructure();
|
|
1339
|
+
return {
|
|
1340
|
+
useInsiderESLint: () => renderEslint(config),
|
|
1341
|
+
useInsiderOXC: () => renderOxlint(config)
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
659
1344
|
//#endregion
|
|
660
|
-
export { ensureConfigStructure, merge, mergeAll, silenceDependencyWarning, silenceRules, useInsider };
|
|
1345
|
+
export { ensureConfigStructure, ensureOxlintConfig, merge, mergeAll, silenceDependencyWarning, silenceRules, useInsider };
|
|
661
1346
|
|
|
662
1347
|
//# sourceMappingURL=main.js.map
|