@isentinel/eslint-config 6.1.0 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -0
- package/dist/cli.mjs +231 -30
- package/dist/index.d.mts +55 -34
- package/dist/index.mjs +873 -209
- package/dist/lint-cli.mjs +234 -114
- package/dist/oxfmt.d.mts +132 -0
- package/dist/oxfmt.mjs +750 -0
- package/dist/oxlint.d.mts +14 -0
- package/dist/oxlint.mjs +417 -63
- package/package.json +8 -3
package/dist/oxlint.d.mts
CHANGED
|
@@ -25140,6 +25140,20 @@ interface OptionsConfig extends OptionsComponentExtensions, OptionsProjectType {
|
|
|
25140
25140
|
* @requires eslint-plugin-eslint-plugin
|
|
25141
25141
|
*/
|
|
25142
25142
|
eslintPlugin?: boolean | OptionsOverrides;
|
|
25143
|
+
/**
|
|
25144
|
+
* Where formatting runs.
|
|
25145
|
+
*
|
|
25146
|
+
* - `"eslint"`: oxfmt runs inside the linter as the `oxfmt/oxfmt` rule.
|
|
25147
|
+
* - `"standalone"`: oxfmt runs as its own tool (`oxfmt`, with an
|
|
25148
|
+
* `oxfmt.config.ts` built by `isentinelOxfmt` from
|
|
25149
|
+
* `@isentinel/eslint-config/oxfmt`). The linter keeps the
|
|
25150
|
+
* formatter-conflict disables and still reads the oxfmt config so
|
|
25151
|
+
* width-aware rules agree with it, but enables `oxfmt/oxfmt` nowhere.
|
|
25152
|
+
* Lua formatting (Stylua) stays in ESLint, as oxfmt does not format Lua.
|
|
25153
|
+
*
|
|
25154
|
+
* @default "eslint"
|
|
25155
|
+
*/
|
|
25156
|
+
formatter?: "eslint" | "standalone";
|
|
25143
25157
|
/**
|
|
25144
25158
|
* Use external formatters to format files.
|
|
25145
25159
|
*
|
package/dist/oxlint.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import process from "node:process";
|
|
8
8
|
import { getStaticYAMLValue, parseYAML } from "yaml-eslint-parser";
|
|
9
9
|
import { findUpSync } from "find-up-simple";
|
|
10
|
+
import { isMainThread } from "node:worker_threads";
|
|
10
11
|
import { minVersion } from "semver";
|
|
11
12
|
import { pathToFileURL } from "node:url";
|
|
12
13
|
import globals from "globals";
|
|
@@ -5236,10 +5237,19 @@ const GLOB_SRC_EXTENSIONS = [
|
|
|
5236
5237
|
"cjs"
|
|
5237
5238
|
];
|
|
5238
5239
|
[...GLOB_SRC_EXTENSIONS];
|
|
5240
|
+
const GLOB_JS = "**/*.{,c,m}js";
|
|
5239
5241
|
const GLOB_JSX = "**/*.{,c,m}jsx";
|
|
5242
|
+
const GLOB_TS = "**/*.{,c,m}ts";
|
|
5240
5243
|
const GLOB_TSX = "**/*.{,c,m}tsx";
|
|
5241
5244
|
const GLOB_DTS = "**/*.d.{,c,m}ts";
|
|
5242
5245
|
const GLOB_BIN = ["**/bin/**/*", `**/bin.${GLOB_SRC_EXT}`];
|
|
5246
|
+
const GLOB_CSS = "**/*.css";
|
|
5247
|
+
const GLOB_POSTCSS = "**/*.{p,post}css";
|
|
5248
|
+
const GLOB_LESS = "**/*.less";
|
|
5249
|
+
const GLOB_SCSS = "**/*.scss";
|
|
5250
|
+
const GLOB_ALL_JSON = "**/*.json{,5,c}";
|
|
5251
|
+
const GLOB_MARKDOWN = "**/*.md";
|
|
5252
|
+
const GLOB_YAML = "**/*.y{,a}ml";
|
|
5243
5253
|
/**
|
|
5244
5254
|
* Type tests (`expectTypeOf` / `assertType`), run by Vitest's `typecheck`
|
|
5245
5255
|
* mode. They are test files, but assert nothing at runtime.
|
|
@@ -5323,6 +5333,15 @@ const GLOB_EXCLUDE = [
|
|
|
5323
5333
|
function isRecord$1(value) {
|
|
5324
5334
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5325
5335
|
}
|
|
5336
|
+
/**
|
|
5337
|
+
* Whether a value is an array whose every element is a string.
|
|
5338
|
+
*
|
|
5339
|
+
* @param value - The value to test.
|
|
5340
|
+
* @returns Whether the value is a `string` array.
|
|
5341
|
+
*/
|
|
5342
|
+
function isStringArray(value) {
|
|
5343
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
5344
|
+
}
|
|
5326
5345
|
//#endregion
|
|
5327
5346
|
//#region src/prettier-config.ts
|
|
5328
5347
|
/**
|
|
@@ -5414,6 +5433,79 @@ function resolvePrettierSettings(prettierOptions = {}, cwd = process.cwd()) {
|
|
|
5414
5433
|
...prettierOptions
|
|
5415
5434
|
};
|
|
5416
5435
|
}
|
|
5436
|
+
/**
|
|
5437
|
+
* Replace `printWidth` and `tabWidth` in the formatting settings with the
|
|
5438
|
+
* values oxfmt really uses.
|
|
5439
|
+
*
|
|
5440
|
+
* `flawless/arrow-return-style` hands `printWidth` to oxfmt to predict its
|
|
5441
|
+
* output (`useOxfmt.printWidth`), so it must match the width oxfmt formats
|
|
5442
|
+
* with, or `--fix` ping-pongs. An oxfmt config can set a width that differs
|
|
5443
|
+
* from the Prettier-shaped settings, so the oxfmt value wins.
|
|
5444
|
+
*
|
|
5445
|
+
* @template T - The settings shape.
|
|
5446
|
+
* @param settings - The resolved formatting settings.
|
|
5447
|
+
* @param oxfmtOptions - The effective oxfmt options.
|
|
5448
|
+
* @returns The settings with oxfmt's widths.
|
|
5449
|
+
*/
|
|
5450
|
+
function withOxfmtWidths(settings, { printWidth, tabWidth }) {
|
|
5451
|
+
return {
|
|
5452
|
+
...settings,
|
|
5453
|
+
...typeof printWidth === "number" ? { printWidth } : {},
|
|
5454
|
+
...typeof tabWidth === "number" ? { tabWidth } : {}
|
|
5455
|
+
};
|
|
5456
|
+
}
|
|
5457
|
+
/**
|
|
5458
|
+
* The indent oxfmt writes.
|
|
5459
|
+
*
|
|
5460
|
+
* @param oxfmtOptions - The effective oxfmt options.
|
|
5461
|
+
* @returns `"tab"`, or the number of spaces.
|
|
5462
|
+
*/
|
|
5463
|
+
function oxfmtIndent({ tabWidth, useTabs }) {
|
|
5464
|
+
if (useTabs !== false) return "tab";
|
|
5465
|
+
return typeof tabWidth === "number" ? tabWidth : 2;
|
|
5466
|
+
}
|
|
5467
|
+
/**
|
|
5468
|
+
* Default the stylistic `indent` and `quotes` to what oxfmt writes, so the
|
|
5469
|
+
* stylistic rules and oxfmt agree. Explicit `stylistic` values win.
|
|
5470
|
+
*
|
|
5471
|
+
* @param stylistic - The user's stylistic options.
|
|
5472
|
+
* @param oxfmtOptions - The effective oxfmt options.
|
|
5473
|
+
* @returns The stylistic options with oxfmt's indent and quotes.
|
|
5474
|
+
*/
|
|
5475
|
+
function withOxfmtStyle(stylistic, oxfmtOptions) {
|
|
5476
|
+
return {
|
|
5477
|
+
indent: oxfmtIndent(oxfmtOptions),
|
|
5478
|
+
quotes: oxfmtOptions["singleQuote"] === true ? "single" : "double",
|
|
5479
|
+
...stylistic
|
|
5480
|
+
};
|
|
5481
|
+
}
|
|
5482
|
+
/**
|
|
5483
|
+
* Turn the nearest `.editorconfig`'s sections into oxfmt overrides, the way
|
|
5484
|
+
* the `oxfmt/oxfmt` rule's config loader does: `[*]` is skipped (it feeds the
|
|
5485
|
+
* root options), and each other section becomes an override whose globs are
|
|
5486
|
+
* relative to `cwd`. A key set in the oxfmt config root wins over the
|
|
5487
|
+
* sections.
|
|
5488
|
+
*
|
|
5489
|
+
* @param cwd - The config directory.
|
|
5490
|
+
* @param root - The oxfmt config root options.
|
|
5491
|
+
* @returns The overrides, in file order.
|
|
5492
|
+
*/
|
|
5493
|
+
function editorConfigOverrides(cwd = process.cwd(), root = {}) {
|
|
5494
|
+
for (const directory of ancestorDirectories(cwd)) {
|
|
5495
|
+
const contents = readFileOrUndefined(path.join(directory, ".editorconfig"));
|
|
5496
|
+
if (contents === void 0) continue;
|
|
5497
|
+
const prefix = path.relative(cwd, directory).split(path.sep).join("/");
|
|
5498
|
+
return parseEditorConfig(contents).sections.flatMap(({ pattern, properties }) => {
|
|
5499
|
+
const options = Object.fromEntries(Object.entries(editorConfigToOxfmt(properties)).filter(([key]) => !Object.hasOwn(root, key)));
|
|
5500
|
+
if (pattern === "*" || Object.keys(options).length === 0) return [];
|
|
5501
|
+
return [{
|
|
5502
|
+
files: [prefix === "" ? pattern : `${prefix}/${pattern.replace(LEADING_SLASH, "")}`],
|
|
5503
|
+
options
|
|
5504
|
+
}];
|
|
5505
|
+
});
|
|
5506
|
+
}
|
|
5507
|
+
return [];
|
|
5508
|
+
}
|
|
5417
5509
|
function readFileOrUndefined(filePath) {
|
|
5418
5510
|
try {
|
|
5419
5511
|
return fs.readFileSync(filePath, "utf-8");
|
|
@@ -5593,6 +5685,31 @@ function resolveEditorConfigOptions(cwd) {
|
|
|
5593
5685
|
}
|
|
5594
5686
|
return editorConfigToPrettier(properties);
|
|
5595
5687
|
}
|
|
5688
|
+
/**
|
|
5689
|
+
* Map one `.editorconfig` section to oxfmt options, as the rule's config
|
|
5690
|
+
* loader does.
|
|
5691
|
+
*
|
|
5692
|
+
* @param properties - The section's properties.
|
|
5693
|
+
* @returns The oxfmt options it sets.
|
|
5694
|
+
*/
|
|
5695
|
+
function editorConfigToOxfmt(properties) {
|
|
5696
|
+
const options = {};
|
|
5697
|
+
const endOfLine = properties["end_of_line"];
|
|
5698
|
+
if (endOfLine === "cr" || endOfLine === "crlf" || endOfLine === "lf") options["endOfLine"] = endOfLine;
|
|
5699
|
+
const indentStyle = properties["indent_style"];
|
|
5700
|
+
if (indentStyle === "tab" || indentStyle === "space") options["useTabs"] = indentStyle === "tab";
|
|
5701
|
+
const quoteType = properties["quote_type"];
|
|
5702
|
+
if (quoteType === "single" || quoteType === "double") options["singleQuote"] = quoteType === "single";
|
|
5703
|
+
const indentSize = Number(properties["indent_size"]);
|
|
5704
|
+
const tabWidth = Number(properties["tab_width"]);
|
|
5705
|
+
if (options["useTabs"] === false && properties["indent_size"] && Number.isFinite(indentSize)) options["tabWidth"] = indentSize;
|
|
5706
|
+
else if (properties["tab_width"] && Number.isFinite(tabWidth)) options["tabWidth"] = tabWidth;
|
|
5707
|
+
const printWidth = Number(properties["max_line_length"]);
|
|
5708
|
+
if (properties["max_line_length"] && Number.isFinite(printWidth)) options["printWidth"] = printWidth;
|
|
5709
|
+
const finalNewline = properties["insert_final_newline"];
|
|
5710
|
+
if (finalNewline === "true" || finalNewline === "false") options["insertFinalNewline"] = finalNewline === "true";
|
|
5711
|
+
return options;
|
|
5712
|
+
}
|
|
5596
5713
|
//#endregion
|
|
5597
5714
|
//#region src/rules/oxfmt.ts
|
|
5598
5715
|
const UNSUPPORTED_PRETTIER_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -5646,11 +5763,30 @@ function migratePrettierOptions(prettierOptions) {
|
|
|
5646
5763
|
for (const [key, value] of Object.entries(prettierOptions)) {
|
|
5647
5764
|
if (UNSUPPORTED_PRETTIER_KEYS.has(key)) continue;
|
|
5648
5765
|
if (key === "endOfLine" && value === "auto") continue;
|
|
5649
|
-
oxfmtOptions[key] = value;
|
|
5766
|
+
oxfmtOptions[key] = key === "overrides" ? migratePrettierOverrides(value) : value;
|
|
5650
5767
|
}
|
|
5651
5768
|
return oxfmtOptions;
|
|
5652
5769
|
}
|
|
5653
5770
|
/**
|
|
5771
|
+
* Migrate Prettier `overrides`: oxfmt wants `files` as an array, and the
|
|
5772
|
+
* nested options migrated like the root ones.
|
|
5773
|
+
*
|
|
5774
|
+
* @param overrides - The `overrides` value, of unknown shape.
|
|
5775
|
+
* @returns The overrides in oxfmt's shape; empty when not an array.
|
|
5776
|
+
*/
|
|
5777
|
+
function migratePrettierOverrides(overrides) {
|
|
5778
|
+
if (!Array.isArray(overrides)) return [];
|
|
5779
|
+
return overrides.flatMap((override) => {
|
|
5780
|
+
if (!isRecord$1(override)) return [];
|
|
5781
|
+
const { excludeFiles, files, options } = override;
|
|
5782
|
+
return [{
|
|
5783
|
+
...excludeFiles === void 0 ? {} : { excludeFiles: stringsOf(excludeFiles) },
|
|
5784
|
+
files: stringsOf(files),
|
|
5785
|
+
options: isRecord$1(options) ? migratePrettierOptions(options) : {}
|
|
5786
|
+
}];
|
|
5787
|
+
});
|
|
5788
|
+
}
|
|
5789
|
+
/**
|
|
5654
5790
|
* Build the effective oxfmt options shared between the ESLint and oxlint
|
|
5655
5791
|
* factories.
|
|
5656
5792
|
*
|
|
@@ -5666,6 +5802,9 @@ function buildOxfmtOptions({ oxfmtConfigOptions = {}, oxfmtOptions, prettierOpti
|
|
|
5666
5802
|
...oxfmtOptions
|
|
5667
5803
|
};
|
|
5668
5804
|
}
|
|
5805
|
+
function stringsOf(value) {
|
|
5806
|
+
return [value].flat().filter((item) => typeof item === "string");
|
|
5807
|
+
}
|
|
5669
5808
|
//#endregion
|
|
5670
5809
|
//#region src/rules/react.ts
|
|
5671
5810
|
const DOM_IMPORT_MESSAGE = "Import from react-testing-library-lua instead; it re-exports the DOM utilities, and eslint-plugin-testing-library only detects one module per file.";
|
|
@@ -5878,7 +6017,9 @@ function isDomImportRestriction(value, domPackage) {
|
|
|
5878
6017
|
function isObjectStyleRestriction(value) {
|
|
5879
6018
|
return isRecord$1(value) && !("name" in value);
|
|
5880
6019
|
}
|
|
5881
|
-
|
|
6020
|
+
//#endregion
|
|
6021
|
+
//#region src/utils.ts
|
|
6022
|
+
const require$2 = createRequire(import.meta.url);
|
|
5882
6023
|
/**
|
|
5883
6024
|
* Synchronous {@link findWorkspaceRoot}, for the oxlint factory.
|
|
5884
6025
|
*
|
|
@@ -6108,27 +6249,133 @@ function environmentMatcher(name, pattern, { noTty = false } = {}) {
|
|
|
6108
6249
|
return value !== void 0 && pattern.test(value);
|
|
6109
6250
|
};
|
|
6110
6251
|
}
|
|
6111
|
-
|
|
6252
|
+
/** JSON oxfmt config files oxfmt discovers. */
|
|
6253
|
+
const OXFMT_RC_FILES = [".oxfmtrc.json", ".oxfmtrc.jsonc"];
|
|
6254
|
+
/**
|
|
6255
|
+
* Oxfmt config modules oxfmt discovers. It loads other module extensions only
|
|
6256
|
+
* through `--config`, never by search.
|
|
6257
|
+
*/
|
|
6258
|
+
const OXFMT_CONFIG_MODULES = ["oxfmt.config.ts", "oxfmt.config.mts"];
|
|
6259
|
+
OXFMT_CONFIG_MODULES[0];
|
|
6260
|
+
/** Every file name oxfmt discovers. One directory may hold only one of them. */
|
|
6261
|
+
const OXFMT_CONFIG_NAMES = [...OXFMT_RC_FILES, ...OXFMT_CONFIG_MODULES];
|
|
6262
|
+
/** The extensions of the JS/TS files ESLint and oxlint format. */
|
|
6263
|
+
const SCRIPT_EXTENSIONS = [
|
|
6264
|
+
"cjs",
|
|
6265
|
+
"cts",
|
|
6266
|
+
"js",
|
|
6267
|
+
"jsx",
|
|
6268
|
+
"mjs",
|
|
6269
|
+
"mts",
|
|
6270
|
+
"ts",
|
|
6271
|
+
"tsx"
|
|
6272
|
+
];
|
|
6273
|
+
const LEADING_DOT_SLASH = /^\.\//;
|
|
6274
|
+
/** Set once a process has warned about a JS/TS width override. */
|
|
6275
|
+
const WARNED_SCRIPT_OVERRIDE = Symbol.for("@isentinel/eslint-config/oxfmt-override-warned");
|
|
6276
|
+
/** Set once a process has warned about options standalone oxfmt ignores. */
|
|
6277
|
+
const WARNED_STANDALONE = Symbol.for("@isentinel/eslint-config/standalone-options-warned");
|
|
6278
|
+
const GLOB_MAGIC = /[*?[\]{}]/;
|
|
6279
|
+
/**
|
|
6280
|
+
* List the oxfmt config files in one directory, without walking up.
|
|
6281
|
+
*
|
|
6282
|
+
* @param directory - Directory to check.
|
|
6283
|
+
* @returns The file names found, sorted.
|
|
6284
|
+
*/
|
|
6285
|
+
function oxfmtConfigNamesIn(directory) {
|
|
6286
|
+
return OXFMT_CONFIG_NAMES.filter((name) => fs.existsSync(path.join(directory, name))).sort();
|
|
6287
|
+
}
|
|
6288
|
+
/**
|
|
6289
|
+
* Find the oxfmt config the way oxfmt does: walk up from `cwd` to the
|
|
6290
|
+
* filesystem root and take the first directory that holds one. Like oxfmt,
|
|
6291
|
+
* refuse a directory that holds more than one.
|
|
6292
|
+
*
|
|
6293
|
+
* @param cwd - Directory to start from.
|
|
6294
|
+
* @returns The config file, if any.
|
|
6295
|
+
* @throws When one directory holds more than one oxfmt config file.
|
|
6296
|
+
*/
|
|
6297
|
+
function findOxfmtConfig(cwd = process.cwd()) {
|
|
6298
|
+
let directory = path.resolve(cwd);
|
|
6299
|
+
for (;;) {
|
|
6300
|
+
const [first, second] = oxfmtConfigNamesIn(directory);
|
|
6301
|
+
if (second !== void 0) throw new Error(`[@isentinel/eslint-config] Both '${first}' and '${second}' found in ${directory}. Keep one oxfmt config file.`);
|
|
6302
|
+
if (first !== void 0) return {
|
|
6303
|
+
isModule: OXFMT_CONFIG_MODULES.some((name) => name === first),
|
|
6304
|
+
path: path.join(directory, first)
|
|
6305
|
+
};
|
|
6306
|
+
const parent = path.dirname(directory);
|
|
6307
|
+
if (parent === directory) return;
|
|
6308
|
+
directory = parent;
|
|
6309
|
+
}
|
|
6310
|
+
}
|
|
6112
6311
|
/**
|
|
6113
|
-
*
|
|
6114
|
-
*
|
|
6312
|
+
* Warn, at most once per process, about factory options that do not reach
|
|
6313
|
+
* standalone oxfmt: it formats with `oxfmt.config.ts` alone. They still choose
|
|
6314
|
+
* which Markdown code blocks ESLint formats.
|
|
6315
|
+
*
|
|
6316
|
+
* @param options - The factory options.
|
|
6317
|
+
* @param options.componentExts - Oxfmt formats only the extensions
|
|
6318
|
+
* `isentinelOxfmt` is given.
|
|
6319
|
+
* @param options.formatters - Any value but `true` is ignored by oxfmt.
|
|
6320
|
+
* @param options.gitignore - Custom ignore files, or `false`, do not reach
|
|
6321
|
+
* oxfmt.
|
|
6322
|
+
* @param options.ignores - User ignores do not reach oxfmt.
|
|
6323
|
+
*/
|
|
6324
|
+
function warnStandaloneOptions({ componentExts, formatters, gitignore, ignores }) {
|
|
6325
|
+
const state = globalThis;
|
|
6326
|
+
if (!isMainThread || state[WARNED_STANDALONE] === true || process.env["ISENTINEL_QUIET_CONFIG_WARNINGS"] === "1") return;
|
|
6327
|
+
const names = [
|
|
6328
|
+
formatters !== void 0 && formatters !== true ? "formatters" : void 0,
|
|
6329
|
+
Array.isArray(componentExts) && componentExts.length > 0 ? "componentExts" : void 0,
|
|
6330
|
+
ignores === void 0 || Array.isArray(ignores) && ignores.length === 0 ? void 0 : "ignores",
|
|
6331
|
+
gitignore === false || isRecord$1(gitignore) ? "gitignore" : void 0
|
|
6332
|
+
].filter((name) => name !== void 0);
|
|
6333
|
+
if (names.length > 0) {
|
|
6334
|
+
state[WARNED_STANDALONE] = true;
|
|
6335
|
+
console.warn(`[@isentinel/eslint-config] With formatter: "standalone", oxfmt formats with oxfmt.config.ts alone, so these options do not change what oxfmt formats: ${names.join(", ")}. They change only which Markdown code blocks ESLint formats. Set the equivalent in isentinelOxfmt() (formatters, componentExts, ignorePatterns, format options).`);
|
|
6336
|
+
}
|
|
6337
|
+
}
|
|
6338
|
+
/**
|
|
6339
|
+
* Warn, at most once per process, when an oxfmt override changes `printWidth`
|
|
6340
|
+
* or `tabWidth` for JS/TS files: ESLint and oxlint use the root values.
|
|
6341
|
+
*
|
|
6342
|
+
* @param overrides - The config's `overrides`.
|
|
6343
|
+
* @param source - The config file name.
|
|
6344
|
+
*/
|
|
6345
|
+
function warnScriptWidthOverrides(overrides, source) {
|
|
6346
|
+
const state = globalThis;
|
|
6347
|
+
if (state[WARNED_SCRIPT_OVERRIDE] === true || !Array.isArray(overrides)) return;
|
|
6348
|
+
if (overrides.some((override) => {
|
|
6349
|
+
if (!isRecord$1(override) || !isRecord$1(override["options"])) return false;
|
|
6350
|
+
const { printWidth, tabWidth } = override["options"];
|
|
6351
|
+
if (printWidth === void 0 && tabWidth === void 0) return false;
|
|
6352
|
+
return overrideMatches(override, SCRIPT_EXTENSIONS);
|
|
6353
|
+
})) {
|
|
6354
|
+
state[WARNED_SCRIPT_OVERRIDE] = true;
|
|
6355
|
+
console.warn(`[@isentinel/eslint-config] ${source} has an override that changes printWidth or tabWidth for JS/TS files. ESLint and oxlint use the root values for those files.`);
|
|
6356
|
+
}
|
|
6357
|
+
}
|
|
6358
|
+
/**
|
|
6359
|
+
* Synchronous {@link resolveOxfmtConfig}, for the (synchronous) oxlint factory.
|
|
6360
|
+
* Loads a config module through jiti's synchronous API. Oxlint does not lint
|
|
6361
|
+
* Markdown code blocks, so it returns the format options alone.
|
|
6115
6362
|
*
|
|
6116
|
-
* @returns The oxfmt
|
|
6363
|
+
* @returns The oxfmt format options, or an empty object if none found.
|
|
6364
|
+
* @throws When the config is ambiguous or cannot be loaded.
|
|
6117
6365
|
*/
|
|
6118
6366
|
function resolveOxfmtConfigOptionsSync() {
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
}
|
|
6367
|
+
const config = findOxfmtConfig();
|
|
6368
|
+
if (config === void 0) return {};
|
|
6369
|
+
if (!config.isModule) return rcConfig(config.path, fs.readFileSync(config.path, "utf-8")).options;
|
|
6370
|
+
let loaded;
|
|
6371
|
+
try {
|
|
6372
|
+
const { createJiti } = require$2("jiti");
|
|
6373
|
+
const module = createJiti(import.meta.url, { moduleCache: false })(config.path);
|
|
6374
|
+
loaded = isRecord$1(module) && "default" in module ? module["default"] : module;
|
|
6375
|
+
} catch (err) {
|
|
6376
|
+
throw loadError(config.path, err);
|
|
6130
6377
|
}
|
|
6131
|
-
return
|
|
6378
|
+
return moduleConfig(config.path, loaded).options;
|
|
6132
6379
|
}
|
|
6133
6380
|
/**
|
|
6134
6381
|
* Override the severity of all rules in a rules object, preserving rule
|
|
@@ -6152,6 +6399,91 @@ function overrideRuleSeverity(rules, severity, excludeRules = /* @__PURE__ */ ne
|
|
|
6152
6399
|
}));
|
|
6153
6400
|
}
|
|
6154
6401
|
/**
|
|
6402
|
+
* Whether a glob can match a file with one of `extensions`, judged by the
|
|
6403
|
+
* extension of its last segment (`*.ts`, `*.{ts,tsx}`, `*.?ts`, `*`, ...).
|
|
6404
|
+
*
|
|
6405
|
+
* @param glob - An override `files` glob.
|
|
6406
|
+
* @param extensions - The extensions to look for.
|
|
6407
|
+
* @returns Whether it can match such a file.
|
|
6408
|
+
*/
|
|
6409
|
+
function globMatchesExtension(glob, extensions) {
|
|
6410
|
+
const base = glob.split("/").at(-1) ?? glob;
|
|
6411
|
+
const dot = base.lastIndexOf(".");
|
|
6412
|
+
if (dot === -1) return base.includes("*");
|
|
6413
|
+
const extension = base.slice(dot + 1);
|
|
6414
|
+
if (!GLOB_MAGIC.test(extension)) return extensions.includes(extension);
|
|
6415
|
+
return extensions.some((candidate) => path.matchesGlob(candidate, extension));
|
|
6416
|
+
}
|
|
6417
|
+
/**
|
|
6418
|
+
* Whether an oxfmt override can apply to a file with one of `extensions`.
|
|
6419
|
+
*
|
|
6420
|
+
* @param override - One entry of the config's `overrides`.
|
|
6421
|
+
* @param extensions - The extensions to look for.
|
|
6422
|
+
* @returns Whether its `files` can match such a file.
|
|
6423
|
+
*/
|
|
6424
|
+
function overrideMatches(override, extensions) {
|
|
6425
|
+
return (Array.isArray(override["files"]) ? override["files"] : [override["files"]]).some((glob) => typeof glob === "string" && globMatchesExtension(glob, extensions));
|
|
6426
|
+
}
|
|
6427
|
+
function loadError(configPath, error) {
|
|
6428
|
+
return new Error(`[@isentinel/eslint-config] Failed to load ${path.basename(configPath)}: ${String(error)}`, { cause: error });
|
|
6429
|
+
}
|
|
6430
|
+
/**
|
|
6431
|
+
* Whether a config override is an `.editorconfig` section that
|
|
6432
|
+
* `isentinelOxfmt` copied in: the same globs (anchored or not) and a subset
|
|
6433
|
+
* of its options.
|
|
6434
|
+
*
|
|
6435
|
+
* @param override - One config override.
|
|
6436
|
+
* @param sections - The `.editorconfig` sections, as overrides.
|
|
6437
|
+
* @returns Whether it mirrors one of them.
|
|
6438
|
+
*/
|
|
6439
|
+
function mirrorsSection(override, sections) {
|
|
6440
|
+
if (!isRecord$1(override) || !isRecord$1(override["options"])) return false;
|
|
6441
|
+
const { options } = override;
|
|
6442
|
+
const files = [override["files"]].flat().map((glob) => typeof glob === "string" ? glob.replace(LEADING_DOT_SLASH, "") : glob);
|
|
6443
|
+
return sections.some((section) => {
|
|
6444
|
+
return section.files.length === files.length && section.files.every((glob, index) => glob === files[index]) && Object.entries(options).every(([key, value]) => section.options[key] === value);
|
|
6445
|
+
});
|
|
6446
|
+
}
|
|
6447
|
+
/**
|
|
6448
|
+
* Turn a loaded oxfmt config into `oxfmt/oxfmt` rule options, its ignore
|
|
6449
|
+
* patterns and its overrides. The rule takes format options only, so
|
|
6450
|
+
* `$schema`, `ignorePatterns` and `overrides` are dropped from the options.
|
|
6451
|
+
*
|
|
6452
|
+
* @param config - The loaded config.
|
|
6453
|
+
* @param config.$schema - Dropped.
|
|
6454
|
+
* @param config.ignorePatterns - Dropped from the options; oxfmt applies them
|
|
6455
|
+
* itself, except in Markdown code blocks.
|
|
6456
|
+
* @param config.overrides - Dropped from the options, after the width check.
|
|
6457
|
+
* @param configPath - The config file, for the warning.
|
|
6458
|
+
* @returns The format options, ignore patterns and overrides.
|
|
6459
|
+
*/
|
|
6460
|
+
function toResolvedConfig({ $schema: _schema, ignorePatterns, overrides, ...options }, configPath) {
|
|
6461
|
+
const sections = editorConfigOverrides(path.dirname(configPath));
|
|
6462
|
+
const list = Array.isArray(overrides) ? overrides : [];
|
|
6463
|
+
const copied = list.filter((override) => mirrorsSection(override, sections));
|
|
6464
|
+
warnScriptWidthOverrides(copied, ".editorconfig");
|
|
6465
|
+
warnScriptWidthOverrides(list.filter((override) => !copied.includes(override)), path.basename(configPath));
|
|
6466
|
+
return {
|
|
6467
|
+
ignorePatterns: isStringArray(ignorePatterns) ? ignorePatterns : [],
|
|
6468
|
+
options,
|
|
6469
|
+
overrides: Array.isArray(overrides) ? overrides : []
|
|
6470
|
+
};
|
|
6471
|
+
}
|
|
6472
|
+
function moduleConfig(configPath, loaded) {
|
|
6473
|
+
if (!isRecord$1(loaded)) throw loadError(configPath, /* @__PURE__ */ new TypeError("the default export is not an object"));
|
|
6474
|
+
return toResolvedConfig(loaded, configPath);
|
|
6475
|
+
}
|
|
6476
|
+
function rcConfig(configPath, content) {
|
|
6477
|
+
let parsed;
|
|
6478
|
+
try {
|
|
6479
|
+
parsed = getStaticJSONValue(parseJSON(content, { jsonSyntax: "jsonc" }));
|
|
6480
|
+
} catch (err) {
|
|
6481
|
+
throw loadError(configPath, err);
|
|
6482
|
+
}
|
|
6483
|
+
if (!isRecord$1(parsed)) throw loadError(configPath, /* @__PURE__ */ new TypeError("the config is not an object"));
|
|
6484
|
+
return toResolvedConfig(parsed, configPath);
|
|
6485
|
+
}
|
|
6486
|
+
/**
|
|
6155
6487
|
* Resolves the lowest Node major version a semver range can match.
|
|
6156
6488
|
*
|
|
6157
6489
|
* @param range - A semver range such as `>=24.12.0` or `^24 || ^26`.
|
|
@@ -7048,24 +7380,6 @@ function oxlintEslintPlugin(options = {}) {
|
|
|
7048
7380
|
//#endregion
|
|
7049
7381
|
//#region src/rules/flawless.ts
|
|
7050
7382
|
/**
|
|
7051
|
-
* Arrow return style rule on its own, for the Markdown code block override
|
|
7052
|
-
* where only the line width differs.
|
|
7053
|
-
*
|
|
7054
|
-
* @param options - Shared rule options.
|
|
7055
|
-
* @returns The rule map.
|
|
7056
|
-
*/
|
|
7057
|
-
function arrowStyleRules({ maxLen, maxLength, printWidth, tabWidth } = {}) {
|
|
7058
|
-
return { "flawless/arrow-return-style": ["error", {
|
|
7059
|
-
jsxAlwaysUseExplicitReturn: true,
|
|
7060
|
-
maxLen: maxLength ?? maxLen ?? printWidth ?? 100,
|
|
7061
|
-
maxObjectProperties: 2,
|
|
7062
|
-
namedExportsAlwaysUseExplicitReturn: true,
|
|
7063
|
-
objectReturnStyle: "complex-explicit",
|
|
7064
|
-
...tabWidth !== void 0 ? { tabWidth } : {},
|
|
7065
|
-
...printWidth !== void 0 ? { useOxfmt: { printWidth } } : {}
|
|
7066
|
-
}] };
|
|
7067
|
-
}
|
|
7068
|
-
/**
|
|
7069
7383
|
* Base (non-React) flawless rules shared between the ESLint and oxlint
|
|
7070
7384
|
* factories. The React flawless rules live in the react rule map; the
|
|
7071
7385
|
* type-aware rules live in {@link flawlessTypeAwareRules}, plus the type-aware
|
|
@@ -7111,6 +7425,23 @@ function antiSlopRules({ roblox = true }) {
|
|
|
7111
7425
|
...roblox ? {} : { "flawless/no-reflect-get": "error" }
|
|
7112
7426
|
};
|
|
7113
7427
|
}
|
|
7428
|
+
/**
|
|
7429
|
+
* The arrow return style rule.
|
|
7430
|
+
*
|
|
7431
|
+
* @param options - Shared rule options.
|
|
7432
|
+
* @returns The rule map.
|
|
7433
|
+
*/
|
|
7434
|
+
function arrowStyleRules({ maxLen, printWidth, tabWidth } = {}) {
|
|
7435
|
+
return { "flawless/arrow-return-style": ["error", {
|
|
7436
|
+
jsxAlwaysUseExplicitReturn: true,
|
|
7437
|
+
maxLen: maxLen ?? printWidth ?? 100,
|
|
7438
|
+
maxObjectProperties: 2,
|
|
7439
|
+
namedExportsAlwaysUseExplicitReturn: true,
|
|
7440
|
+
objectReturnStyle: "complex-explicit",
|
|
7441
|
+
...tabWidth !== void 0 ? { tabWidth } : {},
|
|
7442
|
+
...printWidth !== void 0 ? { useOxfmt: { printWidth } } : {}
|
|
7443
|
+
}] };
|
|
7444
|
+
}
|
|
7114
7445
|
//#endregion
|
|
7115
7446
|
//#region src/oxlint/configs/flawless.ts
|
|
7116
7447
|
function oxlintFlawless({ antiSlop = false, excludeFiles, roblox = true, stylistic = true } = {}, prettierOptions = {}) {
|
|
@@ -7707,6 +8038,28 @@ const defaultPluginRenaming = {
|
|
|
7707
8038
|
"yml": "yaml"
|
|
7708
8039
|
};
|
|
7709
8040
|
//#endregion
|
|
8041
|
+
//#region src/formatted-files.ts
|
|
8042
|
+
/**
|
|
8043
|
+
* The files the preset formats with oxfmt. ESLint mode enables `oxfmt/oxfmt`
|
|
8044
|
+
* on these globs; the standalone oxfmt config ignores every other file. One
|
|
8045
|
+
* list keeps both modes formatting the same files.
|
|
8046
|
+
*
|
|
8047
|
+
* Imported by the standalone oxfmt config, so keep it light.
|
|
8048
|
+
*/
|
|
8049
|
+
/** Formatted globs, by `oxfmt/oxfmt` config block. */
|
|
8050
|
+
const FORMATTED_FILES = {
|
|
8051
|
+
css: [GLOB_CSS, GLOB_POSTCSS],
|
|
8052
|
+
graphql: ["**/*.graphql"],
|
|
8053
|
+
html: ["**/*.html"],
|
|
8054
|
+
javascript: [GLOB_JS, GLOB_JSX],
|
|
8055
|
+
json: [GLOB_ALL_JSON],
|
|
8056
|
+
less: [GLOB_LESS],
|
|
8057
|
+
markdown: [GLOB_MARKDOWN],
|
|
8058
|
+
scss: [GLOB_SCSS],
|
|
8059
|
+
typescript: [GLOB_TS, GLOB_TSX],
|
|
8060
|
+
yaml: [GLOB_YAML]
|
|
8061
|
+
};
|
|
8062
|
+
//#endregion
|
|
7710
8063
|
//#region src/generated/stylistic.ts
|
|
7711
8064
|
const stylisticRuleNames = /* @__PURE__ */ new Set([
|
|
7712
8065
|
"array-bracket-newline",
|
|
@@ -7811,7 +8164,7 @@ const stylisticRuleNames = /* @__PURE__ */ new Set([
|
|
|
7811
8164
|
//#region src/oxlint/configs/oxfmt.ts
|
|
7812
8165
|
const require$1 = createRequire(import.meta.url);
|
|
7813
8166
|
function oxlintOxfmt(options) {
|
|
7814
|
-
const { componentExts: componentExtensions = [], files: oxfmtFiles, oxfmtOptions = {} } = options ?? {};
|
|
8167
|
+
const { componentExts: componentExtensions = [], files: oxfmtFiles, oxfmtOptions = {}, standalone = false } = options ?? {};
|
|
7815
8168
|
const configPrettier = require$1("eslint-config-prettier/flat");
|
|
7816
8169
|
const configPrettierRules = isRecord$1(configPrettier) && isRecord$1(configPrettier["rules"]) ? configPrettier["rules"] : {};
|
|
7817
8170
|
const rulesToIgnore = /* @__PURE__ */ new Set(["curly", "style/quotes"]);
|
|
@@ -7831,12 +8184,8 @@ function oxlintOxfmt(options) {
|
|
|
7831
8184
|
name: "oxfmt",
|
|
7832
8185
|
specifier: resolveJsPluginSpecifier("eslint-plugin-oxfmt")
|
|
7833
8186
|
};
|
|
7834
|
-
const jsFiles = oxfmtFiles?.flat() ?? [
|
|
7835
|
-
const tsFiles = oxfmtFiles?.flat() ?? [
|
|
7836
|
-
"**/*.{,c,m}ts",
|
|
7837
|
-
"**/*.{,c,m}tsx",
|
|
7838
|
-
...componentExtensions.map((extension) => `**/*.${extension}`)
|
|
7839
|
-
];
|
|
8187
|
+
const jsFiles = oxfmtFiles?.flat() ?? [...FORMATTED_FILES.javascript];
|
|
8188
|
+
const tsFiles = oxfmtFiles?.flat() ?? [...FORMATTED_FILES.typescript, ...componentExtensions.map((extension) => `**/*.${extension}`)];
|
|
7840
8189
|
function createFragments(name, files) {
|
|
7841
8190
|
return [{
|
|
7842
8191
|
name,
|
|
@@ -7849,10 +8198,13 @@ function oxlintOxfmt(options) {
|
|
|
7849
8198
|
}, {
|
|
7850
8199
|
name: `${name}/js-plugin`,
|
|
7851
8200
|
files,
|
|
7852
|
-
jsPlugins: [...jsPlugins, oxfmtPlugin],
|
|
8201
|
+
jsPlugins: standalone ? jsPlugins : [...jsPlugins, oxfmtPlugin],
|
|
7853
8202
|
rules: {
|
|
7854
8203
|
...jsPluginRules,
|
|
7855
|
-
"oxfmt/oxfmt": ["error",
|
|
8204
|
+
...standalone ? {} : { "oxfmt/oxfmt": ["error", {
|
|
8205
|
+
...oxfmtOptions,
|
|
8206
|
+
disableNestedConfig: true
|
|
8207
|
+
}] }
|
|
7856
8208
|
}
|
|
7857
8209
|
}];
|
|
7858
8210
|
}
|
|
@@ -9420,7 +9772,7 @@ const NATIVE_ONLY_JS_PLUGINS = /* @__PURE__ */ new Set(["oxlint-comments"]);
|
|
|
9420
9772
|
*/
|
|
9421
9773
|
function isentinel(factoryOptions, ...userConfigs) {
|
|
9422
9774
|
const options = factoryOptions ?? { name: "isentinel" };
|
|
9423
|
-
const { antiSlop = false, categories, componentExts: componentExtensions = [], e18e: enableE18e = true, env, eslintPlugin: enableEslintPlugin = false, formatters, gitignore: enableGitignore = true, globals, ignores, jsdoc: enableJsdoc = true, jsPlugins: userJsPlugins, jsx: enableJsx = true, options: linterOptions, oxc: enableOxc = true, projectStructure: enableProjectStructure = false, react: enableReact = false, root: customRootGlobs, rules = {}, spellCheck: enableSpellCheck, test: enableTest = false, warnDroppedOverrides: enableDroppedOverrideWarning = true } = options;
|
|
9775
|
+
const { antiSlop = false, categories, componentExts: componentExtensions = [], e18e: enableE18e = true, env, eslintPlugin: enableEslintPlugin = false, formatter = "eslint", formatters, gitignore: enableGitignore = true, globals, ignores, jsdoc: enableJsdoc = true, jsPlugins: userJsPlugins, jsx: enableJsx = true, options: linterOptions, oxc: enableOxc = true, projectStructure: enableProjectStructure = false, react: enableReact = false, root: customRootGlobs, rules = {}, spellCheck: enableSpellCheck, test: enableTest = false, warnDroppedOverrides: enableDroppedOverrideWarning = true } = options;
|
|
9424
9776
|
const rootGlobs = mergeGlobs(GLOB_ROOT, customRootGlobs);
|
|
9425
9777
|
const enableRoblox = options.roblox !== false;
|
|
9426
9778
|
const reactOptions = resolveSubOptions(options, "react");
|
|
@@ -9441,14 +9793,22 @@ function isentinel(factoryOptions, ...userConfigs) {
|
|
|
9441
9793
|
if (options.type !== void 0) return options.type;
|
|
9442
9794
|
return enableRoblox ? "game" : "package";
|
|
9443
9795
|
})();
|
|
9796
|
+
if (formatter === "standalone") warnStandaloneOptions(options);
|
|
9797
|
+
const formatterOptions = formatter !== "standalone" && typeof formatters === "object" ? formatters : {};
|
|
9798
|
+
const oxfmtFormats = formatter === "standalone" || formatters !== false && options.stylistic !== false;
|
|
9799
|
+
const prettierSettings = resolvePrettierSettings(formatterOptions.prettierOptions);
|
|
9800
|
+
const oxfmtOptions = buildOxfmtOptions({
|
|
9801
|
+
oxfmtConfigOptions: oxfmtFormats ? resolveOxfmtConfigOptionsSync() : {},
|
|
9802
|
+
oxfmtOptions: formatterOptions.oxfmtOptions,
|
|
9803
|
+
prettierOptions: prettierSettings
|
|
9804
|
+
});
|
|
9444
9805
|
const stylisticOptions = (() => {
|
|
9445
9806
|
if (options.stylistic === false) return false;
|
|
9446
9807
|
if (typeof options.stylistic === "object") return options.stylistic;
|
|
9447
9808
|
return {};
|
|
9448
9809
|
})();
|
|
9449
9810
|
if (stylisticOptions !== false && !("jsx" in stylisticOptions)) stylisticOptions.jsx = enableJsx;
|
|
9450
|
-
const
|
|
9451
|
-
const prettierSettings = resolvePrettierSettings(formatterOptions.prettierOptions);
|
|
9811
|
+
const widthSettings = withOxfmtWidths(prettierSettings, oxfmtOptions);
|
|
9452
9812
|
const configs = [
|
|
9453
9813
|
oxlintJavascript({
|
|
9454
9814
|
...getOverrides(options, "javascript"),
|
|
@@ -9540,7 +9900,7 @@ function isentinel(factoryOptions, ...userConfigs) {
|
|
|
9540
9900
|
if (stylisticOptions !== false) configs.push(oxlintPerfectionist({
|
|
9541
9901
|
...resolveSubOptions(options, "perfectionist"),
|
|
9542
9902
|
type: projectType
|
|
9543
|
-
}), oxlintStylistic(stylisticOptions));
|
|
9903
|
+
}), oxlintStylistic(oxfmtFormats ? withOxfmtStyle(stylisticOptions, oxfmtOptions) : stylisticOptions));
|
|
9544
9904
|
if (enableReact !== false) configs.push(oxlintReact({
|
|
9545
9905
|
roblox: enableRoblox,
|
|
9546
9906
|
settings: options.settings,
|
|
@@ -9569,30 +9929,24 @@ function isentinel(factoryOptions, ...userConfigs) {
|
|
|
9569
9929
|
antiSlop,
|
|
9570
9930
|
roblox: enableRoblox,
|
|
9571
9931
|
stylistic: stylisticOptions
|
|
9572
|
-
},
|
|
9932
|
+
}, widthSettings), ...needsComplementOverlay ? [oxlintFlawless({
|
|
9573
9933
|
antiSlop,
|
|
9574
9934
|
excludeFiles: robloxScopedFiles,
|
|
9575
9935
|
roblox: false,
|
|
9576
9936
|
stylistic: stylisticOptions
|
|
9577
|
-
},
|
|
9578
|
-
prettierOptions:
|
|
9937
|
+
}, widthSettings)] : [], oxlintComments({
|
|
9938
|
+
prettierOptions: widthSettings,
|
|
9579
9939
|
stylistic: stylisticOptions
|
|
9580
9940
|
}), oxlintDisables({
|
|
9581
9941
|
configDirectory: options.configDirectory,
|
|
9582
9942
|
root: rootGlobs,
|
|
9583
9943
|
workspaceRoot: findWorkspaceRootSync()
|
|
9584
9944
|
}));
|
|
9585
|
-
if (stylisticOptions !== false && formatters !== false) {
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
oxfmtConfigOptions,
|
|
9591
|
-
oxfmtOptions: formatterOptions.oxfmtOptions,
|
|
9592
|
-
prettierOptions: prettierSettings
|
|
9593
|
-
})
|
|
9594
|
-
}));
|
|
9595
|
-
}
|
|
9945
|
+
if (stylisticOptions !== false && formatters !== false) configs.push(oxlintOxfmt({
|
|
9946
|
+
componentExts: componentExtensions,
|
|
9947
|
+
oxfmtOptions,
|
|
9948
|
+
standalone: formatter === "standalone"
|
|
9949
|
+
}));
|
|
9596
9950
|
const jsPlugins = /* @__PURE__ */ new Map();
|
|
9597
9951
|
const nativePlugins = /* @__PURE__ */ new Set();
|
|
9598
9952
|
const globalPlugins = /* @__PURE__ */ new Set();
|