@utoo/lint 0.0.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/bin/migrate.js ADDED
@@ -0,0 +1,361 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname, extname, resolve as resolvePath } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+
6
+ import { Linter } from "../index.js";
7
+
8
+ const ESLINT_CONFIG_FILENAMES = [
9
+ "eslint.config.js",
10
+ "eslint.config.mjs",
11
+ "eslint.config.cjs",
12
+ ".eslintrc.json",
13
+ ".eslintrc"
14
+ ];
15
+ const JAVASCRIPT_CONFIG_EXTENSIONS = new Set([".js", ".mjs", ".cjs"]);
16
+ const IGNORED_RULES = new Map([
17
+ ["prettier/prettier", "formatting is outside utoo-lint"]
18
+ ]);
19
+ const SCHEMA_URL = "https://raw.githubusercontent.com/fireairforce/utoo-lint/main/npm/utoo-lint/schema.json";
20
+ const JAVASCRIPT_CONFIG_LOADER_SCRIPT = `
21
+ import { pathToFileURL } from "node:url";
22
+
23
+ const loaded = await import(pathToFileURL(process.argv[1]).href);
24
+ const seen = new Set();
25
+
26
+ function strip(value) {
27
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
28
+ return value;
29
+ }
30
+ if (typeof value === "function" || typeof value === "symbol" || typeof value === "undefined") {
31
+ return undefined;
32
+ }
33
+ if (typeof value !== "object") {
34
+ return undefined;
35
+ }
36
+ if (seen.has(value)) {
37
+ return undefined;
38
+ }
39
+ seen.add(value);
40
+ if (Array.isArray(value)) {
41
+ const items = value.map(strip).filter((item) => item !== undefined);
42
+ seen.delete(value);
43
+ return items;
44
+ }
45
+ const object = {};
46
+ for (const [key, item] of Object.entries(value)) {
47
+ const stripped = strip(item);
48
+ if (stripped !== undefined) {
49
+ object[key] = stripped;
50
+ }
51
+ }
52
+ seen.delete(value);
53
+ return object;
54
+ }
55
+
56
+ const json = JSON.stringify(strip(loaded.default ?? loaded));
57
+ if (json === undefined) {
58
+ throw new TypeError("config did not export a migratable value");
59
+ }
60
+ process.stdout.write(json);
61
+ `;
62
+
63
+ export async function runMigrate(args) {
64
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
65
+ printMigrateHelp();
66
+ return 0;
67
+ }
68
+
69
+ const source = args[0];
70
+ if (source !== "eslint") {
71
+ console.error(`utoo-lint migrate: unsupported source "${source}"`);
72
+ console.error("Supported sources: eslint");
73
+ return 2;
74
+ }
75
+
76
+ let options;
77
+ try {
78
+ options = parseEslintMigrateArgs(args.slice(1));
79
+ } catch (error) {
80
+ console.error(error.message);
81
+ return 2;
82
+ }
83
+
84
+ if (options.help) {
85
+ printEslintMigrateHelp();
86
+ return 0;
87
+ }
88
+
89
+ let result;
90
+ try {
91
+ result = migrateEslintConfig(options);
92
+ } catch (error) {
93
+ console.error(error.message);
94
+ return 2;
95
+ }
96
+
97
+ const configJson = JSON.stringify(result.config, null, 2) + "\n";
98
+ if (options.print) {
99
+ process.stdout.write(configJson);
100
+ } else {
101
+ if (existsSync(options.output) && !options.force) {
102
+ console.error(`utoo-lint migrate eslint: ${options.output} already exists; pass --force to overwrite it`);
103
+ return 2;
104
+ }
105
+ writeFileSync(options.output, configJson);
106
+ }
107
+
108
+ writeReport(result.report, options, options.print ? process.stderr : process.stdout);
109
+ return result.report.unsupportedRules.length > 0 ? 1 : 0;
110
+ }
111
+
112
+ function parseEslintMigrateArgs(args) {
113
+ const options = {
114
+ cwd: process.cwd(),
115
+ force: false,
116
+ from: undefined,
117
+ help: false,
118
+ output: "utoo.json",
119
+ print: false,
120
+ report: "text"
121
+ };
122
+
123
+ for (let index = 0; index < args.length; index += 1) {
124
+ const arg = args[index];
125
+ if (arg === "--help" || arg === "-h") {
126
+ options.help = true;
127
+ } else if (arg === "--force") {
128
+ options.force = true;
129
+ } else if (arg === "--print") {
130
+ options.print = true;
131
+ } else if (arg === "--from") {
132
+ options.from = requireValue(args, ++index, "--from");
133
+ } else if (arg.startsWith("--from=")) {
134
+ options.from = arg.slice("--from=".length);
135
+ } else if (arg === "--output" || arg === "-o") {
136
+ options.output = requireValue(args, ++index, arg);
137
+ } else if (arg.startsWith("--output=")) {
138
+ options.output = arg.slice("--output=".length);
139
+ } else if (arg === "--report") {
140
+ options.report = parseReportFormat(requireValue(args, ++index, "--report"));
141
+ } else if (arg.startsWith("--report=")) {
142
+ options.report = parseReportFormat(arg.slice("--report=".length));
143
+ } else {
144
+ throw new Error(`utoo-lint migrate eslint: unknown option ${arg}`);
145
+ }
146
+ }
147
+
148
+ options.cwd = resolvePath(options.cwd);
149
+ options.output = resolvePath(options.cwd, options.output);
150
+ if (options.from) {
151
+ options.from = resolvePath(options.cwd, options.from);
152
+ }
153
+ return options;
154
+ }
155
+
156
+ function requireValue(args, index, flag) {
157
+ const value = args[index];
158
+ if (!value) {
159
+ throw new Error(`utoo-lint migrate eslint: ${flag} requires a value`);
160
+ }
161
+ return value;
162
+ }
163
+
164
+ function parseReportFormat(value) {
165
+ if (value === "text" || value === "json") {
166
+ return value;
167
+ }
168
+ throw new Error(`utoo-lint migrate eslint: unsupported --report value ${value}`);
169
+ }
170
+
171
+ function migrateEslintConfig(options) {
172
+ const sourcePath = options.from ?? findEslintConfig(options.cwd);
173
+ if (!sourcePath) {
174
+ throw new Error("utoo-lint migrate eslint: no ESLint config found; pass --from PATH");
175
+ }
176
+
177
+ const eslintConfig = loadEslintConfig(sourcePath, options.cwd);
178
+ const supportedRuleIds = new Set(new Linter().getRules().keys());
179
+ const entries = normalizeConfigEntries(eslintConfig);
180
+ const rules = {};
181
+ const files = new Set();
182
+ const ignores = new Set();
183
+ const supportedRules = [];
184
+ const unsupportedRules = [];
185
+ const ignoredRules = [];
186
+ const optionDroppedRules = [];
187
+
188
+ for (const entry of entries) {
189
+ addPatterns(files, entry.files);
190
+ addPatterns(ignores, entry.ignores);
191
+ if (!entry.rules || typeof entry.rules !== "object") {
192
+ continue;
193
+ }
194
+ for (const [ruleId, ruleConfig] of Object.entries(entry.rules)) {
195
+ if (IGNORED_RULES.has(ruleId)) {
196
+ ignoredRules.push({ ruleId, reason: IGNORED_RULES.get(ruleId) });
197
+ continue;
198
+ }
199
+ if (!supportedRuleIds.has(ruleId)) {
200
+ unsupportedRules.push(ruleId);
201
+ continue;
202
+ }
203
+ rules[ruleId] = severityOnly(ruleConfig);
204
+ supportedRules.push(ruleId);
205
+ if (Array.isArray(ruleConfig) && ruleConfig.length > 1) {
206
+ optionDroppedRules.push(ruleId);
207
+ }
208
+ }
209
+ }
210
+
211
+ const config = {
212
+ $schema: SCHEMA_URL
213
+ };
214
+ if (files.size > 0) {
215
+ config.files = [...files];
216
+ }
217
+ if (ignores.size > 0) {
218
+ config.ignores = [...ignores];
219
+ }
220
+ config.rules = sortObjectByKey(rules);
221
+
222
+ return {
223
+ config,
224
+ report: {
225
+ source: sourcePath,
226
+ output: options.print ? null : options.output,
227
+ supportedRules: uniqueSorted(supportedRules),
228
+ unsupportedRules: uniqueSorted(unsupportedRules),
229
+ ignoredRules: uniqueByRuleId(ignoredRules),
230
+ optionDroppedRules: uniqueSorted(optionDroppedRules)
231
+ }
232
+ };
233
+ }
234
+
235
+ function findEslintConfig(cwd) {
236
+ let current = cwd;
237
+ while (true) {
238
+ for (const filename of ESLINT_CONFIG_FILENAMES) {
239
+ const candidate = resolvePath(current, filename);
240
+ if (existsSync(candidate)) {
241
+ return candidate;
242
+ }
243
+ }
244
+ const parent = dirname(current);
245
+ if (parent === current) {
246
+ return undefined;
247
+ }
248
+ current = parent;
249
+ }
250
+ }
251
+
252
+ function loadEslintConfig(path, cwd) {
253
+ if (JAVASCRIPT_CONFIG_EXTENSIONS.has(extname(path))) {
254
+ const result = spawnSync(process.execPath, ["--input-type=module", "--eval", JAVASCRIPT_CONFIG_LOADER_SCRIPT, path], {
255
+ cwd,
256
+ encoding: "utf8"
257
+ });
258
+ if (result.error) {
259
+ throw result.error;
260
+ }
261
+ if (result.status !== 0) {
262
+ throw new Error(`utoo-lint migrate eslint: unable to load ${path}: ${(result.stderr ?? "").trim() || "JavaScript config loader failed"}`);
263
+ }
264
+ return parseJson(result.stdout, path);
265
+ }
266
+ return parseJson(readFileSync(path, "utf8"), path);
267
+ }
268
+
269
+ function parseJson(source, path) {
270
+ try {
271
+ return JSON.parse(source);
272
+ } catch (error) {
273
+ throw new Error(`utoo-lint migrate eslint: unable to parse ${path}: ${error.message}`);
274
+ }
275
+ }
276
+
277
+ function normalizeConfigEntries(config) {
278
+ if (Array.isArray(config)) {
279
+ return config.flatMap(normalizeConfigEntries);
280
+ }
281
+ if (config && typeof config === "object") {
282
+ return [config];
283
+ }
284
+ return [];
285
+ }
286
+
287
+ function addPatterns(target, value) {
288
+ if (!value) {
289
+ return;
290
+ }
291
+ for (const pattern of Array.isArray(value) ? value : [value]) {
292
+ if (typeof pattern === "string") {
293
+ target.add(pattern);
294
+ }
295
+ }
296
+ }
297
+
298
+ function severityOnly(ruleConfig) {
299
+ return Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
300
+ }
301
+
302
+ function sortObjectByKey(object) {
303
+ return Object.fromEntries(Object.entries(object).sort(([left], [right]) => left.localeCompare(right)));
304
+ }
305
+
306
+ function uniqueSorted(values) {
307
+ return [...new Set(values)].sort();
308
+ }
309
+
310
+ function uniqueByRuleId(values) {
311
+ const result = new Map();
312
+ for (const value of values) {
313
+ result.set(value.ruleId, value);
314
+ }
315
+ return [...result.values()].sort((left, right) => left.ruleId.localeCompare(right.ruleId));
316
+ }
317
+
318
+ function writeReport(report, options, stream) {
319
+ if (options.report === "json") {
320
+ stream.write(JSON.stringify(report, null, 2) + "\n");
321
+ return;
322
+ }
323
+
324
+ stream.write(`utoo-lint migrate eslint: read ${pathToFileURL(report.source).href}\n`);
325
+ if (report.output) {
326
+ stream.write(`utoo-lint migrate eslint: wrote ${report.output}\n`);
327
+ }
328
+ stream.write(`utoo-lint migrate eslint: migrated ${report.supportedRules.length} supported rule(s)\n`);
329
+ if (report.optionDroppedRules.length > 0) {
330
+ stream.write(`utoo-lint migrate eslint: dropped options for ${report.optionDroppedRules.length} rule(s): ${report.optionDroppedRules.join(", ")}\n`);
331
+ }
332
+ if (report.ignoredRules.length > 0) {
333
+ stream.write(`utoo-lint migrate eslint: ignored ${report.ignoredRules.length} rule(s): ${report.ignoredRules.map((rule) => rule.ruleId).join(", ")}\n`);
334
+ }
335
+ if (report.unsupportedRules.length > 0) {
336
+ stream.write(`utoo-lint migrate eslint: ${report.unsupportedRules.length} unsupported rule(s): ${report.unsupportedRules.join(", ")}\n`);
337
+ }
338
+ }
339
+
340
+ function printMigrateHelp() {
341
+ console.log(`Usage:
342
+ utoo-lint migrate eslint [options]
343
+
344
+ Sources:
345
+ eslint Convert an ESLint config into utoo.json
346
+
347
+ Run "utoo-lint migrate eslint --help" for source-specific options.`);
348
+ }
349
+
350
+ function printEslintMigrateHelp() {
351
+ console.log(`Usage:
352
+ utoo-lint migrate eslint [options]
353
+
354
+ Options:
355
+ --from=PATH Read ESLint config from PATH
356
+ --output=PATH, -o PATH Write utoo config to PATH (default: utoo.json)
357
+ --force Overwrite the output file when it exists
358
+ --print Print the generated config instead of writing it
359
+ --report=text|json Select migration report format
360
+ --help, -h Show this help message`);
361
+ }
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+
4
+ import { resolveBinary } from "../lib/binary.js";
5
+
6
+ if (process.argv[2] === "migrate") {
7
+ const { runMigrate } = await import("./migrate.js");
8
+ process.exit(await runMigrate(process.argv.slice(3)));
9
+ }
10
+
11
+ let binary;
12
+ try {
13
+ binary = resolveBinary();
14
+ } catch (error) {
15
+ console.error(error.message);
16
+ process.exit(1);
17
+ }
18
+
19
+ const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });
20
+
21
+ if (result.error) {
22
+ console.error(`utoo-lint: failed to run native binary: ${result.error.message}`);
23
+ process.exit(1);
24
+ }
25
+
26
+ process.exit(result.status ?? 1);
package/config.cjs ADDED
@@ -0,0 +1,155 @@
1
+ let globalIgnoreCount = 0;
2
+
3
+ function defineConfig(...args) {
4
+ if (args.length === 0) {
5
+ throw new TypeError("Expected one or more arguments.");
6
+ }
7
+ return flattenConfigArgs(args).flatMap((config, index) => expandConfigExtends(config, `UserConfig[${index}]`));
8
+ }
9
+
10
+ function globalIgnores(ignorePatterns, name) {
11
+ if (!Array.isArray(ignorePatterns)) {
12
+ throw new TypeError("ignorePatterns must be an array");
13
+ }
14
+ if (ignorePatterns.length === 0) {
15
+ throw new TypeError("ignorePatterns must contain at least one pattern");
16
+ }
17
+ const id = globalIgnoreCount++;
18
+ return {
19
+ name: name || `globalIgnores ${id}`,
20
+ ignores: ignorePatterns
21
+ };
22
+ }
23
+
24
+ function flattenConfigArgs(values) {
25
+ const configs = [];
26
+ for (const value of values) {
27
+ if (Array.isArray(value)) {
28
+ configs.push(...flattenConfigArgs(value));
29
+ } else if (typeof value === "object" && value !== null) {
30
+ configs.push(value);
31
+ } else {
32
+ throw new TypeError(`Expected an object but received ${String(value)}.`);
33
+ }
34
+ }
35
+ return configs;
36
+ }
37
+
38
+ function expandConfigExtends(config, baseConfigName) {
39
+ if (!("extends" in config)) {
40
+ return [config];
41
+ }
42
+ if (!Array.isArray(config.extends)) {
43
+ throw new TypeError("The `extends` property must be an array.");
44
+ }
45
+
46
+ const { extends: extendsList, ...baseConfig } = config;
47
+ const expanded = [];
48
+ for (const [index, extension] of flattenConfigArgs(resolveConfigExtends(config, extendsList)).entries()) {
49
+ if ("basePath" in extension) {
50
+ throw new TypeError("'basePath' in `extends` is not allowed.");
51
+ }
52
+ if ("extends" in extension) {
53
+ throw new TypeError("Nested 'extends' is not allowed.");
54
+ }
55
+ expanded.push(extendConfig(baseConfig, extension, baseConfigName, extension.name ?? extension.__utooLintConfigName ?? `ExtendedConfig[${index}]`));
56
+ }
57
+ if (!isGlobalIgnores(baseConfig)) {
58
+ expanded.push(baseConfig);
59
+ }
60
+ return expanded;
61
+ }
62
+
63
+ function resolveConfigExtends(config, extendsList) {
64
+ return extendsList.map((extension) => {
65
+ if (typeof extension !== "string") {
66
+ return extension;
67
+ }
68
+ const pluginConfig = pluginConfigForName(config, extension);
69
+ if (!pluginConfig) {
70
+ const { namespace, configName } = splitPluginConfigName(extension);
71
+ throw new TypeError(`Plugin config "${configName}" not found in plugin "${namespace}".`);
72
+ }
73
+ return withConfigNames(pluginConfig, extension);
74
+ });
75
+ }
76
+
77
+ function pluginConfigForName(config, name) {
78
+ const { namespace, configName } = splitPluginConfigName(name);
79
+ const plugin = config.plugins?.[namespace];
80
+ if (!plugin) {
81
+ throw new TypeError(`Plugin "${namespace}" not found.`);
82
+ }
83
+ return plugin.configs?.[configName] ?? plugin.configs?.[`flat/${configName}`];
84
+ }
85
+
86
+ function splitPluginConfigName(name) {
87
+ const slashIndex = name.indexOf("/");
88
+ if (slashIndex <= 0 || slashIndex === name.length - 1) {
89
+ throw new TypeError(`Invalid plugin config name "${name}".`);
90
+ }
91
+ return {
92
+ namespace: name.slice(0, slashIndex),
93
+ configName: name.slice(slashIndex + 1)
94
+ };
95
+ }
96
+
97
+ function extendConfig(baseConfig, extension, baseConfigName, extensionName) {
98
+ const { __utooLintConfigName: _name, ...extensionConfig } = extension;
99
+ const result = { ...extensionConfig };
100
+ if (!isGlobalIgnores(extension)) {
101
+ if (baseConfig.files) {
102
+ result.files = extendConfigFiles(baseConfig.files, extension.files);
103
+ }
104
+ if (baseConfig.ignores) {
105
+ result.ignores = baseConfig.ignores.concat(extension.ignores ?? []);
106
+ }
107
+ }
108
+ result.name = `${baseConfig.name ?? baseConfigName} > ${extensionName}`;
109
+ if (baseConfig.basePath) {
110
+ result.basePath = baseConfig.basePath;
111
+ }
112
+ return result;
113
+ }
114
+
115
+ function extendConfigFiles(baseFiles = [], extensionFiles = []) {
116
+ if (!extensionFiles.length) {
117
+ return baseFiles.concat();
118
+ }
119
+ if (!baseFiles.length) {
120
+ return extensionFiles.concat();
121
+ }
122
+ const result = [];
123
+ for (const baseFile of baseFiles) {
124
+ for (const extensionFile of extensionFiles) {
125
+ result.push([
126
+ ...(Array.isArray(baseFile) ? baseFile : [baseFile]),
127
+ ...(Array.isArray(extensionFile) ? extensionFile : [extensionFile])
128
+ ]);
129
+ }
130
+ }
131
+ return result;
132
+ }
133
+
134
+ function isGlobalIgnores(config) {
135
+ const keys = Object.keys(config);
136
+ return keys.includes("ignores") && keys.every((key) => key === "name" || key === "ignores");
137
+ }
138
+
139
+ function withConfigNames(config, name) {
140
+ if (Array.isArray(config)) {
141
+ return config.map((entry, index) => ({
142
+ ...entry,
143
+ __utooLintConfigName: `${name}[${index}]`
144
+ }));
145
+ }
146
+ return {
147
+ ...config,
148
+ __utooLintConfigName: name
149
+ };
150
+ }
151
+
152
+ module.exports = {
153
+ defineConfig,
154
+ globalIgnores
155
+ };
package/config.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { ConfigObject } from "./index.js";
2
+
3
+ export function defineConfig(...configs: Array<ConfigObject | ConfigObject[]>): ConfigObject[];
4
+ export function globalIgnores(ignorePatterns: string[], name?: string): ConfigObject;
package/config.js ADDED
@@ -0,0 +1,150 @@
1
+ let globalIgnoreCount = 0;
2
+
3
+ export function defineConfig(...args) {
4
+ if (args.length === 0) {
5
+ throw new TypeError("Expected one or more arguments.");
6
+ }
7
+ return flattenConfigArgs(args).flatMap((config, index) => expandConfigExtends(config, `UserConfig[${index}]`));
8
+ }
9
+
10
+ export function globalIgnores(ignorePatterns, name) {
11
+ if (!Array.isArray(ignorePatterns)) {
12
+ throw new TypeError("ignorePatterns must be an array");
13
+ }
14
+ if (ignorePatterns.length === 0) {
15
+ throw new TypeError("ignorePatterns must contain at least one pattern");
16
+ }
17
+ const id = globalIgnoreCount++;
18
+ return {
19
+ name: name || `globalIgnores ${id}`,
20
+ ignores: ignorePatterns
21
+ };
22
+ }
23
+
24
+ function flattenConfigArgs(values) {
25
+ const configs = [];
26
+ for (const value of values) {
27
+ if (Array.isArray(value)) {
28
+ configs.push(...flattenConfigArgs(value));
29
+ } else if (typeof value === "object" && value !== null) {
30
+ configs.push(value);
31
+ } else {
32
+ throw new TypeError(`Expected an object but received ${String(value)}.`);
33
+ }
34
+ }
35
+ return configs;
36
+ }
37
+
38
+ function expandConfigExtends(config, baseConfigName) {
39
+ if (!("extends" in config)) {
40
+ return [config];
41
+ }
42
+ if (!Array.isArray(config.extends)) {
43
+ throw new TypeError("The `extends` property must be an array.");
44
+ }
45
+
46
+ const { extends: extendsList, ...baseConfig } = config;
47
+ const expanded = [];
48
+ for (const [index, extension] of flattenConfigArgs(resolveConfigExtends(config, extendsList)).entries()) {
49
+ if ("basePath" in extension) {
50
+ throw new TypeError("'basePath' in `extends` is not allowed.");
51
+ }
52
+ if ("extends" in extension) {
53
+ throw new TypeError("Nested 'extends' is not allowed.");
54
+ }
55
+ expanded.push(extendConfig(baseConfig, extension, baseConfigName, extension.name ?? extension.__utooLintConfigName ?? `ExtendedConfig[${index}]`));
56
+ }
57
+ if (!isGlobalIgnores(baseConfig)) {
58
+ expanded.push(baseConfig);
59
+ }
60
+ return expanded;
61
+ }
62
+
63
+ function resolveConfigExtends(config, extendsList) {
64
+ return extendsList.map((extension) => {
65
+ if (typeof extension !== "string") {
66
+ return extension;
67
+ }
68
+ const pluginConfig = pluginConfigForName(config, extension);
69
+ if (!pluginConfig) {
70
+ const { namespace, configName } = splitPluginConfigName(extension);
71
+ throw new TypeError(`Plugin config "${configName}" not found in plugin "${namespace}".`);
72
+ }
73
+ return withConfigNames(pluginConfig, extension);
74
+ });
75
+ }
76
+
77
+ function pluginConfigForName(config, name) {
78
+ const { namespace, configName } = splitPluginConfigName(name);
79
+ const plugin = config.plugins?.[namespace];
80
+ if (!plugin) {
81
+ throw new TypeError(`Plugin "${namespace}" not found.`);
82
+ }
83
+ return plugin.configs?.[configName] ?? plugin.configs?.[`flat/${configName}`];
84
+ }
85
+
86
+ function splitPluginConfigName(name) {
87
+ const slashIndex = name.indexOf("/");
88
+ if (slashIndex <= 0 || slashIndex === name.length - 1) {
89
+ throw new TypeError(`Invalid plugin config name "${name}".`);
90
+ }
91
+ return {
92
+ namespace: name.slice(0, slashIndex),
93
+ configName: name.slice(slashIndex + 1)
94
+ };
95
+ }
96
+
97
+ function extendConfig(baseConfig, extension, baseConfigName, extensionName) {
98
+ const { __utooLintConfigName: _name, ...extensionConfig } = extension;
99
+ const result = { ...extensionConfig };
100
+ if (!isGlobalIgnores(extension)) {
101
+ if (baseConfig.files) {
102
+ result.files = extendConfigFiles(baseConfig.files, extension.files);
103
+ }
104
+ if (baseConfig.ignores) {
105
+ result.ignores = baseConfig.ignores.concat(extension.ignores ?? []);
106
+ }
107
+ }
108
+ result.name = `${baseConfig.name ?? baseConfigName} > ${extensionName}`;
109
+ if (baseConfig.basePath) {
110
+ result.basePath = baseConfig.basePath;
111
+ }
112
+ return result;
113
+ }
114
+
115
+ function extendConfigFiles(baseFiles = [], extensionFiles = []) {
116
+ if (!extensionFiles.length) {
117
+ return baseFiles.concat();
118
+ }
119
+ if (!baseFiles.length) {
120
+ return extensionFiles.concat();
121
+ }
122
+ const result = [];
123
+ for (const baseFile of baseFiles) {
124
+ for (const extensionFile of extensionFiles) {
125
+ result.push([
126
+ ...(Array.isArray(baseFile) ? baseFile : [baseFile]),
127
+ ...(Array.isArray(extensionFile) ? extensionFile : [extensionFile])
128
+ ]);
129
+ }
130
+ }
131
+ return result;
132
+ }
133
+
134
+ function isGlobalIgnores(config) {
135
+ const keys = Object.keys(config);
136
+ return keys.includes("ignores") && keys.every((key) => key === "name" || key === "ignores");
137
+ }
138
+
139
+ function withConfigNames(config, name) {
140
+ if (Array.isArray(config)) {
141
+ return config.map((entry, index) => ({
142
+ ...entry,
143
+ __utooLintConfigName: `${name}[${index}]`
144
+ }));
145
+ }
146
+ return {
147
+ ...config,
148
+ __utooLintConfigName: name
149
+ };
150
+ }