@isentinel/eslint-config 6.0.0-beta.43 → 6.0.0-beta.44

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
@@ -928,6 +928,10 @@ are `{nodeName}`, `{NodeName}`, `{node_name}` and `{NODE_NAME}`. A file already
928
928
  matching a template is exempt from it. A source file with no test disables the
929
929
  rule at the source.
930
930
 
931
+ The rule needs no type information, so oxlint runs it too: the same option is
932
+ accepted by the oxlint factory, and hybrid mode (`oxlint: true`) hands the check
933
+ to oxlint.
934
+
931
935
  Two caveats. Deleting `foo.spec.ts` does not change `foo.ts`, so a cached run
932
936
  replays the old clean result — the check is only sound uncached. And the plugin
933
937
  writes `projectStructure.cache.json` into `projectRoot` while it has something
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.43";
12
+ var version = "6.0.0-beta.44";
13
13
  var package_default = {
14
14
  name: "@isentinel/eslint-config",
15
15
  version,
package/dist/index.mjs CHANGED
@@ -3950,42 +3950,44 @@ async function detectCatalogUsage() {
3950
3950
  return yaml.includes("catalog:") || yaml.includes("catalogs:");
3951
3951
  }
3952
3952
  //#endregion
3953
- //#region src/eslint/configs/project-structure.ts
3953
+ //#region src/rules/project-structure.ts
3954
3954
  init_globs();
3955
3955
  /** Stands for the extension of the file a rule matched. */
3956
3956
  const EXTENSION_TOKEN = "{ext}";
3957
3957
  const DEFAULT_ENFORCE_EXISTENCE = [`{node-name}.spec.${EXTENSION_TOKEN}`];
3958
3958
  /** Reusable-rule id for "a folder of any name". */
3959
3959
  const ANY_FOLDER = "isentinelAnyFolder";
3960
- async function projectStructure({ enforceExistence = DEFAULT_ENFORCE_EXISTENCE, files = [GLOB_SRC], ignores = [
3960
+ /** The files the co-location check runs on, before `ignores`. */
3961
+ const PROJECT_STRUCTURE_FILES = [GLOB_SRC];
3962
+ /** The files the co-location check skips. */
3963
+ const PROJECT_STRUCTURE_IGNORES = [
3961
3964
  ...GLOB_TESTS,
3962
3965
  GLOB_DTS,
3963
3966
  ...GLOB_BUILD_CONFIGS
3964
- ], overrides = {}, projectRoot = process.cwd(), structureRoot } = {}) {
3965
- await ensurePackages(["eslint-plugin-project-structure"]);
3966
- const { projectStructurePlugin } = await import("eslint-plugin-project-structure");
3967
+ ];
3968
+ /**
3969
+ * Co-location rules shared between the ESLint and oxlint factories.
3970
+ *
3971
+ * `folder-structure` is not type-aware and runs unchanged as an oxlint
3972
+ * jsPlugin, so both engines have to hand it the same options or they disagree
3973
+ * about which files are co-located.
3974
+ *
3975
+ * @param options - The templates to enforce, and the roots they resolve
3976
+ * against.
3977
+ * @returns The rule map.
3978
+ */
3979
+ function folderStructureRules({ enforceExistence = DEFAULT_ENFORCE_EXISTENCE, projectRoot = process.cwd(), structureRoot } = {}) {
3967
3980
  const children = buildChildren(typeof enforceExistence === "string" ? [enforceExistence] : enforceExistence);
3968
- return [{
3969
- name: "isentinel/project-structure/setup",
3970
- plugins: { "project-structure": projectStructurePlugin }
3971
- }, {
3972
- name: "isentinel/project-structure/rules",
3973
- files,
3974
- ignores,
3975
- rules: {
3976
- "project-structure/folder-structure": ["error", {
3977
- longPathsInfo: false,
3978
- projectRoot,
3979
- rules: { [ANY_FOLDER]: {
3980
- name: "*",
3981
- children
3982
- } },
3983
- structure: children,
3984
- ...structureRoot === void 0 ? {} : { structureRoot }
3985
- }],
3986
- ...overrides
3987
- }
3988
- }];
3981
+ return { "project-structure/folder-structure": ["error", {
3982
+ longPathsInfo: false,
3983
+ projectRoot,
3984
+ rules: { [ANY_FOLDER]: {
3985
+ name: "*",
3986
+ children
3987
+ } },
3988
+ structure: children,
3989
+ ...structureRoot === void 0 ? {} : { structureRoot }
3990
+ }] };
3989
3991
  }
3990
3992
  /**
3991
3993
  * The rules matching the files inside one folder, in the order
@@ -4012,6 +4014,28 @@ function buildChildren(templates) {
4012
4014
  return children;
4013
4015
  }
4014
4016
  //#endregion
4017
+ //#region src/eslint/configs/project-structure.ts
4018
+ async function projectStructure({ enforceExistence, files = PROJECT_STRUCTURE_FILES, ignores = PROJECT_STRUCTURE_IGNORES, overrides = {}, projectRoot, structureRoot } = {}) {
4019
+ await ensurePackages(["eslint-plugin-project-structure"]);
4020
+ const { projectStructurePlugin } = await import("eslint-plugin-project-structure");
4021
+ return [{
4022
+ name: "isentinel/project-structure/setup",
4023
+ plugins: { "project-structure": projectStructurePlugin }
4024
+ }, {
4025
+ name: "isentinel/project-structure/rules",
4026
+ files,
4027
+ ignores,
4028
+ rules: {
4029
+ ...folderStructureRules({
4030
+ enforceExistence,
4031
+ projectRoot,
4032
+ structureRoot
4033
+ }),
4034
+ ...overrides
4035
+ }
4036
+ }];
4037
+ }
4038
+ //#endregion
4015
4039
  //#region src/rules/promise.ts
4016
4040
  /**
4017
4041
  * Promise rules shared between the ESLint and oxlint factories.
@@ -15660,6 +15684,7 @@ var init_adapters = __esmMin((() => {
15660
15684
  "oxfmt": "eslint-plugin-oxfmt",
15661
15685
  "oxlint-comments": "oxlint-plugin-oxlint-comments",
15662
15686
  "perfectionist": "eslint-plugin-perfectionist",
15687
+ "project-structure": "eslint-plugin-project-structure",
15663
15688
  "promise-js": "eslint-plugin-promise",
15664
15689
  "react-jsx": "eslint-plugin-react-jsx",
15665
15690
  "react-naming-convention": "eslint-plugin-react-naming-convention",
@@ -16549,6 +16574,9 @@ var init_oxlint_capabilities = __esmMin((() => {
16549
16574
  "prefer-spread",
16550
16575
  "prefer-template",
16551
16576
  "preserve-caught-error",
16577
+ "project-structure/file-composition",
16578
+ "project-structure/folder-structure",
16579
+ "project-structure/independent-modules",
16552
16580
  "promise/always-return",
16553
16581
  "promise/avoid-new",
16554
16582
  "promise/catch-or-return",
package/dist/lint-cli.mjs CHANGED
@@ -38,7 +38,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
38
38
  }) : target, mod));
39
39
  //#endregion
40
40
  //#region package.json
41
- var version = "6.0.0-beta.43";
41
+ var version = "6.0.0-beta.44";
42
42
  //#endregion
43
43
  //#region src/lint-cli/lib/cli/types.ts
44
44
  /**
package/dist/oxlint.d.mts CHANGED
@@ -3860,6 +3860,7 @@ interface OverrideSiteChains {
3860
3860
  e18e: MainChain;
3861
3861
  eslintPlugin: FeatureChain;
3862
3862
  javascript: MainChain;
3863
+ projectStructure: FeatureChain;
3863
3864
  react: ReactChain;
3864
3865
  roblox: MainChain;
3865
3866
  typescript: MainChain;
@@ -33259,7 +33260,7 @@ type OxlintCommentsRequireDescription = [] | [{
33259
33260
  ignore?: ("oxlint-disable" | "oxlint-disable-line" | "oxlint-disable-next-line" | "oxlint-enable")[];
33260
33261
  }];
33261
33262
  /** JsPlugin prefixes whose rules keep their ESLint-side names in oxlint configs. */
33262
- type OxlintKeptJsPluginPrefix = '@cspell' | 'antfu' | 'better-max-params' | 'comment-length' | 'de-morgan' | 'e18e' | 'erasable-syntax-only' | 'eslint-plugin' | 'flawless' | 'jest-extended' | 'oxfmt' | 'perfectionist' | 'react-jsx' | 'react-naming-convention' | 'roblox' | 'sentinel' | 'small-rules' | 'sonar' | 'style' | 'testing-library' | 'ts' | 'unused-imports';
33263
+ type OxlintKeptJsPluginPrefix = '@cspell' | 'antfu' | 'better-max-params' | 'comment-length' | 'de-morgan' | 'e18e' | 'erasable-syntax-only' | 'eslint-plugin' | 'flawless' | 'jest-extended' | 'oxfmt' | 'perfectionist' | 'project-structure' | 'react-jsx' | 'react-naming-convention' | 'roblox' | 'sentinel' | 'small-rules' | 'sonar' | 'style' | 'testing-library' | 'ts' | 'unused-imports';
33263
33264
  /** JsPlugin rules whose oxlint names match the ESLint-side names. */
33264
33265
  type OxlintKeptJsPluginRuleOptions = { [K in keyof RuleOptions as K extends `${OxlintKeptJsPluginPrefix}/${string}` ? K : never]: RuleOptions[K]; };
33265
33266
  /** All rules known to the oxlint factory, keyed by canonical oxlint rule name. */
@@ -33305,7 +33306,7 @@ type TypedOxlintConfigItem = Omit<OxlintOverride$1, "rules"> & {
33305
33306
  * Oxlint can only lint JS/TS files, so the JSON, YAML, TOML, Markdown and
33306
33307
  * pnpm-related options are omitted, along with ESLint-only options.
33307
33308
  */
33308
- type OxlintOptionsConfig = Omit<OptionsConfig, "autoRenamePlugins" | "flawless" | "jsonc" | "markdown" | "namedConfigs" | "naming" | "oxlint" | "pnpm" | "projectStructure" | "toml" | "yaml">;
33309
+ type OxlintOptionsConfig = Omit<OptionsConfig, "autoRenamePlugins" | "flawless" | "jsonc" | "markdown" | "namedConfigs" | "naming" | "oxlint" | "pnpm" | "toml" | "yaml">;
33309
33310
  /**
33310
33311
  * Options accepted by the oxlint factory function.
33311
33312
  *
package/dist/oxlint.mjs CHANGED
@@ -616,6 +616,9 @@ const jsPluginRuleNames = /* @__PURE__ */ new Set([
616
616
  "prefer-spread",
617
617
  "prefer-template",
618
618
  "preserve-caught-error",
619
+ "project-structure/file-composition",
620
+ "project-structure/folder-structure",
621
+ "project-structure/independent-modules",
619
622
  "promise/always-return",
620
623
  "promise/avoid-new",
621
624
  "promise/catch-or-return",
@@ -4747,6 +4750,7 @@ const oxlintJsPlugins = {
4747
4750
  "oxfmt": "eslint-plugin-oxfmt",
4748
4751
  "oxlint-comments": "oxlint-plugin-oxlint-comments",
4749
4752
  "perfectionist": "eslint-plugin-perfectionist",
4753
+ "project-structure": "eslint-plugin-project-structure",
4750
4754
  "promise-js": "eslint-plugin-promise",
4751
4755
  "react-jsx": "eslint-plugin-react-jsx",
4752
4756
  "react-naming-convention": "eslint-plugin-react-naming-convention",
@@ -5165,6 +5169,23 @@ const GLOB_ROOT = [
5165
5169
  ];
5166
5170
  const GLOB_SRC_EXT = "{,c,m}[jt]s{,x}";
5167
5171
  const GLOB_SRC = "**/*.{,c,m}[jt]s{,x}";
5172
+ /**
5173
+ * The real TS/JS-family file extensions {@link GLOB_SRC_EXT} encodes (the glob
5174
+ * additionally matches non-existent combinations such as `cjsx`). Kept next to
5175
+ * the glob so the two are maintained together; consumed by the lint CLI to size
5176
+ * its type-aware pass.
5177
+ */
5178
+ const GLOB_SRC_EXTENSIONS = [
5179
+ "ts",
5180
+ "tsx",
5181
+ "mts",
5182
+ "cts",
5183
+ "js",
5184
+ "jsx",
5185
+ "mjs",
5186
+ "cjs"
5187
+ ];
5188
+ [...GLOB_SRC_EXTENSIONS];
5168
5189
  const GLOB_JSX = "**/*.{,c,m}jsx";
5169
5190
  const GLOB_TSX = "**/*.{,c,m}tsx";
5170
5191
  const GLOB_DTS = "**/*.d.{,c,m}ts";
@@ -8013,6 +8034,94 @@ function oxlintPerfectionist(config) {
8013
8034
  })];
8014
8035
  }
8015
8036
  //#endregion
8037
+ //#region src/rules/project-structure.ts
8038
+ /** Stands for the extension of the file a rule matched. */
8039
+ const EXTENSION_TOKEN = "{ext}";
8040
+ const DEFAULT_ENFORCE_EXISTENCE = [`{node-name}.spec.${EXTENSION_TOKEN}`];
8041
+ /** Reusable-rule id for "a folder of any name". */
8042
+ const ANY_FOLDER = "isentinelAnyFolder";
8043
+ /** The files the co-location check runs on, before `ignores`. */
8044
+ const PROJECT_STRUCTURE_FILES = [GLOB_SRC];
8045
+ /** The files the co-location check skips. */
8046
+ const PROJECT_STRUCTURE_IGNORES = [
8047
+ ...GLOB_TESTS,
8048
+ GLOB_DTS,
8049
+ ...GLOB_BUILD_CONFIGS
8050
+ ];
8051
+ /**
8052
+ * Co-location rules shared between the ESLint and oxlint factories.
8053
+ *
8054
+ * `folder-structure` is not type-aware and runs unchanged as an oxlint
8055
+ * jsPlugin, so both engines have to hand it the same options or they disagree
8056
+ * about which files are co-located.
8057
+ *
8058
+ * @param options - The templates to enforce, and the roots they resolve
8059
+ * against.
8060
+ * @returns The rule map.
8061
+ */
8062
+ function folderStructureRules({ enforceExistence = DEFAULT_ENFORCE_EXISTENCE, projectRoot = process.cwd(), structureRoot } = {}) {
8063
+ const children = buildChildren(typeof enforceExistence === "string" ? [enforceExistence] : enforceExistence);
8064
+ return { "project-structure/folder-structure": ["error", {
8065
+ longPathsInfo: false,
8066
+ projectRoot,
8067
+ rules: { [ANY_FOLDER]: {
8068
+ name: "*",
8069
+ children
8070
+ } },
8071
+ structure: children,
8072
+ ...structureRoot === void 0 ? {} : { structureRoot }
8073
+ }] };
8074
+ }
8075
+ /**
8076
+ * The rules matching the files inside one folder, in the order
8077
+ * `folder-structure` tries them - first match wins.
8078
+ *
8079
+ * Every entry is permissive: `structure` is required by the rule and an
8080
+ * unmatched node is an error, so anything less would turn the co-location
8081
+ * check into whole-tree naming enforcement.
8082
+ *
8083
+ * @param templates - The `enforceExistence` templates, before substitution.
8084
+ * @returns The child rules, ending in the recursive folder rule.
8085
+ */
8086
+ function buildChildren(templates) {
8087
+ const children = templates.map((template) => {
8088
+ return { name: (template.split("/").pop() ?? template).replaceAll(/\{[^{}]*\}/g, "*") };
8089
+ });
8090
+ for (const extension of GLOB_SRC_EXTENSIONS) children.push({
8091
+ name: `*.${extension}`,
8092
+ enforceExistence: templates.map((template) => {
8093
+ return template.replaceAll(EXTENSION_TOKEN, () => extension);
8094
+ })
8095
+ });
8096
+ children.push({ ruleId: ANY_FOLDER });
8097
+ return children;
8098
+ }
8099
+ //#endregion
8100
+ //#region src/oxlint/configs/project-structure.ts
8101
+ /**
8102
+ * Co-location rules for standalone oxlint.
8103
+ *
8104
+ * `folder-structure` runs as a jsPlugin: it visits `Program` once per file and
8105
+ * answers from the filesystem, so oxlint needs no type information to run it.
8106
+ *
8107
+ * @param options - The templates to enforce, and the roots they resolve
8108
+ * against.
8109
+ * @returns The generated config fragments.
8110
+ */
8111
+ function oxlintProjectStructure({ enforceExistence, files = PROJECT_STRUCTURE_FILES, ignores = PROJECT_STRUCTURE_IGNORES, overrides = {}, projectRoot, structureRoot } = {}) {
8112
+ return createOxlintConfigs({
8113
+ name: "isentinel/project-structure",
8114
+ excludeFiles: ignores,
8115
+ files: files.flat(),
8116
+ overrides,
8117
+ rules: folderStructureRules({
8118
+ enforceExistence,
8119
+ projectRoot,
8120
+ structureRoot
8121
+ })
8122
+ });
8123
+ }
8124
+ //#endregion
8016
8125
  //#region src/rules/promise.ts
8017
8126
  /**
8018
8127
  * Promise rules shared between the ESLint and oxlint factories.
@@ -9230,7 +9339,7 @@ const NATIVE_ONLY_JS_PLUGINS = /* @__PURE__ */ new Set(["oxlint-comments"]);
9230
9339
  */
9231
9340
  function isentinel(factoryOptions, ...userConfigs) {
9232
9341
  const options = factoryOptions ?? { name: "isentinel" };
9233
- 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, warnDroppedOverrides: enableDroppedOverrideWarning = true } = options;
9342
+ 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, projectStructure: enableProjectStructure = false, react: enableReact = false, root: customRootGlobs, rules = {}, spellCheck: enableSpellCheck, test: enableTest = false, warnDroppedOverrides: enableDroppedOverrideWarning = true } = options;
9234
9343
  const rootGlobs = mergeGlobs(GLOB_ROOT, customRootGlobs);
9235
9344
  const enableRoblox = options.roblox !== false;
9236
9345
  const reactOptions = resolveSubOptions(options, "react");
@@ -9359,6 +9468,7 @@ function isentinel(factoryOptions, ...userConfigs) {
9359
9468
  isInEditor
9360
9469
  }));
9361
9470
  if (enableEslintPlugin !== false) configs.push(oxlintEslintPlugin({ ...getOverrides(options, "eslintPlugin") }));
9471
+ if (enableProjectStructure !== false) configs.push(oxlintProjectStructure(resolveSubOptions(options, "projectStructure")));
9362
9472
  if (enableOxc) {
9363
9473
  configs.push(oxlintOxc({ roblox: enableRoblox }));
9364
9474
  if (needsComplementOverlay) configs.push(oxlintOxc({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isentinel/eslint-config",
3
- "version": "6.0.0-beta.43",
3
+ "version": "6.0.0-beta.44",
4
4
  "description": "iSentinel's ESLint config",
5
5
  "keywords": [
6
6
  "eslint-config",
@@ -145,7 +145,7 @@
145
145
  "typescript": "6.0.3",
146
146
  "unplugin-unused": "0.5.7",
147
147
  "vitest": "4.1.10",
148
- "@isentinel/eslint-config": "6.0.0-beta.43"
148
+ "@isentinel/eslint-config": "6.0.0-beta.44"
149
149
  },
150
150
  "peerDependencies": {
151
151
  "@vitest/eslint-plugin": "^1.6.4",