@isentinel/eslint-config 6.2.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 CHANGED
@@ -990,19 +990,21 @@ as in the factories, so oxfmt formats the same files the linter would. TOML,
990
990
  `.mdx` and other files the linter does not format are ignored.
991
991
 
992
992
  In standalone mode, oxfmt never sees the linter's options. The factories warn
993
- once per lint run when you set any of these, because they do not change
994
- formatting: `formatters` (any value except `true`), `componentExts`, `ignores`,
995
- or `gitignore` (`false` or custom files). Set the equivalent in `isentinelOxfmt`
996
- instead (`formatters`, `componentExts`, `ignorePatterns`, and format options).
993
+ once per lint run when you set any of these, because they do not change what
994
+ oxfmt formats: `formatters` (any value except `true`), `componentExts`,
995
+ `ignores`, or `gitignore` (`false` or custom files). They change only which
996
+ Markdown code blocks ESLint formats: `formatters` chooses their languages, and
997
+ the ignores choose their files. Set the equivalent in `isentinelOxfmt` instead
998
+ (`formatters`, `componentExts`, `ignorePatterns`, and format options).
997
999
  `stylistic: false` turns off the stylistic lint rules but not oxfmt. The rule
998
1000
  widths also come from the oxfmt config only, not from
999
1001
  `formatters.prettierOptions` or `formatters.oxfmtOptions`. The setup wizard
1000
1002
  copies migrated `.eslintignore` patterns to both configs.
1001
1003
 
1002
- With `formatter: "standalone"`, the linter enables `oxfmt/oxfmt` nowhere but
1003
- keeps the rules that turn off formatting conflicts. Lua formatting (Stylua)
1004
- stays in ESLint, because oxfmt does not format Lua. The setup wizard can write
1005
- all of this for you.
1004
+ With `formatter: "standalone"`, the linter enables `oxfmt/oxfmt` only in
1005
+ Markdown code blocks (see below), but keeps the rules that turn off formatting
1006
+ conflicts. Lua formatting (Stylua) stays in ESLint, because oxfmt does not
1007
+ format Lua. The setup wizard can write all of this for you.
1006
1008
 
1007
1009
  `isentinel-lint` runs `oxfmt --check` next to the linters whenever it finds an
1008
1010
  `oxfmt.config.ts` or `oxfmt.config.mts`, and `oxfmt --write` last with `--fix`.
@@ -1031,12 +1033,18 @@ becomes implicit only if it fits. It comes from `stylistic.maxLen`, else
1031
1033
  `useOxfmt.printWidth` is the width the rule gives oxfmt to predict its output.
1032
1034
  It always comes from the root `printWidth` of the oxfmt config, so that `--fix`
1033
1035
  agrees with oxfmt. An override that changes `printWidth` or `tabWidth` for JS or
1034
- TS files gets a warning, because the linter uses the root values. In Markdown
1035
- code blocks, `useOxfmt` uses the Markdown widths instead, because oxfmt formats
1036
- embedded code with the Markdown options. Each oxfmt, `.editorconfig` or
1037
- `.prettierrc` override that changes the Markdown widths reaches only the code
1038
- blocks of the files it matches. The comment length rules take their `tabSize`
1039
- from the same oxfmt widths.
1036
+ TS files gets a warning, because the linter uses the root values. The comment
1037
+ length rules take their `tabSize` from the same oxfmt widths.
1038
+
1039
+ Markdown prose and Markdown code blocks are formatted separately, in both modes.
1040
+ Oxfmt formats the prose at the Markdown width (`jsdocPrintWidth`, else 80) and
1041
+ leaves code blocks alone (`embeddedLanguageFormatting: "off"`). ESLint formats
1042
+ each code block with `oxfmt/oxfmt` and the options of its language, the same as
1043
+ a file of that language: a TypeScript block uses the root `printWidth`, a YAML
1044
+ block the YAML options. Overrides and `.editorconfig` sections, which match by
1045
+ path, do not reach code blocks; the config's `ignorePatterns` do. The lint rules
1046
+ in a code block use the same widths as in a source file. ESLint does not format
1047
+ code blocks in files it ignores.
1040
1048
 
1041
1049
  The stylistic `indent` and `quotes`, and the JSON bracket spacing, follow the
1042
1050
  oxfmt options (`useTabs`, `tabWidth`, `singleQuote`, `bracketSpacing`) unless
package/dist/cli.mjs CHANGED
@@ -83,7 +83,7 @@ function oxfmtConfigNamesIn(directory) {
83
83
  }
84
84
  //#endregion
85
85
  //#region package.json
86
- var version = "6.2.0";
86
+ var version = "6.3.0";
87
87
  var package_default = {
88
88
  name: "@isentinel/eslint-config",
89
89
  version,
package/dist/index.d.mts CHANGED
@@ -27270,7 +27270,7 @@ type Yoda = [] | [("always" | "never")] | [("always" | "never"), {
27270
27270
  onlyEquality?: boolean;
27271
27271
  }];
27272
27272
  // Names of all the configs
27273
- type ConfigNames = 'isentinel/eslint/comments' | 'isentinel/eslint/comments/src' | 'isentinel/e18e/rules' | 'isentinel/eslint-plugin/setup' | 'isentinel/eslint-plugin/rules' | 'isentinel/flawless/setup' | 'isentinel/flawless/rules' | 'isentinel/flawless/markdown-code' | 'isentinel/flawless/rules-type-aware' | 'isentinel/gitignore' | 'isentinel/ignores' | 'isentinel/imports/rules' | 'isentinel/imports/game' | 'isentinel/javascript/setup' | 'isentinel/javascript/rules' | 'isentinel/jsdoc/setup' | 'isentinel/jsdoc' | 'isentinel/jsonc/setup' | 'isentinel/jsonc/rules' | 'isentinel/jsonc/tsconfig' | 'isentinel/markdown/setup' | 'isentinel/markdown/processor' | 'isentinel/markdown/parser' | 'isentinel/markdown/disables' | 'isentinel/naming/setup' | 'isentinel/naming/ts/rules-type-aware' | 'isentinel/naming/tsx/rules-type-aware' | 'isentinel/node/rules' | 'isentinel/oxfmt/setup' | 'isentinel/oxfmt/javascript' | 'isentinel/oxfmt/typescript' | 'isentinel/oxfmt/css' | 'isentinel/oxfmt/scss' | 'isentinel/oxfmt/less' | 'isentinel/oxfmt/html' | 'isentinel/oxfmt/markdown' | 'isentinel/oxfmt/graphql' | 'isentinel/oxfmt/json' | 'isentinel/oxfmt/json5' | 'isentinel/oxfmt/yaml' | 'isentinel/package-json/setup' | 'isentinel/package-json' | 'isentinel/package-json/root' | 'isentinel/perfectionist/setup' | 'isentinel/perfectionist' | 'isentinel/perfectionist/jsx' | 'isentinel/pnpm/setup' | 'isentinel/pnpm/package-json' | 'isentinel/pnpm/pnpm-workspace-yaml' | 'isentinel/project-structure/setup' | 'isentinel/project-structure/rules' | 'isentinel/promise' | 'isentinel/react/setup' | 'isentinel/react/setup/naming' | 'isentinel/react/setup/testing-library' | 'isentinel/react/rules' | 'isentinel/react/type-aware-rules' | 'isentinel/roblox/setup' | 'isentinel/roblox/parser' | 'isentinel/roblox/type-aware-parser' | 'isentinel/roblox' | 'isentinel/roblox/rules-type-aware' | 'isentinel/roblox/format-lua/setup' | 'isentinel/roblox/format-lua' | 'isentinel/small-rules/setup' | 'isentinel/small-rules' | 'isentinel/sonarjs' | 'isentinel/spelling/setup' | 'isentinel/spelling' | 'isentinel/stylistic/setup' | 'isentinel/stylistic' | 'isentinel/test/jest/setup' | 'isentinel/test/jest/rules' | 'isentinel/test/jest/type-tests' | 'isentinel/test/vitest/setup' | 'isentinel/test/vitest/rules' | 'isentinel/test/vitest/type-tests' | 'isentinel/toml/setup' | 'isentinel/toml/rules' | 'isentinel/typescript/setup' | 'isentinel/typescript/parser' | 'isentinel/typescript/type-aware-parser' | 'isentinel/typescript/rules' | 'isentinel/typescript/rules-type-aware' | 'isentinel/typescript/erasable-syntax-only' | 'isentinel/unicorn/setup' | 'isentinel/unicorn/rules' | 'isentinel/unicorn/root' | 'isentinel/yaml/setup' | 'isentinel/yaml/rules';
27273
+ type ConfigNames = 'isentinel/eslint/comments' | 'isentinel/eslint/comments/src' | 'isentinel/e18e/rules' | 'isentinel/eslint-plugin/setup' | 'isentinel/eslint-plugin/rules' | 'isentinel/flawless/setup' | 'isentinel/flawless/rules' | 'isentinel/flawless/rules-type-aware' | 'isentinel/gitignore' | 'isentinel/ignores' | 'isentinel/imports/rules' | 'isentinel/imports/game' | 'isentinel/javascript/setup' | 'isentinel/javascript/rules' | 'isentinel/jsdoc/setup' | 'isentinel/jsdoc' | 'isentinel/jsonc/setup' | 'isentinel/jsonc/rules' | 'isentinel/jsonc/tsconfig' | 'isentinel/markdown/setup' | 'isentinel/markdown/processor' | 'isentinel/markdown/parser' | 'isentinel/markdown/disables' | 'isentinel/naming/setup' | 'isentinel/naming/ts/rules-type-aware' | 'isentinel/naming/tsx/rules-type-aware' | 'isentinel/node/rules' | 'isentinel/oxfmt/setup' | 'isentinel/oxfmt/javascript' | 'isentinel/oxfmt/typescript' | 'isentinel/oxfmt/css' | 'isentinel/oxfmt/scss' | 'isentinel/oxfmt/less' | 'isentinel/oxfmt/html' | 'isentinel/oxfmt/markdown' | 'isentinel/oxfmt/graphql' | 'isentinel/oxfmt/json' | 'isentinel/oxfmt/json5' | 'isentinel/oxfmt/yaml' | 'isentinel/oxfmt/markdown-code/javascript' | 'isentinel/oxfmt/markdown-code/typescript' | 'isentinel/oxfmt/markdown-code/css' | 'isentinel/oxfmt/markdown-code/scss' | 'isentinel/oxfmt/markdown-code/less' | 'isentinel/oxfmt/markdown-code/html' | 'isentinel/oxfmt/markdown-code/graphql' | 'isentinel/oxfmt/markdown-code/json' | 'isentinel/oxfmt/markdown-code/json5' | 'isentinel/oxfmt/markdown-code/yaml' | 'isentinel/package-json/setup' | 'isentinel/package-json' | 'isentinel/package-json/root' | 'isentinel/perfectionist/setup' | 'isentinel/perfectionist' | 'isentinel/perfectionist/jsx' | 'isentinel/pnpm/setup' | 'isentinel/pnpm/package-json' | 'isentinel/pnpm/pnpm-workspace-yaml' | 'isentinel/project-structure/setup' | 'isentinel/project-structure/rules' | 'isentinel/promise' | 'isentinel/react/setup' | 'isentinel/react/setup/naming' | 'isentinel/react/setup/testing-library' | 'isentinel/react/rules' | 'isentinel/react/type-aware-rules' | 'isentinel/roblox/setup' | 'isentinel/roblox/parser' | 'isentinel/roblox/type-aware-parser' | 'isentinel/roblox' | 'isentinel/roblox/rules-type-aware' | 'isentinel/roblox/format-lua/setup' | 'isentinel/roblox/format-lua' | 'isentinel/small-rules/setup' | 'isentinel/small-rules' | 'isentinel/sonarjs' | 'isentinel/spelling/setup' | 'isentinel/spelling' | 'isentinel/stylistic/setup' | 'isentinel/stylistic' | 'isentinel/test/jest/setup' | 'isentinel/test/jest/rules' | 'isentinel/test/jest/type-tests' | 'isentinel/test/vitest/setup' | 'isentinel/test/vitest/rules' | 'isentinel/test/vitest/type-tests' | 'isentinel/toml/setup' | 'isentinel/toml/rules' | 'isentinel/typescript/setup' | 'isentinel/typescript/parser' | 'isentinel/typescript/type-aware-parser' | 'isentinel/typescript/rules' | 'isentinel/typescript/rules-type-aware' | 'isentinel/typescript/erasable-syntax-only' | 'isentinel/unicorn/setup' | 'isentinel/unicorn/rules' | 'isentinel/unicorn/root' | 'isentinel/yaml/setup' | 'isentinel/yaml/rules';
27274
27274
  //#endregion
27275
27275
  //#region src/utils.d.ts
27276
27276
  type ExtractRuleOptions<T> = T extends Linter.RuleEntry<infer U> ? U : never;
@@ -27315,21 +27315,12 @@ type OxfmtOptions = Omit<FormatConfig, "jsdoc"> & {
27315
27315
  lineWrappingStyle?: "balance" | "greedy";
27316
27316
  });
27317
27317
  };
27318
- /** The widths oxfmt formats Markdown files with. */
27319
- interface MarkdownWidths {
27320
- printWidth?: number;
27321
- tabWidth?: number;
27322
- }
27323
- /** A config override that changes the widths of some Markdown files. */
27324
27318
  /** Where an oxfmt override applies, relative to the config directory. */
27325
27319
  interface OverrideScope {
27326
27320
  /** Globs it skips: its `excludeFiles` and its negated `files`. */
27327
27321
  excludeFiles: Array<string>;
27328
27322
  files: Array<string>;
27329
27323
  }
27330
- interface MarkdownWidthOverride extends OverrideScope {
27331
- widths: MarkdownWidths;
27332
- }
27333
27324
  //#endregion
27334
27325
  //#region src/types.d.ts
27335
27326
  type Awaitable<T> = Promise<T> | T;
@@ -28362,12 +28353,7 @@ declare const GLOB_ALL_SRC: string[];
28362
28353
  declare const GLOB_EXCLUDE: string[];
28363
28354
  //#endregion
28364
28355
  //#region src/eslint/configs/comments.d.ts
28365
- declare function comments({ markdownCode, markdownCodeOverrides, prettierOptions, stylistic }?: OptionsFormatters & OptionsStylistic & {
28366
- /** The widths oxfmt formats Markdown code blocks with. */
28367
- markdownCode?: MarkdownWidths;
28368
- /** Oxfmt config overrides that change the Markdown widths. */
28369
- markdownCodeOverrides?: Array<MarkdownWidthOverride>;
28370
- }): Promise<Array<TypedFlatConfigItem>>;
28356
+ declare function comments({ prettierOptions, stylistic }?: OptionsFormatters & OptionsStylistic): Promise<Array<TypedFlatConfigItem>>;
28371
28357
  //#endregion
28372
28358
  //#region src/eslint/configs/disables.d.ts
28373
28359
  /**
@@ -28415,6 +28401,8 @@ interface FlawlessRuleOptions extends OptionsHasRoblox {
28415
28401
  declare function oxfmt(options?: OptionsComponentExtensions & OptionsFiles & OptionsOverrides & {
28416
28402
  formatters?: OptionsFormatters | true;
28417
28403
  oxfmtConfigOptions?: OxfmtOptions;
28404
+ /** The oxfmt config's ignore patterns, for Markdown code blocks. */
28405
+ oxfmtIgnorePatterns?: Array<string>;
28418
28406
  oxfmtOptions?: OxfmtOptions;
28419
28407
  oxlint?: boolean;
28420
28408
  prettierOptions?: Options;
@@ -28433,14 +28421,6 @@ declare function flawless(options?: FlawlessRuleOptions & OptionsOverridesTypeAw
28433
28421
  * linted as standard-TS/Node land.
28434
28422
  */
28435
28423
  complementIgnores?: Array<string>;
28436
- /**
28437
- * The widths oxfmt formats Markdown code blocks with, when it
28438
- * formats Markdown: it formats embedded code with the Markdown
28439
- * options, not the root ones.
28440
- */
28441
- markdownCode?: MarkdownWidths;
28442
- /** Oxfmt config overrides that change the Markdown widths. */
28443
- markdownCodeOverrides?: Array<MarkdownWidthOverride>;
28444
28424
  }, prettierOptions?: PrettierOptions): Promise<Array<TypedFlatConfigItem>>;
28445
28425
  //#endregion
28446
28426
  //#region src/eslint/configs/gitignore.d.ts
package/dist/index.mjs CHANGED
@@ -764,6 +764,32 @@ function editorConfigToOxfmt(properties) {
764
764
  return options;
765
765
  }
766
766
  //#endregion
767
+ //#region src/guards.ts
768
+ /**
769
+ * Internal runtime type guards. Prefer these over `as` assertions so values
770
+ * crossing untyped boundaries (`JSON.parse`, dynamic `import`, plugin objects)
771
+ * are validated at runtime rather than asserted away.
772
+ */
773
+ /**
774
+ * Whether a value is a non-null, non-array object usable as a string-keyed
775
+ * record. Narrows `unknown` without an assertion.
776
+ *
777
+ * @param value - The value to test.
778
+ * @returns Whether the value is a plain object.
779
+ */
780
+ function isRecord(value) {
781
+ return typeof value === "object" && value !== null && !Array.isArray(value);
782
+ }
783
+ /**
784
+ * Whether a value is an array whose every element is a string.
785
+ *
786
+ * @param value - The value to test.
787
+ * @returns Whether the value is a `string` array.
788
+ */
789
+ function isStringArray(value) {
790
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
791
+ }
792
+ //#endregion
767
793
  //#region src/rules/oxfmt.ts
768
794
  const UNSUPPORTED_PRETTIER_KEYS = /* @__PURE__ */ new Set([
769
795
  "experimentalOperatorPosition",
@@ -830,12 +856,12 @@ function migratePrettierOptions(prettierOptions) {
830
856
  function migratePrettierOverrides(overrides) {
831
857
  if (!Array.isArray(overrides)) return [];
832
858
  return overrides.flatMap((override) => {
833
- if (typeof override !== "object" || override === null) return [];
859
+ if (!isRecord(override)) return [];
834
860
  const { excludeFiles, files, options } = override;
835
861
  return [{
836
862
  ...excludeFiles === void 0 ? {} : { excludeFiles: stringsOf(excludeFiles) },
837
863
  files: stringsOf(files),
838
- options: typeof options === "object" && options !== null ? migratePrettierOptions(options) : {}
864
+ options: isRecord(options) ? migratePrettierOptions(options) : {}
839
865
  }];
840
866
  });
841
867
  }
@@ -860,11 +886,16 @@ function buildOxfmtOptions({ oxfmtConfigOptions = {}, oxfmtOptions, prettierOpti
860
886
  * ESLint config and the standalone oxfmt config so both format Markdown the
861
887
  * same way.
862
888
  *
889
+ * Oxfmt would format code blocks with these options too, at the prose width.
890
+ * It leaves them alone instead, and ESLint formats each block with the options
891
+ * of its own language (see `isentinel/oxfmt/markdown-code/*`).
892
+ *
863
893
  * @param jsdocPrintWidth - Width for Markdown prose; 80 when unset.
864
894
  * @returns Options applied on top of the base for Markdown files.
865
895
  */
866
896
  function markdownOxfmtOptions(jsdocPrintWidth) {
867
897
  return {
898
+ embeddedLanguageFormatting: "off",
868
899
  printWidth: Number(jsdocPrintWidth) || 80,
869
900
  proseWrap: "always",
870
901
  tabWidth: 2
@@ -885,23 +916,6 @@ const YAML_OXFMT_OPTIONS = {
885
916
  useTabs: false
886
917
  };
887
918
  //#endregion
888
- //#region src/guards.ts
889
- /**
890
- * Internal runtime type guards. Prefer these over `as` assertions so values
891
- * crossing untyped boundaries (`JSON.parse`, dynamic `import`, plugin objects)
892
- * are validated at runtime rather than asserted away.
893
- */
894
- /**
895
- * Whether a value is a non-null, non-array object usable as a string-keyed
896
- * record. Narrows `unknown` without an assertion.
897
- *
898
- * @param value - The value to test.
899
- * @returns Whether the value is a plain object.
900
- */
901
- function isRecord(value) {
902
- return typeof value === "object" && value !== null && !Array.isArray(value);
903
- }
904
- //#endregion
905
919
  //#region src/rules/react.ts
906
920
  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.";
907
921
  /**
@@ -1507,8 +1521,6 @@ const SCRIPT_EXTENSIONS = [
1507
1521
  "ts",
1508
1522
  "tsx"
1509
1523
  ];
1510
- /** The extensions of the Markdown files ESLint lints code blocks in. */
1511
- const MARKDOWN_EXTENSIONS = ["md"];
1512
1524
  const JSON_EXTENSIONS = [
1513
1525
  "json",
1514
1526
  "json5",
@@ -1553,21 +1565,13 @@ function findOxfmtConfig(cwd = process.cwd()) {
1553
1565
  }
1554
1566
  }
1555
1567
  /**
1556
- * The overrides that set `printWidth` or `tabWidth` for Markdown files.
1568
+ * The string globs of an override's `files` or `excludeFiles`.
1557
1569
  *
1558
- * @param overrides - The config's `overrides`.
1559
- * @returns Each such override's scope and widths, in order.
1570
+ * @param value - One glob or a list of them, of unknown shape.
1571
+ * @returns The string globs.
1560
1572
  */
1561
- function markdownOverridesOf(overrides) {
1562
- if (!Array.isArray(overrides)) return [];
1563
- return overrides.flatMap((override) => {
1564
- if (!isRecord(override) || !overrideMatches(override, MARKDOWN_EXTENSIONS)) return [];
1565
- const widths = widthsOf(override["options"]);
1566
- return Object.keys(widths).length > 0 ? [{
1567
- ...scopeOf(override),
1568
- widths
1569
- }] : [];
1570
- });
1573
+ function globsOf(value) {
1574
+ return [value].flat().filter((glob) => typeof glob === "string");
1571
1575
  }
1572
1576
  /**
1573
1577
  * Where an override applies: its `files`, minus its `excludeFiles` and any
@@ -1577,12 +1581,9 @@ function markdownOverridesOf(overrides) {
1577
1581
  * @returns Its scope.
1578
1582
  */
1579
1583
  function scopeOf(override) {
1580
- const strings = (value) => {
1581
- return [value].flat().filter((glob) => typeof glob === "string");
1582
- };
1583
- const globs = strings(override["files"]);
1584
+ const globs = globsOf(override["files"]);
1584
1585
  return {
1585
- excludeFiles: [...strings(override["excludeFiles"]), ...globs.filter((glob) => glob.startsWith("!")).map((glob) => glob.slice(1))],
1586
+ excludeFiles: [...globsOf(override["excludeFiles"]), ...globs.filter((glob) => glob.startsWith("!")).map((glob) => glob.slice(1))],
1586
1587
  files: globs.filter((glob) => !glob.startsWith("!"))
1587
1588
  };
1588
1589
  }
@@ -1609,7 +1610,8 @@ function jsonLayoutOverrides(overrides) {
1609
1610
  }
1610
1611
  /**
1611
1612
  * Warn, at most once per process, about factory options that do not reach
1612
- * standalone oxfmt: it formats with `oxfmt.config.ts` alone.
1613
+ * standalone oxfmt: it formats with `oxfmt.config.ts` alone. They still choose
1614
+ * which Markdown code blocks ESLint formats.
1613
1615
  *
1614
1616
  * @param options - The factory options.
1615
1617
  * @param options.componentExts - Oxfmt formats only the extensions
@@ -1630,7 +1632,7 @@ function warnStandaloneOptions({ componentExts, formatters, gitignore, ignores }
1630
1632
  ].filter((name) => name !== void 0);
1631
1633
  if (names.length > 0) {
1632
1634
  state[WARNED_STANDALONE] = true;
1633
- console.warn(`[@isentinel/eslint-config] With formatter: "standalone", oxfmt formats with oxfmt.config.ts alone, so these options do not change formatting: ${names.join(", ")}. Set the equivalent in isentinelOxfmt() (formatters, componentExts, ignorePatterns, format options).`);
1635
+ 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).`);
1634
1636
  }
1635
1637
  }
1636
1638
  /**
@@ -1658,8 +1660,7 @@ function warnScriptWidthOverrides(overrides, source) {
1658
1660
  * {@link findOxfmtConfig}): `.oxfmtrc.json`, `.oxfmtrc.jsonc`,
1659
1661
  * `oxfmt.config.ts` or `oxfmt.config.mts`.
1660
1662
  *
1661
- * @returns The format options and Markdown widths; empty options if none
1662
- * found.
1663
+ * @returns The format options and overrides; empty options if none found.
1663
1664
  * @rejects When the config is ambiguous or cannot be loaded.
1664
1665
  */
1665
1666
  async function resolveOxfmtConfig() {
@@ -1748,26 +1749,26 @@ function mirrorsSection(override, sections) {
1748
1749
  });
1749
1750
  }
1750
1751
  /**
1751
- * Turn a loaded oxfmt config into `oxfmt/oxfmt` rule options and the Markdown
1752
- * widths. The rule takes format options only, so `$schema`, `ignorePatterns`
1753
- * and `overrides` are dropped from the options.
1752
+ * Turn a loaded oxfmt config into `oxfmt/oxfmt` rule options, its ignore
1753
+ * patterns and its overrides. The rule takes format options only, so
1754
+ * `$schema`, `ignorePatterns` and `overrides` are dropped from the options.
1754
1755
  *
1755
1756
  * @param config - The loaded config.
1756
1757
  * @param config.$schema - Dropped.
1757
- * @param config.ignorePatterns - Dropped; oxfmt applies them itself.
1758
- * @param config.overrides - Dropped, after the width check; read for the
1759
- * Markdown widths.
1758
+ * @param config.ignorePatterns - Dropped from the options; oxfmt applies them
1759
+ * itself, except in Markdown code blocks.
1760
+ * @param config.overrides - Dropped from the options, after the width check.
1760
1761
  * @param configPath - The config file, for the warning.
1761
- * @returns The format options and Markdown widths.
1762
+ * @returns The format options, ignore patterns and overrides.
1762
1763
  */
1763
- function toResolvedConfig({ $schema: _schema, ignorePatterns: _ignorePatterns, overrides, ...options }, configPath) {
1764
+ function toResolvedConfig({ $schema: _schema, ignorePatterns, overrides, ...options }, configPath) {
1764
1765
  const sections = editorConfigOverrides(path.dirname(configPath));
1765
1766
  const list = Array.isArray(overrides) ? overrides : [];
1766
1767
  const copied = list.filter((override) => mirrorsSection(override, sections));
1767
1768
  warnScriptWidthOverrides(copied, ".editorconfig");
1768
1769
  warnScriptWidthOverrides(list.filter((override) => !copied.includes(override)), path.basename(configPath));
1769
1770
  return {
1770
- markdownOverrides: markdownOverridesOf(overrides),
1771
+ ignorePatterns: isStringArray(ignorePatterns) ? ignorePatterns : [],
1771
1772
  options,
1772
1773
  overrides: Array.isArray(overrides) ? overrides : []
1773
1774
  };
@@ -1787,20 +1788,6 @@ function rcConfig(configPath, content) {
1787
1788
  return toResolvedConfig(parsed, configPath);
1788
1789
  }
1789
1790
  /**
1790
- * Pick `printWidth` and `tabWidth` out of a set of format options.
1791
- *
1792
- * @param options - Format options, of unknown shape.
1793
- * @returns The widths it sets.
1794
- */
1795
- function widthsOf(options) {
1796
- if (!isRecord(options)) return {};
1797
- const { printWidth, tabWidth } = options;
1798
- return {
1799
- ...typeof printWidth === "number" ? { printWidth } : {},
1800
- ...typeof tabWidth === "number" ? { tabWidth } : {}
1801
- };
1802
- }
1803
- /**
1804
1791
  * Resolves the lowest Node major version a semver range can match.
1805
1792
  *
1806
1793
  * @param range - A semver range such as `>=24.12.0` or `^24 || ^26`.
@@ -1979,15 +1966,7 @@ function hydrate(specifier, transform) {
1979
1966
  //#endregion
1980
1967
  //#region src/eslint/configs/comments.ts
1981
1968
  init_globs();
1982
- async function comments({ markdownCode, markdownCodeOverrides = [], prettierOptions = {}, stylistic = true } = {}) {
1983
- function lengthRules(tabSize) {
1984
- return commentLengthRules({
1985
- maxLength: (Number(prettierOptions["jsdocPrintWidth"]) || 80) + 2,
1986
- multiLineMaxLength: Number(prettierOptions["jsdocPrintWidth"]) || 80,
1987
- semanticComments: ["oxlint-disable", "oxlint-enable"],
1988
- tabSize: tabSize ?? prettierOptions.tabWidth ?? 4
1989
- });
1990
- }
1969
+ async function comments({ prettierOptions = {}, stylistic = true } = {}) {
1991
1970
  const pluginCommentLength = lazyPlugin("eslint-plugin-comment-length");
1992
1971
  const pluginStylistic = lazyPlugin("@stylistic/eslint-plugin");
1993
1972
  return [{
@@ -2009,19 +1988,13 @@ async function comments({ markdownCode, markdownCodeOverrides = [], prettierOpti
2009
1988
  }, ...stylistic !== false ? [{
2010
1989
  name: "isentinel/eslint/comments/src",
2011
1990
  files: [GLOB_SRC],
2012
- rules: lengthRules(void 0)
2013
- }, ...markdownCode === void 0 ? [] : [{
2014
- name: "isentinel/eslint/comments/markdown-code",
2015
- files: [GLOB_MARKDOWN_CODE],
2016
- rules: lengthRules(markdownCode.tabWidth)
2017
- }, ...markdownCodeOverrides.map(({ excludeFiles, files, widths }, index) => {
2018
- return {
2019
- name: `isentinel/eslint/comments/markdown-code/override-${index}`,
2020
- files: files.map((glob) => `${glob}/${GLOB_SRC}`),
2021
- ignores: excludeFiles.map((glob) => `${glob}/**`),
2022
- rules: lengthRules(widths.tabWidth ?? markdownCode.tabWidth)
2023
- };
2024
- })]] : []];
1991
+ rules: commentLengthRules({
1992
+ maxLength: (Number(prettierOptions["jsdocPrintWidth"]) || 80) + 2,
1993
+ multiLineMaxLength: Number(prettierOptions["jsdocPrintWidth"]) || 80,
1994
+ semanticComments: ["oxlint-disable", "oxlint-enable"],
1995
+ tabSize: prettierOptions.tabWidth ?? 4
1996
+ })
1997
+ }] : []];
2025
1998
  }
2026
1999
  //#endregion
2027
2000
  //#region src/eslint/configs/disables.ts
@@ -2299,24 +2272,6 @@ async function eslintPlugin({ files = [GLOB_SRC], overrides = {} } = {}) {
2299
2272
  //#endregion
2300
2273
  //#region src/rules/flawless.ts
2301
2274
  /**
2302
- * Arrow return style rule on its own, for the Markdown code block override
2303
- * where only the line width differs.
2304
- *
2305
- * @param options - Shared rule options.
2306
- * @returns The rule map.
2307
- */
2308
- function arrowStyleRules({ maxLen, maxLength, printWidth, tabWidth } = {}) {
2309
- return { "flawless/arrow-return-style": ["error", {
2310
- jsxAlwaysUseExplicitReturn: true,
2311
- maxLen: maxLength ?? maxLen ?? printWidth ?? 100,
2312
- maxObjectProperties: 2,
2313
- namedExportsAlwaysUseExplicitReturn: true,
2314
- objectReturnStyle: "complex-explicit",
2315
- ...tabWidth !== void 0 ? { tabWidth } : {},
2316
- ...printWidth !== void 0 ? { useOxfmt: { printWidth } } : {}
2317
- }] };
2318
- }
2319
- /**
2320
2275
  * Base (non-React) flawless rules shared between the ESLint and oxlint
2321
2276
  * factories. The React flawless rules live in the react rule map; the
2322
2277
  * type-aware rules live in {@link flawlessTypeAwareRules}, plus the type-aware
@@ -2377,6 +2332,23 @@ function antiSlopRules({ roblox = true }) {
2377
2332
  };
2378
2333
  }
2379
2334
  /**
2335
+ * The arrow return style rule.
2336
+ *
2337
+ * @param options - Shared rule options.
2338
+ * @returns The rule map.
2339
+ */
2340
+ function arrowStyleRules({ maxLen, printWidth, tabWidth } = {}) {
2341
+ return { "flawless/arrow-return-style": ["error", {
2342
+ jsxAlwaysUseExplicitReturn: true,
2343
+ maxLen: maxLen ?? printWidth ?? 100,
2344
+ maxObjectProperties: 2,
2345
+ namedExportsAlwaysUseExplicitReturn: true,
2346
+ objectReturnStyle: "complex-explicit",
2347
+ ...tabWidth !== void 0 ? { tabWidth } : {},
2348
+ ...printWidth !== void 0 ? { useOxfmt: { printWidth } } : {}
2349
+ }] };
2350
+ }
2351
+ /**
2380
2352
  * Type-aware anti-slop rules.
2381
2353
  *
2382
2354
  * `flawless/no-reflect-set` is complement-only, like `flawless/no-reflect-get`.
@@ -2397,7 +2369,7 @@ function antiSlopTypeAwareRules({ roblox = true }) {
2397
2369
  //#region src/eslint/configs/flawless.ts
2398
2370
  init_globs();
2399
2371
  async function flawless(options = {}, prettierOptions = {}) {
2400
- const { antiSlop = false, complementIgnores, markdownCode = {}, markdownCodeOverrides = [], overridesTypeAware = {}, roblox = true, stylistic = true, typeAware = true } = options;
2372
+ const { antiSlop = false, complementIgnores, overridesTypeAware = {}, roblox = true, stylistic = true, typeAware = true } = options;
2401
2373
  const eslintPluginFlawless = await interopDefault(import("eslint-plugin-flawless"));
2402
2374
  const filesTypeAware = [GLOB_TS, GLOB_TSX];
2403
2375
  const ignoresTypeAware = options.ignoresTypeAware ?? [`**/*.md/**`, "**/*.d.{,c,m}ts"];
@@ -2412,13 +2384,6 @@ async function flawless(options = {}, prettierOptions = {}) {
2412
2384
  stylistic,
2413
2385
  tabWidth
2414
2386
  };
2415
- function markdownCodeRules(widths) {
2416
- return arrowStyleRules({
2417
- maxLength: Number(prettierOptions["jsdocPrintWidth"]) || 80,
2418
- printWidth: widths.printWidth ?? printWidth,
2419
- tabWidth: widths.tabWidth ?? tabWidth
2420
- });
2421
- }
2422
2387
  return [
2423
2388
  {
2424
2389
  name: "isentinel/flawless/setup",
@@ -2432,21 +2397,6 @@ async function flawless(options = {}, prettierOptions = {}) {
2432
2397
  roblox
2433
2398
  })
2434
2399
  },
2435
- ...stylistic !== false ? [{
2436
- name: "isentinel/flawless/markdown-code",
2437
- files: [GLOB_MARKDOWN_CODE],
2438
- rules: markdownCodeRules(markdownCode)
2439
- }, ...markdownCodeOverrides.map(({ excludeFiles, files, widths }, index) => {
2440
- return {
2441
- name: `isentinel/flawless/markdown-code/override-${index}`,
2442
- files: files.map((glob) => `${glob}/${GLOB_SRC}`),
2443
- ignores: excludeFiles.map((glob) => `${glob}/**`),
2444
- rules: markdownCodeRules({
2445
- ...markdownCode,
2446
- ...widths
2447
- })
2448
- };
2449
- })] : [],
2450
2400
  ...complementIgnores ? [{
2451
2401
  name: "isentinel/flawless/complement",
2452
2402
  files: [GLOB_SRC],
@@ -3915,6 +3865,53 @@ function resolveFormattedLanguages(formatters = true) {
3915
3865
  };
3916
3866
  }
3917
3867
  //#endregion
3868
+ //#region src/eslint/oxfmt-markdown-code.ts
3869
+ /** As `GLOB_MARKDOWN`, case-sensitive. */
3870
+ const MARKDOWN_FILE = /\.md$/;
3871
+ /**
3872
+ * Wrap eslint-plugin-oxfmt so `oxfmt/oxfmt` formats Markdown code blocks.
3873
+ *
3874
+ * The rule skips every virtual file, so a code block is formatted only by
3875
+ * the formatter of the file it is in. Oxfmt formats Markdown code blocks at
3876
+ * the prose width, so the preset turns that off and lets the rule format each
3877
+ * block with the options of its own language. Virtual files of other
3878
+ * processors (Vue blocks, say) stay skipped: oxfmt formats those files whole.
3879
+ *
3880
+ * @param plugin - The loaded plugin.
3881
+ * @returns The plugin with the wrapped rule.
3882
+ */
3883
+ function oxfmtMarkdownCode(plugin) {
3884
+ const { rules } = plugin;
3885
+ const rule = rules?.["oxfmt"];
3886
+ if (rule === void 0) return plugin;
3887
+ return {
3888
+ ...plugin,
3889
+ rules: {
3890
+ ...rules,
3891
+ oxfmt: markdownCodeRule(rule)
3892
+ }
3893
+ };
3894
+ }
3895
+ /**
3896
+ * Whether a file is a code block of a Markdown file.
3897
+ *
3898
+ * @param context - The rule context.
3899
+ * @returns Whether the file is a Markdown code block.
3900
+ */
3901
+ function isMarkdownCodeBlock({ filename, physicalFilename }) {
3902
+ return filename !== physicalFilename && MARKDOWN_FILE.test(physicalFilename);
3903
+ }
3904
+ function markdownCodeRule(rule) {
3905
+ return {
3906
+ ...rule,
3907
+ create(context) {
3908
+ if (!isMarkdownCodeBlock(context)) return rule.create(context);
3909
+ const proxy = Object.create(context, { physicalFilename: { value: context.filename } });
3910
+ return rule.create(proxy);
3911
+ }
3912
+ };
3913
+ }
3914
+ //#endregion
3918
3915
  //#region src/eslint/oxfmt-single-fix.ts
3919
3916
  /** Records the edit a fix makes, instead of making it. */
3920
3917
  const recordingFixer = { replaceTextRange(range, text) {
@@ -4028,7 +4025,7 @@ const defaultPluginRenaming = {
4028
4025
  //#region src/eslint/configs/oxfmt.ts
4029
4026
  init_globs();
4030
4027
  async function oxfmt(options) {
4031
- const { componentExts: componentExtensions = [], files: oxfmtFiles, formatters = {}, oxfmtConfigOptions = {}, oxfmtOptions: userOxfmtOptions, oxlint: enableOxlint = false, prettierOptions = {}, standalone = false } = options ?? {};
4028
+ const { componentExts: componentExtensions = [], files: oxfmtFiles, formatters = {}, oxfmtConfigOptions = {}, oxfmtIgnorePatterns = [], oxfmtOptions: userOxfmtOptions, oxlint: enableOxlint = false, prettierOptions = {}, standalone = false } = options ?? {};
4032
4029
  const languages = resolveFormattedLanguages(formatters);
4033
4030
  const oxfmtOptions = buildOxfmtOptions({
4034
4031
  oxfmtConfigOptions,
@@ -4049,7 +4046,28 @@ async function oxfmt(options) {
4049
4046
  disableNestedConfig: true
4050
4047
  }] };
4051
4048
  }
4052
- const pluginOxfmt = lazyPlugin("eslint-plugin-oxfmt", oxfmtSingleFix);
4049
+ /**
4050
+ * The `oxfmt/oxfmt` entry for Markdown code blocks, in every mode: oxfmt
4051
+ * leaves them alone, and oxlint cannot read Markdown. A code block takes
4052
+ * the root options of its language only: the rule does not load the
4053
+ * config, so its overrides and the `.editorconfig` sections, which match
4054
+ * by path, do not reach the block. The config's ignore patterns still do.
4055
+ *
4056
+ * @param layer - Options applied over the base options.
4057
+ * @returns The rule entry.
4058
+ */
4059
+ function codeBlockRule(layer = {}) {
4060
+ return { "oxfmt/oxfmt": ["error", {
4061
+ ...oxfmtOptions,
4062
+ ...layer,
4063
+ ignorePatterns: oxfmtIgnorePatterns,
4064
+ overrides: [],
4065
+ useConfig: false
4066
+ }] };
4067
+ }
4068
+ const pluginOxfmt = lazyPlugin("eslint-plugin-oxfmt", (plugin) => {
4069
+ return oxfmtSingleFix(oxfmtMarkdownCode(plugin));
4070
+ });
4053
4071
  const configPrettier = await interopDefault(import("eslint-config-prettier/flat"));
4054
4072
  const rulesToIgnore = ["curly", "style/quotes"];
4055
4073
  const rules = renameRules(configPrettier.rules, defaultPluginRenaming);
@@ -4091,9 +4109,80 @@ async function oxfmt(options) {
4091
4109
  files: [...FORMATTED_FILES.yaml],
4092
4110
  rules: formatRule(YAML_OXFMT_OPTIONS)
4093
4111
  });
4112
+ const codeBlocks = markdownCodeBlocks(languages, componentExtensions).map(([name, files, layer]) => {
4113
+ return {
4114
+ name: `isentinel/oxfmt/markdown-code/${name}`,
4115
+ files: files.map((glob) => `${GLOB_MARKDOWN}/${glob}`),
4116
+ rules: codeBlockRule(layer)
4117
+ };
4118
+ });
4119
+ if (languages.markdown) configs.push(...codeBlocks);
4120
+ else configs.push({
4121
+ name: "isentinel/oxfmt/markdown-code",
4122
+ files: codeBlocks.flatMap((block) => block.files),
4123
+ rules: { "oxfmt/oxfmt": "off" }
4124
+ });
4094
4125
  return configs;
4095
4126
  }
4096
4127
  /**
4128
+ * The code-block languages to format, with the options each one uses: the
4129
+ * same as a file of that language. Markdown blocks are left out: a config
4130
+ * that matches their virtual `*.md/*.md` files makes ESLint fail.
4131
+ *
4132
+ * @param languages - Which languages are formatted.
4133
+ * @param componentExtensions - Component extensions formatted as TypeScript.
4134
+ * @returns The code-block languages.
4135
+ */
4136
+ function markdownCodeBlocks(languages, componentExtensions) {
4137
+ const blocks = [[
4138
+ "javascript",
4139
+ [...FORMATTED_FILES.javascript],
4140
+ {}
4141
+ ], [
4142
+ "typescript",
4143
+ [...FORMATTED_FILES.typescript, ...componentExtensions.map((extension) => `**/*.${extension}`)],
4144
+ {}
4145
+ ]];
4146
+ if (languages.css) blocks.push([
4147
+ "css",
4148
+ [...FORMATTED_FILES.css],
4149
+ {}
4150
+ ], [
4151
+ "scss",
4152
+ [...FORMATTED_FILES.scss],
4153
+ {}
4154
+ ], [
4155
+ "less",
4156
+ [...FORMATTED_FILES.less],
4157
+ {}
4158
+ ]);
4159
+ if (languages.html) blocks.push([
4160
+ "html",
4161
+ [...FORMATTED_FILES.html],
4162
+ {}
4163
+ ]);
4164
+ if (languages.graphql) blocks.push([
4165
+ "graphql",
4166
+ [...FORMATTED_FILES.graphql],
4167
+ {}
4168
+ ]);
4169
+ if (languages.json) blocks.push([
4170
+ "json",
4171
+ [...FORMATTED_FILES.json],
4172
+ {}
4173
+ ], [
4174
+ "json5",
4175
+ [GLOB_JSON5],
4176
+ JSON5_OXFMT_OPTIONS
4177
+ ]);
4178
+ if (languages.yaml) blocks.push([
4179
+ "yaml",
4180
+ [...FORMATTED_FILES.yaml],
4181
+ YAML_OXFMT_OPTIONS
4182
+ ]);
4183
+ return blocks;
4184
+ }
4185
+ /**
4097
4186
  * A block for a language ESLint has no parser for: the plain parser lets
4098
4187
  * `oxfmt/oxfmt` see the file, and keeps ESLint from parsing Markdown code
4099
4188
  * blocks in it as JavaScript.
@@ -20858,19 +20947,10 @@ async function isentinel(options, ...userConfigs) {
20858
20947
  config: enableGitignore,
20859
20948
  explicit: "gitignore" in options
20860
20949
  }));
20861
- const markdownCodeWidths = formatter === "standalone" || formatters !== false && shouldEnableFeature(formatters, "markdown") ? {
20862
- markdownCode: markdownOxfmtOptions(prettierSettings["jsdocPrintWidth"]),
20863
- markdownCodeOverrides: [
20864
- ...markdownOverridesOf(sectionOverrides),
20865
- ...oxfmtConfig.markdownOverrides ?? [],
20866
- ...markdownOverridesOf(prettierSettings["overrides"])
20867
- ]
20868
- } : {};
20869
20950
  configs.push(smallRules({
20870
20951
  isInEditor,
20871
20952
  stylistic: stylisticOptions
20872
20953
  }), comments({
20873
- ...markdownCodeWidths,
20874
20954
  prettierOptions: widthSettings,
20875
20955
  stylistic: stylisticOptions
20876
20956
  }), flawless({
@@ -20878,7 +20958,6 @@ async function isentinel(options, ...userConfigs) {
20878
20958
  ..."tsconfigPath" in typescriptOptions ? { tsconfigPath: typescriptOptions.tsconfigPath } : {},
20879
20959
  ..."typeAware" in typescriptOptions ? { typeAware: typescriptOptions.typeAware } : {},
20880
20960
  antiSlop,
20881
- ...markdownCodeWidths,
20882
20961
  roblox: enableRoblox,
20883
20962
  stylistic: stylisticOptions
20884
20963
  }, widthSettings), ignores(options.ignores), imports({
@@ -21033,6 +21112,7 @@ async function isentinel(options, ...userConfigs) {
21033
21112
  yaml: false
21034
21113
  } : formatters,
21035
21114
  oxfmtConfigOptions,
21115
+ oxfmtIgnorePatterns: oxfmtConfig.ignorePatterns,
21036
21116
  oxfmtOptions: formatterOptions.oxfmtOptions,
21037
21117
  oxlint: oxlintMode === "full",
21038
21118
  prettierOptions: prettierSettings,
package/dist/lint-cli.mjs CHANGED
@@ -42,7 +42,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
42
42
  }) : target, mod));
43
43
  //#endregion
44
44
  //#region package.json
45
- var version = "6.2.0";
45
+ var version = "6.3.0";
46
46
  //#endregion
47
47
  //#region src/lint-cli/lib/cli/types.ts
48
48
  /**
package/dist/oxfmt.mjs CHANGED
@@ -315,15 +315,15 @@ function parseJsonLike(contents) {
315
315
  return;
316
316
  }
317
317
  }
318
- function isRecord(value) {
318
+ function isRecord$1(value) {
319
319
  return typeof value === "object" && value !== null && !Array.isArray(value);
320
320
  }
321
321
  function readPackageJsonPrettierKey(filePath) {
322
322
  const contents = readFileOrUndefined(filePath);
323
323
  if (contents === void 0) return;
324
324
  const parsed = parseJsonLike(contents);
325
- const prettierKey = isRecord(parsed) ? parsed["prettier"] : void 0;
326
- return isRecord(prettierKey) ? prettierKey : void 0;
325
+ const prettierKey = isRecord$1(parsed) ? parsed["prettier"] : void 0;
326
+ return isRecord$1(prettierKey) ? prettierKey : void 0;
327
327
  }
328
328
  /**
329
329
  * Load a JS Prettier config. Node's `require` handles ESM entry points
@@ -336,8 +336,8 @@ function readPackageJsonPrettierKey(filePath) {
336
336
  function requireConfigModule(filePath) {
337
337
  try {
338
338
  const loaded = createRequire(filePath)(filePath);
339
- const value = isRecord(loaded) && "default" in loaded ? loaded["default"] : loaded;
340
- return isRecord(value) ? value : void 0;
339
+ const value = isRecord$1(loaded) && "default" in loaded ? loaded["default"] : loaded;
340
+ return isRecord$1(value) ? value : void 0;
341
341
  } catch {
342
342
  return;
343
343
  }
@@ -355,7 +355,7 @@ function readConfigFile(filePath) {
355
355
  const contents = readFileOrUndefined(filePath);
356
356
  if (contents === void 0) return;
357
357
  const parsed = parseJsonLike(contents) ?? parseYamlLike(contents);
358
- return isRecord(parsed) ? parsed : void 0;
358
+ return isRecord$1(parsed) ? parsed : void 0;
359
359
  }
360
360
  function ancestorDirectories(cwd) {
361
361
  const directories = [];
@@ -506,6 +506,23 @@ function editorConfigToOxfmt(properties) {
506
506
  return options;
507
507
  }
508
508
  //#endregion
509
+ //#region src/guards.ts
510
+ /**
511
+ * Internal runtime type guards. Prefer these over `as` assertions so values
512
+ * crossing untyped boundaries (`JSON.parse`, dynamic `import`, plugin objects)
513
+ * are validated at runtime rather than asserted away.
514
+ */
515
+ /**
516
+ * Whether a value is a non-null, non-array object usable as a string-keyed
517
+ * record. Narrows `unknown` without an assertion.
518
+ *
519
+ * @param value - The value to test.
520
+ * @returns Whether the value is a plain object.
521
+ */
522
+ function isRecord(value) {
523
+ return typeof value === "object" && value !== null && !Array.isArray(value);
524
+ }
525
+ //#endregion
509
526
  //#region src/rules/oxfmt.ts
510
527
  const UNSUPPORTED_PRETTIER_KEYS = /* @__PURE__ */ new Set([
511
528
  "experimentalOperatorPosition",
@@ -572,12 +589,12 @@ function migratePrettierOptions(prettierOptions) {
572
589
  function migratePrettierOverrides(overrides) {
573
590
  if (!Array.isArray(overrides)) return [];
574
591
  return overrides.flatMap((override) => {
575
- if (typeof override !== "object" || override === null) return [];
592
+ if (!isRecord(override)) return [];
576
593
  const { excludeFiles, files, options } = override;
577
594
  return [{
578
595
  ...excludeFiles === void 0 ? {} : { excludeFiles: stringsOf(excludeFiles) },
579
596
  files: stringsOf(files),
580
- options: typeof options === "object" && options !== null ? migratePrettierOptions(options) : {}
597
+ options: isRecord(options) ? migratePrettierOptions(options) : {}
581
598
  }];
582
599
  });
583
600
  }
@@ -592,17 +609,11 @@ function migratePrettierOverrides(overrides) {
592
609
  * @returns The overrides with anchored globs.
593
610
  */
594
611
  function anchorOverrideGlobs(overrides) {
595
- function anchor(glob) {
596
- const negated = glob.startsWith("!");
597
- const body = negated ? glob.slice(1) : glob;
598
- const anchored = body.includes("/") ? body : `./${body}`;
599
- return negated ? `!${anchored}` : anchored;
600
- }
601
612
  return overrides.map((override) => {
602
613
  return {
603
614
  ...override,
604
- ...override.excludeFiles ? { excludeFiles: override.excludeFiles.map(anchor) } : {},
605
- files: override.files.map(anchor)
615
+ ...override.excludeFiles ? { excludeFiles: override.excludeFiles.map(anchorGlob) } : {},
616
+ files: override.files.map(anchorGlob)
606
617
  };
607
618
  });
608
619
  }
@@ -627,11 +638,16 @@ function buildOxfmtOptions({ oxfmtConfigOptions = {}, oxfmtOptions, prettierOpti
627
638
  * ESLint config and the standalone oxfmt config so both format Markdown the
628
639
  * same way.
629
640
  *
641
+ * Oxfmt would format code blocks with these options too, at the prose width.
642
+ * It leaves them alone instead, and ESLint formats each block with the options
643
+ * of its own language (see `isentinel/oxfmt/markdown-code/*`).
644
+ *
630
645
  * @param jsdocPrintWidth - Width for Markdown prose; 80 when unset.
631
646
  * @returns Options applied on top of the base for Markdown files.
632
647
  */
633
648
  function markdownOxfmtOptions(jsdocPrintWidth) {
634
649
  return {
650
+ embeddedLanguageFormatting: "off",
635
651
  printWidth: Number(jsdocPrintWidth) || 80,
636
652
  proseWrap: "always",
637
653
  tabWidth: 2
@@ -651,6 +667,18 @@ const YAML_OXFMT_OPTIONS = {
651
667
  tabWidth: 2,
652
668
  useTabs: false
653
669
  };
670
+ /**
671
+ * Anchor one slash-less glob to the config directory, keeping its negation.
672
+ *
673
+ * @param glob - An override glob.
674
+ * @returns The anchored glob.
675
+ */
676
+ function anchorGlob(glob) {
677
+ const negated = glob.startsWith("!");
678
+ const body = negated ? glob.slice(1) : glob;
679
+ const anchored = body.includes("/") ? body : `./${body}`;
680
+ return negated ? `!${anchored}` : anchored;
681
+ }
654
682
  //#endregion
655
683
  //#region src/oxfmt/index.ts
656
684
  /**
package/dist/oxlint.mjs CHANGED
@@ -5333,6 +5333,15 @@ const GLOB_EXCLUDE = [
5333
5333
  function isRecord$1(value) {
5334
5334
  return typeof value === "object" && value !== null && !Array.isArray(value);
5335
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
+ }
5336
5345
  //#endregion
5337
5346
  //#region src/prettier-config.ts
5338
5347
  /**
@@ -5768,12 +5777,12 @@ function migratePrettierOptions(prettierOptions) {
5768
5777
  function migratePrettierOverrides(overrides) {
5769
5778
  if (!Array.isArray(overrides)) return [];
5770
5779
  return overrides.flatMap((override) => {
5771
- if (typeof override !== "object" || override === null) return [];
5780
+ if (!isRecord$1(override)) return [];
5772
5781
  const { excludeFiles, files, options } = override;
5773
5782
  return [{
5774
5783
  ...excludeFiles === void 0 ? {} : { excludeFiles: stringsOf(excludeFiles) },
5775
5784
  files: stringsOf(files),
5776
- options: typeof options === "object" && options !== null ? migratePrettierOptions(options) : {}
5785
+ options: isRecord$1(options) ? migratePrettierOptions(options) : {}
5777
5786
  }];
5778
5787
  });
5779
5788
  }
@@ -6261,8 +6270,6 @@ const SCRIPT_EXTENSIONS = [
6261
6270
  "ts",
6262
6271
  "tsx"
6263
6272
  ];
6264
- /** The extensions of the Markdown files ESLint lints code blocks in. */
6265
- const MARKDOWN_EXTENSIONS = ["md"];
6266
6273
  const LEADING_DOT_SLASH = /^\.\//;
6267
6274
  /** Set once a process has warned about a JS/TS width override. */
6268
6275
  const WARNED_SCRIPT_OVERRIDE = Symbol.for("@isentinel/eslint-config/oxfmt-override-warned");
@@ -6302,42 +6309,9 @@ function findOxfmtConfig(cwd = process.cwd()) {
6302
6309
  }
6303
6310
  }
6304
6311
  /**
6305
- * The overrides that set `printWidth` or `tabWidth` for Markdown files.
6306
- *
6307
- * @param overrides - The config's `overrides`.
6308
- * @returns Each such override's scope and widths, in order.
6309
- */
6310
- function markdownOverridesOf(overrides) {
6311
- if (!Array.isArray(overrides)) return [];
6312
- return overrides.flatMap((override) => {
6313
- if (!isRecord$1(override) || !overrideMatches(override, MARKDOWN_EXTENSIONS)) return [];
6314
- const widths = widthsOf(override["options"]);
6315
- return Object.keys(widths).length > 0 ? [{
6316
- ...scopeOf(override),
6317
- widths
6318
- }] : [];
6319
- });
6320
- }
6321
- /**
6322
- * Where an override applies: its `files`, minus its `excludeFiles` and any
6323
- * negated glob in `files`.
6324
- *
6325
- * @param override - One override.
6326
- * @returns Its scope.
6327
- */
6328
- function scopeOf(override) {
6329
- const strings = (value) => {
6330
- return [value].flat().filter((glob) => typeof glob === "string");
6331
- };
6332
- const globs = strings(override["files"]);
6333
- return {
6334
- excludeFiles: [...strings(override["excludeFiles"]), ...globs.filter((glob) => glob.startsWith("!")).map((glob) => glob.slice(1))],
6335
- files: globs.filter((glob) => !glob.startsWith("!"))
6336
- };
6337
- }
6338
- /**
6339
6312
  * Warn, at most once per process, about factory options that do not reach
6340
- * standalone oxfmt: it formats with `oxfmt.config.ts` alone.
6313
+ * standalone oxfmt: it formats with `oxfmt.config.ts` alone. They still choose
6314
+ * which Markdown code blocks ESLint formats.
6341
6315
  *
6342
6316
  * @param options - The factory options.
6343
6317
  * @param options.componentExts - Oxfmt formats only the extensions
@@ -6358,7 +6332,7 @@ function warnStandaloneOptions({ componentExts, formatters, gitignore, ignores }
6358
6332
  ].filter((name) => name !== void 0);
6359
6333
  if (names.length > 0) {
6360
6334
  state[WARNED_STANDALONE] = true;
6361
- console.warn(`[@isentinel/eslint-config] With formatter: "standalone", oxfmt formats with oxfmt.config.ts alone, so these options do not change formatting: ${names.join(", ")}. Set the equivalent in isentinelOxfmt() (formatters, componentExts, ignorePatterns, format options).`);
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).`);
6362
6336
  }
6363
6337
  }
6364
6338
  /**
@@ -6471,26 +6445,26 @@ function mirrorsSection(override, sections) {
6471
6445
  });
6472
6446
  }
6473
6447
  /**
6474
- * Turn a loaded oxfmt config into `oxfmt/oxfmt` rule options and the Markdown
6475
- * widths. The rule takes format options only, so `$schema`, `ignorePatterns`
6476
- * and `overrides` are dropped from the options.
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.
6477
6451
  *
6478
6452
  * @param config - The loaded config.
6479
6453
  * @param config.$schema - Dropped.
6480
- * @param config.ignorePatterns - Dropped; oxfmt applies them itself.
6481
- * @param config.overrides - Dropped, after the width check; read for the
6482
- * Markdown widths.
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.
6483
6457
  * @param configPath - The config file, for the warning.
6484
- * @returns The format options and Markdown widths.
6458
+ * @returns The format options, ignore patterns and overrides.
6485
6459
  */
6486
- function toResolvedConfig({ $schema: _schema, ignorePatterns: _ignorePatterns, overrides, ...options }, configPath) {
6460
+ function toResolvedConfig({ $schema: _schema, ignorePatterns, overrides, ...options }, configPath) {
6487
6461
  const sections = editorConfigOverrides(path.dirname(configPath));
6488
6462
  const list = Array.isArray(overrides) ? overrides : [];
6489
6463
  const copied = list.filter((override) => mirrorsSection(override, sections));
6490
6464
  warnScriptWidthOverrides(copied, ".editorconfig");
6491
6465
  warnScriptWidthOverrides(list.filter((override) => !copied.includes(override)), path.basename(configPath));
6492
6466
  return {
6493
- markdownOverrides: markdownOverridesOf(overrides),
6467
+ ignorePatterns: isStringArray(ignorePatterns) ? ignorePatterns : [],
6494
6468
  options,
6495
6469
  overrides: Array.isArray(overrides) ? overrides : []
6496
6470
  };
@@ -6510,20 +6484,6 @@ function rcConfig(configPath, content) {
6510
6484
  return toResolvedConfig(parsed, configPath);
6511
6485
  }
6512
6486
  /**
6513
- * Pick `printWidth` and `tabWidth` out of a set of format options.
6514
- *
6515
- * @param options - Format options, of unknown shape.
6516
- * @returns The widths it sets.
6517
- */
6518
- function widthsOf(options) {
6519
- if (!isRecord$1(options)) return {};
6520
- const { printWidth, tabWidth } = options;
6521
- return {
6522
- ...typeof printWidth === "number" ? { printWidth } : {},
6523
- ...typeof tabWidth === "number" ? { tabWidth } : {}
6524
- };
6525
- }
6526
- /**
6527
6487
  * Resolves the lowest Node major version a semver range can match.
6528
6488
  *
6529
6489
  * @param range - A semver range such as `>=24.12.0` or `^24 || ^26`.
@@ -7420,24 +7380,6 @@ function oxlintEslintPlugin(options = {}) {
7420
7380
  //#endregion
7421
7381
  //#region src/rules/flawless.ts
7422
7382
  /**
7423
- * Arrow return style rule on its own, for the Markdown code block override
7424
- * where only the line width differs.
7425
- *
7426
- * @param options - Shared rule options.
7427
- * @returns The rule map.
7428
- */
7429
- function arrowStyleRules({ maxLen, maxLength, printWidth, tabWidth } = {}) {
7430
- return { "flawless/arrow-return-style": ["error", {
7431
- jsxAlwaysUseExplicitReturn: true,
7432
- maxLen: maxLength ?? maxLen ?? printWidth ?? 100,
7433
- maxObjectProperties: 2,
7434
- namedExportsAlwaysUseExplicitReturn: true,
7435
- objectReturnStyle: "complex-explicit",
7436
- ...tabWidth !== void 0 ? { tabWidth } : {},
7437
- ...printWidth !== void 0 ? { useOxfmt: { printWidth } } : {}
7438
- }] };
7439
- }
7440
- /**
7441
7383
  * Base (non-React) flawless rules shared between the ESLint and oxlint
7442
7384
  * factories. The React flawless rules live in the react rule map; the
7443
7385
  * type-aware rules live in {@link flawlessTypeAwareRules}, plus the type-aware
@@ -7483,6 +7425,23 @@ function antiSlopRules({ roblox = true }) {
7483
7425
  ...roblox ? {} : { "flawless/no-reflect-get": "error" }
7484
7426
  };
7485
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
+ }
7486
7445
  //#endregion
7487
7446
  //#region src/oxlint/configs/flawless.ts
7488
7447
  function oxlintFlawless({ antiSlop = false, excludeFiles, roblox = true, stylistic = true } = {}, prettierOptions = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isentinel/eslint-config",
3
- "version": "6.2.0",
3
+ "version": "6.3.0",
4
4
  "description": "iSentinel's ESLint config",
5
5
  "keywords": [
6
6
  "eslint-config",
@@ -149,7 +149,7 @@
149
149
  "typescript": "6.0.3",
150
150
  "unplugin-unused": "0.5.7",
151
151
  "vitest": "4.1.10",
152
- "@isentinel/eslint-config": "6.2.0"
152
+ "@isentinel/eslint-config": "6.3.0"
153
153
  },
154
154
  "peerDependencies": {
155
155
  "@vitest/eslint-plugin": "^1.6.4",