@isentinel/eslint-config 6.0.0-beta.13 → 6.0.0-beta.14

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/dist/cli.mjs CHANGED
@@ -9,7 +9,7 @@ import path from "node:path";
9
9
  import { existsSync, readFileSync } from "fs";
10
10
  import { execSync } from "node:child_process";
11
11
  //#region package.json
12
- var version = "6.0.0-beta.13";
12
+ var version = "6.0.0-beta.14";
13
13
  var package_default = {
14
14
  name: "@isentinel/eslint-config",
15
15
  version,
package/dist/index.d.mts CHANGED
@@ -26244,7 +26244,7 @@ type Yoda = [] | [("always" | "never")] | [("always" | "never"), {
26244
26244
  onlyEquality?: boolean;
26245
26245
  }];
26246
26246
  // Names of all the configs
26247
- 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/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/promise' | 'isentinel/react/setup' | 'isentinel/react/setup/naming' | '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/vitest/setup' | 'isentinel/test/vitest/rules' | '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';
26247
+ 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/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/promise' | 'isentinel/react/setup' | 'isentinel/react/setup/naming' | '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';
26248
26248
  //#endregion
26249
26249
  //#region src/utils.d.ts
26250
26250
  type ExtractRuleOptions<T> = T extends Linter.RuleEntry<infer U> ? U : never;
@@ -26761,9 +26761,14 @@ interface OptionsConfig extends OptionsComponentExtensions, OptionsProjectType {
26761
26761
  * Requires installing the optional peer dependencies `oxlint` and
26762
26762
  * `oxlint-tsgolint`.
26763
26763
  *
26764
+ * `"native"` narrows the hand-off to the rules oxlint implements in Rust
26765
+ * (native rules plus the tsgolint type-aware ones): rules that would run in
26766
+ * oxlint as jsPlugins — and formatting — stay in ESLint. Pair it with
26767
+ * `jsPlugins: false` in the oxlint factory, or both engines run them.
26768
+ *
26764
26769
  * @default false
26765
26770
  */
26766
- oxlint?: boolean;
26771
+ oxlint?: "native" | boolean;
26767
26772
  /**
26768
26773
  * Warn at config-build time when a user config references a rule that
26769
26774
  * oxlint owns in hybrid mode (`oxlint: true`), where the ESLint entry has
@@ -27094,6 +27099,11 @@ declare const GLOB_MISE: Array<string>;
27094
27099
  declare const GLOB_HTML = "**/*.htm{,l}";
27095
27100
  declare const GLOB_XML = "**/*.xml";
27096
27101
  declare const GLOB_GRAPHQL = "**/*.{g,graph}ql";
27102
+ /**
27103
+ * Type tests (`expectTypeOf` / `assertType`), run by Vitest's `typecheck`
27104
+ * mode. They are test files, but assert nothing at runtime.
27105
+ */
27106
+ declare const GLOB_TYPE_TESTS = "**/*.test-d.{,c,m}[jt]s{,x}";
27097
27107
  declare const GLOB_TESTS: Array<string>;
27098
27108
  declare const GLOB_BUILD_TOOLS: Array<string>;
27099
27109
  declare const GLOB_ALL_SRC: string[];
@@ -27282,4 +27292,4 @@ declare function unicorn({ complementIgnores, nameReplacements, roblox, root: cu
27282
27292
  //#region src/eslint/configs/yaml.d.ts
27283
27293
  declare function yaml({ files, overrides, stylistic }?: OptionsFiles & OptionsOverrides & OptionsStylistic): Promise<Array<TypedFlatConfigItem>>;
27284
27294
  //#endregion
27285
- export { Awaitable, type ConfigNames, type FlatConfigComposer, GLOB_ALL_JSON, GLOB_ALL_SRC, GLOB_BUILD_TOOLS, GLOB_CSS, GLOB_DTS, GLOB_EXCLUDE, GLOB_GRAPHQL, GLOB_HTML, GLOB_JS, GLOB_JSON, GLOB_JSON5, GLOB_JSONC, GLOB_JSX, GLOB_LESS, GLOB_LINTABLE_EXTENSIONS, GLOB_LUA, GLOB_MARKDOWN, GLOB_MARKDOWN_BLOCKS, GLOB_MARKDOWN_CODE, GLOB_MARKDOWN_IN_MARKDOWN, GLOB_MISE, GLOB_PACKAGE_JSON, GLOB_PACKAGE_JSON_ROOT, GLOB_POSTCSS, GLOB_ROOT, GLOB_ROOT_SRC, GLOB_SCSS, GLOB_SRC, GLOB_SRC_EXT, GLOB_SRC_EXTENSIONS, GLOB_STYLE, GLOB_TESTS, GLOB_TOML, GLOB_TS, GLOB_TSCONFIG, GLOB_TSX, GLOB_XML, GLOB_YAML, GitignoreOptions, JsdocOptions, NamedFlatConfigItem, NamedOptionsConfig, OptionsComponentExtensions, OptionsConfig, OptionsE18e, OptionsFiles, OptionsFilesTypeAware, OptionsFormatters, OptionsHasRoblox, OptionsHasTypeScript, OptionsIsInEditor, OptionsJest, OptionsOverrides, OptionsOverridesTypeAware, OptionsPnpm, OptionsProjectType, OptionsStylistic, OptionsTestFramework, OptionsTypeScriptErasableOnly, OptionsTypeScriptParserOptions, OptionsTypeScriptWithTypes, OptionsTypescript, OptionsUnicorn, OptionsVitest, type OxfmtOptions, PerfectionistConfig, type PrettierOptions, ReactConfig, Rules, SpellCheckConfig, StylisticConfig, StylisticConfigDefaults, TypedFlatConfigItem, comments, isentinel as default, isentinel, defaultPluginRenaming, disables, e18e, eslintPlugin, flawless, gitignore, ignores$1 as ignores, imports, isInAgentSession, isInEditorEnvironment, isInGitHooksOrLintStaged, javascript, jsdoc, jsonc, loadSmallRulesPlugin, markdown, naming, node, oxfmt, packageJson, perfectionist, pnpm, promise, react, roblox$1 as roblox, smallRules, sonarjs, sortCspell, sortGithubAction, sortMiseToml, sortPnpmWorkspace, sortRojoProject, sortTsconfig, spelling, stylistic$1 as stylistic, test, toml, typescript, unicorn, yaml };
27295
+ export { Awaitable, type ConfigNames, type FlatConfigComposer, GLOB_ALL_JSON, GLOB_ALL_SRC, GLOB_BUILD_TOOLS, GLOB_CSS, GLOB_DTS, GLOB_EXCLUDE, GLOB_GRAPHQL, GLOB_HTML, GLOB_JS, GLOB_JSON, GLOB_JSON5, GLOB_JSONC, GLOB_JSX, GLOB_LESS, GLOB_LINTABLE_EXTENSIONS, GLOB_LUA, GLOB_MARKDOWN, GLOB_MARKDOWN_BLOCKS, GLOB_MARKDOWN_CODE, GLOB_MARKDOWN_IN_MARKDOWN, GLOB_MISE, GLOB_PACKAGE_JSON, GLOB_PACKAGE_JSON_ROOT, GLOB_POSTCSS, GLOB_ROOT, GLOB_ROOT_SRC, GLOB_SCSS, GLOB_SRC, GLOB_SRC_EXT, GLOB_SRC_EXTENSIONS, GLOB_STYLE, GLOB_TESTS, GLOB_TOML, GLOB_TS, GLOB_TSCONFIG, GLOB_TSX, GLOB_TYPE_TESTS, GLOB_XML, GLOB_YAML, GitignoreOptions, JsdocOptions, NamedFlatConfigItem, NamedOptionsConfig, OptionsComponentExtensions, OptionsConfig, OptionsE18e, OptionsFiles, OptionsFilesTypeAware, OptionsFormatters, OptionsHasRoblox, OptionsHasTypeScript, OptionsIsInEditor, OptionsJest, OptionsOverrides, OptionsOverridesTypeAware, OptionsPnpm, OptionsProjectType, OptionsStylistic, OptionsTestFramework, OptionsTypeScriptErasableOnly, OptionsTypeScriptParserOptions, OptionsTypeScriptWithTypes, OptionsTypescript, OptionsUnicorn, OptionsVitest, type OxfmtOptions, PerfectionistConfig, type PrettierOptions, ReactConfig, Rules, SpellCheckConfig, StylisticConfig, StylisticConfigDefaults, TypedFlatConfigItem, comments, isentinel as default, isentinel, defaultPluginRenaming, disables, e18e, eslintPlugin, flawless, gitignore, ignores$1 as ignores, imports, isInAgentSession, isInEditorEnvironment, isInGitHooksOrLintStaged, javascript, jsdoc, jsonc, loadSmallRulesPlugin, markdown, naming, node, oxfmt, packageJson, perfectionist, pnpm, promise, react, roblox$1 as roblox, smallRules, sonarjs, sortCspell, sortGithubAction, sortMiseToml, sortPnpmWorkspace, sortRojoProject, sortTsconfig, spelling, stylistic$1 as stylistic, test, toml, typescript, unicorn, yaml };
package/dist/index.mjs CHANGED
@@ -152,10 +152,16 @@ const GLOB_MISE = [
152
152
  const GLOB_HTML = "**/*.htm{,l}";
153
153
  const GLOB_XML = "**/*.xml";
154
154
  const GLOB_GRAPHQL = "**/*.{g,graph}ql";
155
+ /**
156
+ * Type tests (`expectTypeOf` / `assertType`), run by Vitest's `typecheck`
157
+ * mode. They are test files, but assert nothing at runtime.
158
+ */
159
+ const GLOB_TYPE_TESTS = `**/*.test-d.${GLOB_SRC_EXT}`;
155
160
  const GLOB_TESTS = [
156
161
  `**/__tests__/**/*.${GLOB_SRC_EXT}`,
157
162
  `**/*.spec.${GLOB_SRC_EXT}`,
158
163
  `**/*.test.${GLOB_SRC_EXT}`,
164
+ GLOB_TYPE_TESTS,
159
165
  `**/*.bench.${GLOB_SRC_EXT}`,
160
166
  `**/*.benchmark.${GLOB_SRC_EXT}`
161
167
  ];
@@ -4631,6 +4637,21 @@ async function stylistic(options = {}) {
4631
4637
  //#endregion
4632
4638
  //#region src/rules/test.ts
4633
4639
  /**
4640
+ * Rules that make no sense in a type test (`*.test-d.ts`): these files assert
4641
+ * with `expectTypeOf`/`assertType` at compile time, run no expectations, and
4642
+ * name their blocks after the types under test.
4643
+ *
4644
+ * @param prefix - The test-plugin prefix in use (`jest` or `vitest`).
4645
+ * @returns The rule map.
4646
+ */
4647
+ function typeTestRules(prefix) {
4648
+ return {
4649
+ [`${prefix}/expect-expect`]: "off",
4650
+ [`${prefix}/prefer-expect-assertions`]: "off",
4651
+ [`${prefix}/prefer-lowercase-title`]: "off"
4652
+ };
4653
+ }
4654
+ /**
4634
4655
  * Jest-extended rules shared between the ESLint and oxlint factories.
4635
4656
  *
4636
4657
  * @returns The rule map.
@@ -4859,6 +4880,10 @@ async function test({ files = GLOB_TESTS, isInEditor = false, jest = false, over
4859
4880
  globalPackage: "@rbxts/jest-globals",
4860
4881
  version: 27
4861
4882
  } }
4883
+ }, {
4884
+ name: "isentinel/test/jest/type-tests",
4885
+ files: [GLOB_TYPE_TESTS],
4886
+ rules: typeTestRules("jest")
4862
4887
  });
4863
4888
  }
4864
4889
  if (enableVitest) {
@@ -4896,6 +4921,10 @@ async function test({ files = GLOB_TESTS, isInEditor = false, jest = false, over
4896
4921
  ...useJestExtended ? { jest: { globalPackage: "vitest" } } : {},
4897
4922
  vitest: { typecheck: vitestOptions.typecheck ?? false }
4898
4923
  }
4924
+ }, {
4925
+ name: "isentinel/test/vitest/type-tests",
4926
+ files: [GLOB_TYPE_TESTS],
4927
+ rules: typeTestRules("vitest")
4899
4928
  });
4900
4929
  }
4901
4930
  return configs;
@@ -15378,6 +15407,17 @@ function isOxlintCovered(rule) {
15378
15407
  return rule in oxlintRuleMapping || rule in oxcCoveredRules;
15379
15408
  }
15380
15409
  /**
15410
+ * Whether the rule only runs in oxlint through a jsPlugin (the original ESLint
15411
+ * plugin loaded by oxlint), rather than natively in Rust. Native-only hybrid
15412
+ * mode (`oxlint: "native"`) leaves these rules in ESLint.
15413
+ *
15414
+ * @param rule - The canonical ESLint rule name.
15415
+ * @returns Whether the rule runs as an oxlint jsPlugin.
15416
+ */
15417
+ function isJsPluginRule(rule) {
15418
+ return oxlintRuleMapping[rule] === "js-plugin";
15419
+ }
15420
+ /**
15381
15421
  * Whether the rule runs via oxlint-tsgolint (`oxlint --type-aware`); such rules
15382
15422
  * are noise without type information, so they are gated on `typeAware`.
15383
15423
  *
@@ -15398,9 +15438,11 @@ const HYBRID_FORMATTING_RULES = /* @__PURE__ */ new Set(["oxfmt/oxfmt"]);
15398
15438
  * virtual files, so the preset re-enables those rules there.
15399
15439
  *
15400
15440
  * @param configs - The resolved flat config items.
15441
+ * @param mode - The hybrid mode; in `native` mode jsPlugin rules and formatting
15442
+ * stay in ESLint, so entries for them are live and not reported.
15401
15443
  */
15402
- function warnDeadMappedRules(configs) {
15403
- const references = findDeadMappedRules(configs);
15444
+ function warnDeadMappedRules(configs, mode) {
15445
+ const references = findDeadMappedRules(configs, mode);
15404
15446
  if (references.length === 0) return;
15405
15447
  const lines = references.map(({ config, rule }) => ` - "${rule}" in ${config}`);
15406
15448
  console.warn(`[@isentinel/eslint-config] These config entries reference rules that oxlint owns in hybrid mode, so they have no effect in ESLint. Move them to oxlint.config.ts (or your oxfmt options) or remove them:\n${lines.join("\n")}`);
@@ -15427,12 +15469,13 @@ function warnMissingTsgolint() {
15427
15469
  * @param typeAware - Whether oxlint runs type-aware (oxlint-tsgolint present).
15428
15470
  * When `false`, tsgolint rules are kept in ESLint so they do not vanish from
15429
15471
  * both engines.
15472
+ * @param mode - The hybrid mode; `native` keeps jsPlugin rules in ESLint.
15430
15473
  */
15431
- function dropOxlintCoveredRules(configs, typeAware = true) {
15474
+ function dropOxlintCoveredRules(configs, typeAware = true, mode = "full") {
15432
15475
  for (let index = 0; index < configs.length; index += 1) {
15433
15476
  const config = configs[index];
15434
15477
  if (config?.name === void 0 || !config.name.startsWith("isentinel/") || config.name.endsWith("/markdown-code") || config.rules === void 0 || targetsMarkdown(config.files)) continue;
15435
- const dropped = dropCoveredRulesFromConfig(config.rules, typeAware);
15478
+ const dropped = dropCoveredRulesFromConfig(config.rules, typeAware, mode);
15436
15479
  if (Object.keys(dropped).length === 0) continue;
15437
15480
  if ((config.ignores ?? []).some((pattern) => typeof pattern === "string" && pattern.includes(".md/"))) continue;
15438
15481
  configs.splice(index + 1, 0, {
@@ -15443,6 +15486,18 @@ function dropOxlintCoveredRules(configs, typeAware = true) {
15443
15486
  index += 1;
15444
15487
  }
15445
15488
  }
15489
+ /**
15490
+ * Whether oxlint owns the rule in the given hybrid mode (and therefore whether
15491
+ * ESLint must drop it).
15492
+ *
15493
+ * @param rule - The canonical ESLint rule name.
15494
+ * @param mode - The hybrid mode.
15495
+ * @returns Whether oxlint runs the rule.
15496
+ */
15497
+ function isOwnedByOxlint(rule, mode) {
15498
+ if (!isOxlintCovered(rule)) return false;
15499
+ return mode === "full" || !isJsPluginRule(rule);
15500
+ }
15446
15501
  function isPresetConfig(config) {
15447
15502
  return config.name?.startsWith("isentinel/") ?? false;
15448
15503
  }
@@ -15462,22 +15517,22 @@ function targetsMarkdown(files) {
15462
15517
  const patterns = files.flat();
15463
15518
  return patterns.length > 0 && patterns.every((pattern) => typeof pattern === "string" && pattern.includes(".md"));
15464
15519
  }
15465
- function findDeadMappedRules(configs) {
15520
+ function findDeadMappedRules(configs, mode) {
15466
15521
  const references = [];
15467
15522
  for (const config of configs) {
15468
15523
  if (config.rules === void 0 || isPresetConfig(config) || targetsMarkdown(config.files)) continue;
15469
15524
  const label = describeConfig(config);
15470
- for (const [rule, value] of Object.entries(config.rules)) if (value !== void 0 && (isOxlintCovered(rule) || HYBRID_FORMATTING_RULES.has(rule))) references.push({
15525
+ for (const [rule, value] of Object.entries(config.rules)) if (value !== void 0 && (isOwnedByOxlint(rule, mode) || mode === "full" && HYBRID_FORMATTING_RULES.has(rule))) references.push({
15471
15526
  config: label,
15472
15527
  rule
15473
15528
  });
15474
15529
  }
15475
15530
  return references;
15476
15531
  }
15477
- function dropCoveredRulesFromConfig(rules, typeAware) {
15532
+ function dropCoveredRulesFromConfig(rules, typeAware, mode) {
15478
15533
  const dropped = {};
15479
15534
  for (const [rule, value] of Object.entries(rules)) {
15480
- if (value === void 0 || !isOxlintCovered(rule) || !typeAware && isTsgolintRule(rule)) continue;
15535
+ if (value === void 0 || !isOwnedByOxlint(rule, mode) || !typeAware && isTsgolintRule(rule)) continue;
15481
15536
  const severity = Array.isArray(value) ? value[0] : value;
15482
15537
  if (severity === "off" || severity === 0) continue;
15483
15538
  dropped[rule] = value;
@@ -15662,6 +15717,10 @@ async function isentinel(options, ...userConfigs) {
15662
15717
  const { autoRenamePlugins = true, componentExts: componentExtensions = [], e18e: enableE18e = true, eslintPlugin: enableEslintPlugin = false, formatters, gitignore: enableGitignore = true, jsdoc: enableJsdoc = true, jsonc: enableJsonc = true, jsx: enableJsx = true, markdown: enableMarkdown = true, oxlint: enableOxlint = false, pnpm: enableCatalogs = findUpSync("pnpm-workspace.yaml") !== void 0, react: enableReact = false, root: customRootGlobs, spellCheck: enableSpellCheck, toml: enableToml = true, yaml: enableYaml = true } = options;
15663
15718
  const rootGlobs = mergeGlobs(GLOB_ROOT, customRootGlobs);
15664
15719
  const enableRoblox = options.roblox !== false;
15720
+ const oxlintMode = (() => {
15721
+ if (enableOxlint === false) return "off";
15722
+ return enableOxlint === "native" ? "native" : "full";
15723
+ })();
15665
15724
  const robloxScopedFiles = typeof options.roblox === "object" && options.roblox.files !== void 0 ? options.roblox.files.flat() : void 0;
15666
15725
  const hasComplement = !enableRoblox || robloxScopedFiles !== void 0;
15667
15726
  const needsComplementOverlay = enableRoblox && robloxScopedFiles !== void 0;
@@ -15808,33 +15867,33 @@ async function isentinel(options, ...userConfigs) {
15808
15867
  componentExts: componentExtensions,
15809
15868
  type: projectType
15810
15869
  }));
15811
- if (enableSpellCheck !== false && !typeAwareOnly) {
15870
+ if (enableSpellCheck !== false) {
15812
15871
  configs.push(spelling({
15813
15872
  ...resolveSubOptions(options, "spellCheck"),
15814
15873
  componentExts: componentExtensions,
15815
15874
  isInEditor
15816
15875
  }));
15817
- if (stylisticOptions !== false && enableYaml !== false) configs.push(sortCspell());
15876
+ if (stylisticOptions !== false && enableYaml !== false && !typeAwareOnly) configs.push(sortCspell());
15818
15877
  }
15819
- writeHybridStatusForCwd(enableOxlint);
15820
- if (enableOxlint) configs.push([{
15878
+ writeHybridStatusForCwd(oxlintMode !== "off");
15879
+ if (oxlintMode !== "off") configs.push([{
15821
15880
  name: "isentinel/oxlint-marker",
15822
15881
  settings: { "isentinel/oxlint": true }
15823
15882
  }]);
15824
15883
  configs.push(disables({ root: rootGlobs }));
15825
- if (stylisticOptions !== false && !typeAwareOnly) configs.push(oxfmt({
15884
+ if (stylisticOptions !== false) configs.push(oxfmt({
15826
15885
  componentExts: componentExtensions,
15827
- formatters: formatters !== false ? formatters : {
15886
+ formatters: typeAwareOnly || formatters === false ? {
15828
15887
  css: false,
15829
15888
  graphql: false,
15830
15889
  html: false,
15831
15890
  json: false,
15832
15891
  markdown: false,
15833
15892
  yaml: false
15834
- },
15893
+ } : formatters,
15835
15894
  oxfmtConfigOptions,
15836
15895
  oxfmtOptions: formatterOptions.oxfmtOptions,
15837
- oxlint: enableOxlint,
15896
+ oxlint: oxlintMode === "full",
15838
15897
  prettierOptions: prettierSettings
15839
15898
  }));
15840
15899
  if ("files" in options) throw new Error("[@isentinel/eslint-config] The first argument should not contain the \"files\" property as the options are supposed to be global. Place it in the second or later config instead.");
@@ -15849,12 +15908,12 @@ async function isentinel(options, ...userConfigs) {
15849
15908
  return [...previous, GLOB_MARKDOWN];
15850
15909
  });
15851
15910
  if (autoRenamePlugins) composer = composer.renamePlugins(defaultPluginRenaming);
15852
- if (enableOxlint) {
15911
+ if (oxlintMode !== "off") {
15853
15912
  const tsgolintAvailable = isPackageExists("oxlint-tsgolint");
15854
15913
  if (!tsgolintAvailable) warnMissingTsgolint();
15855
15914
  composer = composer.onResolved((resolved) => {
15856
- if (options.oxlintWarnDeadRules !== false) warnDeadMappedRules(resolved);
15857
- dropOxlintCoveredRules(resolved, tsgolintAvailable);
15915
+ if (options.oxlintWarnDeadRules !== false) warnDeadMappedRules(resolved, oxlintMode);
15916
+ dropOxlintCoveredRules(resolved, tsgolintAvailable, oxlintMode);
15858
15917
  });
15859
15918
  }
15860
15919
  if (typeAwareMode !== void 0) composer = composer.onResolved((resolved) => {
@@ -15880,4 +15939,4 @@ async function isentinel(options, ...userConfigs) {
15880
15939
  return composer;
15881
15940
  }
15882
15941
  //#endregion
15883
- export { GLOB_ALL_JSON, GLOB_ALL_SRC, GLOB_BUILD_TOOLS, GLOB_CSS, GLOB_DTS, GLOB_EXCLUDE, GLOB_GRAPHQL, GLOB_HTML, GLOB_JS, GLOB_JSON, GLOB_JSON5, GLOB_JSONC, GLOB_JSX, GLOB_LESS, GLOB_LINTABLE_EXTENSIONS, GLOB_LUA, GLOB_MARKDOWN, GLOB_MARKDOWN_BLOCKS, GLOB_MARKDOWN_CODE, GLOB_MARKDOWN_IN_MARKDOWN, GLOB_MISE, GLOB_PACKAGE_JSON, GLOB_PACKAGE_JSON_ROOT, GLOB_POSTCSS, GLOB_ROOT, GLOB_ROOT_SRC, GLOB_SCSS, GLOB_SRC, GLOB_SRC_EXT, GLOB_SRC_EXTENSIONS, GLOB_STYLE, GLOB_TESTS, GLOB_TOML, GLOB_TS, GLOB_TSCONFIG, GLOB_TSX, GLOB_XML, GLOB_YAML, StylisticConfigDefaults, comments, isentinel as default, isentinel, defaultPluginRenaming, disables, e18e, eslintPlugin, flawless, gitignore, ignores, imports, isInAgentSession, isInEditorEnvironment, isInGitHooksOrLintStaged, javascript, jsdoc, jsonc, loadSmallRulesPlugin, markdown, naming, node, oxfmt, packageJson, perfectionist, pnpm, promise, react, roblox, smallRules, sonarjs, sortCspell, sortGithubAction, sortMiseToml, sortPnpmWorkspace, sortRojoProject, sortTsconfig, spelling, stylistic, test, toml, typescript, unicorn, yaml };
15942
+ export { GLOB_ALL_JSON, GLOB_ALL_SRC, GLOB_BUILD_TOOLS, GLOB_CSS, GLOB_DTS, GLOB_EXCLUDE, GLOB_GRAPHQL, GLOB_HTML, GLOB_JS, GLOB_JSON, GLOB_JSON5, GLOB_JSONC, GLOB_JSX, GLOB_LESS, GLOB_LINTABLE_EXTENSIONS, GLOB_LUA, GLOB_MARKDOWN, GLOB_MARKDOWN_BLOCKS, GLOB_MARKDOWN_CODE, GLOB_MARKDOWN_IN_MARKDOWN, GLOB_MISE, GLOB_PACKAGE_JSON, GLOB_PACKAGE_JSON_ROOT, GLOB_POSTCSS, GLOB_ROOT, GLOB_ROOT_SRC, GLOB_SCSS, GLOB_SRC, GLOB_SRC_EXT, GLOB_SRC_EXTENSIONS, GLOB_STYLE, GLOB_TESTS, GLOB_TOML, GLOB_TS, GLOB_TSCONFIG, GLOB_TSX, GLOB_TYPE_TESTS, GLOB_XML, GLOB_YAML, StylisticConfigDefaults, comments, isentinel as default, isentinel, defaultPluginRenaming, disables, e18e, eslintPlugin, flawless, gitignore, ignores, imports, isInAgentSession, isInEditorEnvironment, isInGitHooksOrLintStaged, javascript, jsdoc, jsonc, loadSmallRulesPlugin, markdown, naming, node, oxfmt, packageJson, perfectionist, pnpm, promise, react, roblox, smallRules, sonarjs, sortCspell, sortGithubAction, sortMiseToml, sortPnpmWorkspace, sortRojoProject, sortTsconfig, spelling, stylistic, test, toml, typescript, unicorn, yaml };
package/dist/lint-cli.mjs CHANGED
@@ -36,7 +36,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
36
36
  }) : target, mod));
37
37
  //#endregion
38
38
  //#region package.json
39
- var version = "6.0.0-beta.13";
39
+ var version = "6.0.0-beta.14";
40
40
  /**
41
41
  * The real TS/JS-family file extensions {@link GLOB_SRC_EXT} encodes (the glob
42
42
  * additionally matches non-existent combinations such as `cjsx`). Kept next to
@@ -3228,7 +3228,7 @@ function resolveIgnoredHelper() {
3228
3228
  * The stderr warning emitted when the resolved ESLint config is not hybrid, so
3229
3229
  * running oxlint too would double-lint every mapped rule. Oxlint is dropped.
3230
3230
  */
3231
- const NON_HYBRID_WARNING = "isentinel-lint: the ESLint config does not enable hybrid mode (`oxlint: true`), so oxlint would re-run rules ESLint already checks. Running ESLint only; enable hybrid mode in your config or pass --oxlint to run oxlint explicitly.\n";
3231
+ const NON_HYBRID_WARNING = "isentinel-lint: the ESLint config does not enable hybrid mode (`oxlint: true` or `oxlint: \"native\"`), so oxlint would re-run rules ESLint already checks. Running ESLint only; enable hybrid mode in your config or pass --oxlint to run oxlint explicitly.\n";
3232
3232
  /**
3233
3233
  * The stderr warning emitted when the hybrid status cannot be determined (the
3234
3234
  * probe failed). The run fails open: both engines run, as before.
package/dist/oxlint.d.mts CHANGED
@@ -23955,9 +23955,14 @@ interface OptionsConfig extends OptionsComponentExtensions, OptionsProjectType {
23955
23955
  * Requires installing the optional peer dependencies `oxlint` and
23956
23956
  * `oxlint-tsgolint`.
23957
23957
  *
23958
+ * `"native"` narrows the hand-off to the rules oxlint implements in Rust
23959
+ * (native rules plus the tsgolint type-aware ones): rules that would run in
23960
+ * oxlint as jsPlugins — and formatting — stay in ESLint. Pair it with
23961
+ * `jsPlugins: false` in the oxlint factory, or both engines run them.
23962
+ *
23958
23963
  * @default false
23959
23964
  */
23960
- oxlint?: boolean;
23965
+ oxlint?: "native" | boolean;
23961
23966
  /**
23962
23967
  * Warn at config-build time when a user config references a rule that
23963
23968
  * oxlint owns in hybrid mode (`oxlint: true`), where the ESLint entry has
@@ -32097,7 +32102,7 @@ type OxlintOptionsConfig = Omit<OptionsConfig, "autoRenamePlugins" | "flawless"
32097
32102
  * Combines oxlint config fields (`env`, `globals`, `rules`, ...) with the
32098
32103
  * shared preset options and the top-level linter `options` object.
32099
32104
  */
32100
- type OxlintFactoryOptions = Omit<TypedOxlintConfigItem, "files"> & OxlintOptionsConfig & {
32105
+ type OxlintFactoryOptions = {
32101
32106
  /**
32102
32107
  * Rule categories to enable at the top level.
32103
32108
  *
@@ -32107,6 +32112,21 @@ type OxlintFactoryOptions = Omit<TypedOxlintConfigItem, "files"> & OxlintOptions
32107
32112
  * enabling one category leaves the rest off.
32108
32113
  */
32109
32114
  categories?: RuleCategories$1;
32115
+ /**
32116
+ * Extra jsPlugins to load, or `false` to run oxlint with native rules
32117
+ * only.
32118
+ *
32119
+ * With `false` the factory drops every rule that would run through a
32120
+ * jsPlugin (spelling, the react and jest families, oxfmt formatting, ...),
32121
+ * leaving oxlint with its Rust rules and — when enabled — the tsgolint
32122
+ * type-aware ones. Pair it with `oxlint: "native"` in the ESLint factory,
32123
+ * which keeps exactly those rules in ESLint.
32124
+ *
32125
+ * `oxlint-comments` is the one exception: it lints the `oxlint-disable`
32126
+ * directives that native rules still need, ESLint cannot run it, and its
32127
+ * rules visit each file once, so it stays loaded.
32128
+ */
32129
+ jsPlugins?: false | NonNullable<TypedOxlintConfigItem["jsPlugins"]>;
32110
32130
  /**
32111
32131
  * Top-level linter options emitted into the generated config
32112
32132
  * (`typeAware`, `typeCheck`, `maxWarnings`, ...).
@@ -32124,7 +32144,7 @@ type OxlintFactoryOptions = Omit<TypedOxlintConfigItem, "files"> & OxlintOptions
32124
32144
  * see no effect from this option.
32125
32145
  */
32126
32146
  oxc?: boolean;
32127
- };
32147
+ } & Omit<TypedOxlintConfigItem, "files" | "jsPlugins"> & OxlintOptionsConfig;
32128
32148
  //#endregion
32129
32149
  //#region src/oxlint/factory.d.ts
32130
32150
  /**
@@ -32144,4 +32164,14 @@ type OxlintFactoryOptions = Omit<TypedOxlintConfigItem, "files"> & OxlintOptions
32144
32164
  */
32145
32165
  declare function isentinel<const O extends OxlintFactoryOptions>(factoryOptions?: O & ValidateOxlintOptions<O>, ...userConfigs: Array<TypedOxlintConfigItem>): OxlintConfig$1;
32146
32166
  //#endregion
32147
- export { type DummyRuleMap, type ExternalPluginEntry, type OxlintConfig, OxlintFactoryOptions, OxlintLinterOptions, OxlintNativeRuleName, type OxlintNativeRuleOptions, OxlintOptionsConfig, type OxlintOverride, OxlintPlugin, type OxlintRenamedJsPluginRuleOptions, type OxlintRuleOptions, type OxlintRules, OxlintSettings, type OxlintTarget, type RuleCategories, TypedOxlintConfigItem, isentinel as default, isentinel, isOxlintCovered, oxlintJsPlugins, oxlintRuleMapping, staysInEslint, translateRuleToOxlint };
32167
+ //#region src/oxlint/utils.d.ts
32168
+ /**
32169
+ * Resolve a jsPlugin specifier, throwing when the package is not installed.
32170
+ *
32171
+ * @param specifier - The plugin package specifier.
32172
+ * @returns The absolute `file://` specifier.
32173
+ * @throws {Error} When the package cannot be resolved.
32174
+ */
32175
+ declare function resolveJsPluginSpecifier(specifier: string): string;
32176
+ //#endregion
32177
+ export { type DummyRuleMap, type ExternalPluginEntry, type OxlintConfig, OxlintFactoryOptions, OxlintLinterOptions, OxlintNativeRuleName, type OxlintNativeRuleOptions, OxlintOptionsConfig, type OxlintOverride, OxlintPlugin, type OxlintRenamedJsPluginRuleOptions, type OxlintRuleOptions, type OxlintRules, OxlintSettings, type OxlintTarget, type RuleCategories, TypedOxlintConfigItem, isentinel as default, isentinel, isOxlintCovered, oxlintJsPlugins, oxlintRuleMapping, resolveJsPluginSpecifier, staysInEslint, translateRuleToOxlint };
package/dist/oxlint.mjs CHANGED
@@ -978,10 +978,16 @@ const GLOB_SRC = "**/*.{,c,m}[jt]s{,x}";
978
978
  const GLOB_JSX = "**/*.{,c,m}jsx";
979
979
  const GLOB_TSX = "**/*.{,c,m}tsx";
980
980
  const GLOB_DTS = "**/*.d.{,c,m}ts";
981
+ /**
982
+ * Type tests (`expectTypeOf` / `assertType`), run by Vitest's `typecheck`
983
+ * mode. They are test files, but assert nothing at runtime.
984
+ */
985
+ const GLOB_TYPE_TESTS = `**/*.test-d.${GLOB_SRC_EXT}`;
981
986
  const GLOB_TESTS = [
982
987
  `**/__tests__/**/*.${GLOB_SRC_EXT}`,
983
988
  `**/*.spec.${GLOB_SRC_EXT}`,
984
989
  `**/*.test.${GLOB_SRC_EXT}`,
990
+ GLOB_TYPE_TESTS,
985
991
  `**/*.bench.${GLOB_SRC_EXT}`,
986
992
  `**/*.benchmark.${GLOB_SRC_EXT}`
987
993
  ];
@@ -1711,6 +1717,38 @@ function resolveJsPluginSpecifier(specifier) {
1711
1717
  return resolved;
1712
1718
  }
1713
1719
  /**
1720
+ * The dedupe key for a jsPlugin entry (a bare specifier, or a named entry).
1721
+ *
1722
+ * @param entry - A bare package specifier, or a `{ name, specifier }` entry.
1723
+ * @returns The plugin name used to deduplicate registrations.
1724
+ */
1725
+ function jsPluginKey(entry) {
1726
+ return typeof entry === "string" ? entry : entry.name;
1727
+ }
1728
+ /**
1729
+ * Drop every rule whose plugin prefix is not registered on the generated
1730
+ * config, mutating the overrides in place. Oxlint fails the whole config build
1731
+ * on a rule naming an unknown plugin, so entries left behind by a plugin that
1732
+ * native-only mode dropped have to go rather than sit inert.
1733
+ *
1734
+ * Keyed on what is actually registered, so a consumer's own jsPlugin keeps its
1735
+ * rules while a preset plugin that is no longer loaded loses them. Unprefixed
1736
+ * (core) rules are always kept.
1737
+ *
1738
+ * @param overrides - The merged overrides (mutated).
1739
+ * @param registeredPlugins - Every native plugin and jsPlugin name registered.
1740
+ */
1741
+ function stripUnregisteredPluginRules(overrides, registeredPlugins) {
1742
+ for (const override of overrides) {
1743
+ const { rules } = override;
1744
+ if (rules === void 0) continue;
1745
+ for (const rule of Object.keys(rules)) {
1746
+ const slashIndex = rule.indexOf("/");
1747
+ if (slashIndex !== -1 && !registeredPlugins.has(rule.slice(0, slashIndex))) delete rules[rule];
1748
+ }
1749
+ }
1750
+ }
1751
+ /**
1714
1752
  * Split a canonical (ESLint-named) rule map into oxlint-native rules and
1715
1753
  * jsPlugin rules, translating rule names via the oxlint rule mapping.
1716
1754
  *
@@ -3469,6 +3507,21 @@ function oxlintStylistic(options = {}) {
3469
3507
  //#endregion
3470
3508
  //#region src/rules/test.ts
3471
3509
  /**
3510
+ * Rules that make no sense in a type test (`*.test-d.ts`): these files assert
3511
+ * with `expectTypeOf`/`assertType` at compile time, run no expectations, and
3512
+ * name their blocks after the types under test.
3513
+ *
3514
+ * @param prefix - The test-plugin prefix in use (`jest` or `vitest`).
3515
+ * @returns The rule map.
3516
+ */
3517
+ function typeTestRules(prefix) {
3518
+ return {
3519
+ [`${prefix}/expect-expect`]: "off",
3520
+ [`${prefix}/prefer-expect-assertions`]: "off",
3521
+ [`${prefix}/prefer-lowercase-title`]: "off"
3522
+ };
3523
+ }
3524
+ /**
3472
3525
  * Jest-extended rules shared between the ESLint and oxlint factories.
3473
3526
  *
3474
3527
  * @returns The rule map.
@@ -3690,6 +3743,11 @@ function oxlintTest({ files = GLOB_TESTS, isInEditor = false, jest = false, over
3690
3743
  globalPackage: "@rbxts/jest-globals",
3691
3744
  version: 27
3692
3745
  } }
3746
+ }), ...createOxlintConfigs({
3747
+ name: "isentinel/test/jest/type-tests",
3748
+ files: [GLOB_TYPE_TESTS],
3749
+ keepUnmappedOff: true,
3750
+ rules: typeTestRules("jest")
3693
3751
  }));
3694
3752
  if (enableVitest) configs.push(...createOxlintConfigs({
3695
3753
  name: "isentinel/test/vitest",
@@ -3705,6 +3763,11 @@ function oxlintTest({ files = GLOB_TESTS, isInEditor = false, jest = false, over
3705
3763
  ...vitestOptions.overrides
3706
3764
  },
3707
3765
  settings: { vitest: { typecheck: vitestOptions.typecheck ?? false } }
3766
+ }), ...createOxlintConfigs({
3767
+ name: "isentinel/test/vitest/type-tests",
3768
+ files: [GLOB_TYPE_TESTS],
3769
+ keepUnmappedOff: true,
3770
+ rules: typeTestRules("vitest")
3708
3771
  }));
3709
3772
  return configs;
3710
3773
  }
@@ -4186,6 +4249,16 @@ const DEFAULT_CATEGORIES = {
4186
4249
  suspicious: "off"
4187
4250
  };
4188
4251
  /**
4252
+ * JsPlugins the preset keeps even under `jsPlugins: false`.
4253
+ *
4254
+ * `oxlint-comments` lints the `oxlint-disable` directives that native rules
4255
+ * still need, and the ESLint side cannot take it over: its rules use oxlint's
4256
+ * `createOnce` API, which ESLint rejects. Its rules visit `Program` once per
4257
+ * file, so the cost is negligible next to the jsPlugin rule set that native-only
4258
+ * mode exists to avoid.
4259
+ */
4260
+ const NATIVE_ONLY_JS_PLUGINS = /* @__PURE__ */ new Set(["oxlint-comments"]);
4261
+ /**
4189
4262
  * Implementation signature; the public overload above carries the
4190
4263
  * redundant-override validation generics.
4191
4264
  *
@@ -4196,9 +4269,10 @@ const DEFAULT_CATEGORIES = {
4196
4269
  */
4197
4270
  function isentinel(factoryOptions, ...userConfigs) {
4198
4271
  const options = factoryOptions ?? { name: "isentinel" };
4199
- const { categories, componentExts: componentExtensions = [], e18e: enableE18e = true, env, eslintPlugin: enableEslintPlugin = false, formatters, gitignore: enableGitignore = true, globals, ignores, jsdoc: enableJsdoc = true, jsx: enableJsx = true, options: linterOptions, oxc: enableOxc = true, react: enableReact = false, root: customRootGlobs, rules = {}, spellCheck: enableSpellCheck, test: enableTest = false } = options;
4272
+ const { 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, react: enableReact = false, root: customRootGlobs, rules = {}, spellCheck: enableSpellCheck, test: enableTest = false } = options;
4200
4273
  const rootGlobs = mergeGlobs(GLOB_ROOT, customRootGlobs);
4201
4274
  const enableRoblox = options.roblox !== false;
4275
+ const nativeOnly = userJsPlugins === false;
4202
4276
  const robloxScopedFiles = typeof options.roblox === "object" && options.roblox.files !== void 0 ? options.roblox.files.flat() : void 0;
4203
4277
  const hasComplement = !enableRoblox || robloxScopedFiles !== void 0;
4204
4278
  const needsComplementOverlay = enableRoblox && robloxScopedFiles !== void 0;
@@ -4344,20 +4418,27 @@ function isentinel(factoryOptions, ...userConfigs) {
4344
4418
  ...gitignorePatterns,
4345
4419
  ...ignores ?? []
4346
4420
  ];
4421
+ /**
4422
+ * Merge a fragment into the accumulated overrides.
4423
+ *
4424
+ * @param config - The fragment to merge.
4425
+ */
4347
4426
  function mergeFragment(config) {
4427
+ const fragmentJsPlugins = config.jsPlugins ?? [];
4348
4428
  const fragmentPlugins = config.plugins ?? [];
4349
4429
  for (const plugin of fragmentPlugins) nativePlugins.add(plugin);
4350
- const fragmentJsPlugins = config.jsPlugins ?? [];
4351
- for (const jsPlugin of fragmentJsPlugins) {
4352
- const key = typeof jsPlugin === "string" ? jsPlugin : jsPlugin.name;
4353
- jsPlugins.set(key, jsPlugin);
4354
- }
4430
+ for (const jsPlugin of fragmentJsPlugins) jsPlugins.set(jsPluginKey(jsPlugin), jsPlugin);
4355
4431
  if (config.settings) Object.assign(mergedSettings, config.settings);
4356
4432
  const { name: _name, plugins: _plugins, settings: _settings, ...override } = config;
4357
4433
  overrides.push(override);
4358
4434
  }
4359
- const fragments = configs.flat();
4360
- for (const fragment of fragments) mergeFragment(fragment);
4435
+ for (const fragment of configs.flat()) {
4436
+ if (nativeOnly && droppedByNativeOnly(fragment)) {
4437
+ if (fragment.settings) Object.assign(mergedSettings, fragment.settings);
4438
+ continue;
4439
+ }
4440
+ mergeFragment(fragment);
4441
+ }
4361
4442
  if (Object.keys(rules).length > 0) {
4362
4443
  const optionsFragments = createOxlintConfigs({
4363
4444
  name: "isentinel/options-rules",
@@ -4365,7 +4446,10 @@ function isentinel(factoryOptions, ...userConfigs) {
4365
4446
  keepUnmappedOff: true,
4366
4447
  rules
4367
4448
  });
4368
- for (const fragment of optionsFragments) mergeFragment(fragment);
4449
+ for (const fragment of optionsFragments) {
4450
+ const { jsPlugins: _fragmentJsPlugins, ...withoutJsPlugins } = fragment;
4451
+ mergeFragment(nativeOnly ? withoutJsPlugins : fragment);
4452
+ }
4369
4453
  }
4370
4454
  if (globals && Object.keys(globals).length > 0) mergeFragment({
4371
4455
  name: "isentinel/options-globals",
@@ -4373,6 +4457,8 @@ function isentinel(factoryOptions, ...userConfigs) {
4373
4457
  globals
4374
4458
  });
4375
4459
  for (const userConfig of userConfigs) mergeFragment(userConfig);
4460
+ if (Array.isArray(userJsPlugins)) for (const jsPlugin of userJsPlugins) jsPlugins.set(jsPluginKey(jsPlugin), jsPlugin);
4461
+ if (nativeOnly) stripUnregisteredPluginRules(overrides, /* @__PURE__ */ new Set([...nativePlugins, ...jsPlugins.keys()]));
4376
4462
  if (defaultSeverity) {
4377
4463
  const severityExcludeRules = /* @__PURE__ */ new Set(["sonar/todo-tag"]);
4378
4464
  for (const override of overrides) if (override.rules) override.rules = overrideRuleSeverity(override.rules, defaultSeverity, severityExcludeRules);
@@ -4395,5 +4481,16 @@ function isentinel(factoryOptions, ...userConfigs) {
4395
4481
  settings: mergedSettings
4396
4482
  });
4397
4483
  }
4484
+ /**
4485
+ * Whether native-only mode drops a preset fragment: it needs a jsPlugin, and at
4486
+ * least one of them is not kept in that mode.
4487
+ *
4488
+ * @param fragment - The preset fragment.
4489
+ * @returns Whether the fragment is dropped.
4490
+ */
4491
+ function droppedByNativeOnly(fragment) {
4492
+ const fragmentJsPlugins = fragment.jsPlugins ?? [];
4493
+ return fragmentJsPlugins.length > 0 && fragmentJsPlugins.some((entry) => !NATIVE_ONLY_JS_PLUGINS.has(jsPluginKey(entry)));
4494
+ }
4398
4495
  //#endregion
4399
- export { isentinel as default, isentinel, isOxlintCovered, oxlintJsPlugins, oxlintRuleMapping, staysInEslint, translateRuleToOxlint };
4496
+ export { isentinel as default, isentinel, isOxlintCovered, oxlintJsPlugins, oxlintRuleMapping, resolveJsPluginSpecifier, staysInEslint, translateRuleToOxlint };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isentinel/eslint-config",
3
- "version": "6.0.0-beta.13",
3
+ "version": "6.0.0-beta.14",
4
4
  "description": "iSentinel's ESLint config",
5
5
  "keywords": [
6
6
  "eslint-config",
@@ -138,7 +138,7 @@
138
138
  "typescript": "6.0.3",
139
139
  "unplugin-unused": "0.5.7",
140
140
  "vitest": "4.1.10",
141
- "@isentinel/eslint-config": "6.0.0-beta.13"
141
+ "@isentinel/eslint-config": "6.0.0-beta.14"
142
142
  },
143
143
  "peerDependencies": {
144
144
  "@vitest/eslint-plugin": "^1.6.4",