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