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

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
@@ -890,6 +890,49 @@ otherwise, you can install them manually:
890
890
  pnpm i -D eslint-plugin-eslint-plugin
891
891
  ```
892
892
 
893
+ #### Co-located Tests
894
+
895
+ `projectStructure` requires every source file to sit next to its test file,
896
+ through
897
+ [eslint-plugin-project-structure](https://github.com/Igorkowalski94/eslint-plugin-project-structure).
898
+ `src/place-service.ts` then has to be joined by `src/place-service.spec.ts`.
899
+ Only `enforceExistence` is driven here, so the rule never enforces naming:
900
+
901
+ ```ts
902
+ // eslint.config.ts
903
+ import isentinel from "@isentinel/eslint-config";
904
+
905
+ export default isentinel({
906
+ projectStructure: {
907
+ // The plugin's name placeholders, plus `{ext}` for the extension of the
908
+ // file that matched. Default: ["{node-name}.spec.{ext}"]
909
+ enforceExistence: "__tests__/{node-name}.test.{ext}",
910
+ // Default: every source file the preset lints
911
+ files: ["src/**/*.{ts,tsx}"],
912
+ // Default: tests, `.d.ts` files and build configs
913
+ ignores: ["**/index.ts"],
914
+ // Default: `process.cwd()`
915
+ projectRoot: import.meta.dirname,
916
+ // Restricts the check to one subtree. Default: all of `projectRoot`
917
+ structureRoot: "src",
918
+ },
919
+ });
920
+ ```
921
+
922
+ ```bash
923
+ pnpm i -D eslint-plugin-project-structure
924
+ ```
925
+
926
+ `{node-name}` matches the kebab-case filenames the preset enforces; the others
927
+ are `{nodeName}`, `{NodeName}`, `{node_name}` and `{NODE_NAME}`. A file already
928
+ matching a template is exempt from it. A source file with no test disables the
929
+ rule at the source.
930
+
931
+ Two caveats. Deleting `foo.spec.ts` does not change `foo.ts`, so a cached run
932
+ replays the old clean result — the check is only sound uncached. And the plugin
933
+ writes `projectStructure.cache.json` into `projectRoot` while it has something
934
+ to report, so gitignore it.
935
+
893
936
  ### Git hooks
894
937
 
895
938
  If you want to apply lint and auto-fix before every commit, use
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.42";
12
+ var version = "6.0.0-beta.43";
13
13
  var package_default = {
14
14
  name: "@isentinel/eslint-config",
15
15
  version,
@@ -155,6 +155,7 @@ var package_default = {
155
155
  "eslint-plugin-jest": "catalog:peer",
156
156
  "eslint-plugin-jest-extended": "catalog:peer",
157
157
  "eslint-plugin-n": "catalog:peer",
158
+ "eslint-plugin-project-structure": "catalog:peer",
158
159
  "eslint-plugin-react-jsx": "catalog:peer",
159
160
  "eslint-plugin-react-naming-convention": "catalog:peer",
160
161
  "eslint-plugin-react-x": "catalog:peer",
@@ -179,6 +180,7 @@ var package_default = {
179
180
  "eslint-plugin-jest": "^29.15.0",
180
181
  "eslint-plugin-jest-extended": "^3.0.0",
181
182
  "eslint-plugin-n": "^17.0.0 || ^18.0.0",
183
+ "eslint-plugin-project-structure": "^3.14.0",
182
184
  "eslint-plugin-react-jsx": "^5.10.0",
183
185
  "eslint-plugin-react-naming-convention": "^5.10.0",
184
186
  "eslint-plugin-react-x": "^5.10.0",
@@ -192,6 +194,7 @@ var package_default = {
192
194
  "eslint-plugin-jest": { "optional": true },
193
195
  "eslint-plugin-jest-extended": { "optional": true },
194
196
  "eslint-plugin-n": { "optional": true },
197
+ "eslint-plugin-project-structure": { "optional": true },
195
198
  "eslint-plugin-react-jsx": { "optional": true },
196
199
  "eslint-plugin-react-naming-convention": { "optional": true },
197
200
  "eslint-plugin-react-x": { "optional": true },
@@ -531,7 +534,7 @@ async function updateEslintFiles(result) {
531
534
  //#endregion
532
535
  //#region src/cli/constants-generated.ts
533
536
  const versionsMap = {
534
- "eslint": "10.7.0",
537
+ "eslint": "10.8.1",
535
538
  "eslint-plugin-jest": "29.16.0",
536
539
  "eslint-plugin-react-jsx": "5.17.3",
537
540
  "eslint-plugin-react-naming-convention": "5.17.3",
package/dist/index.d.mts CHANGED
@@ -6488,6 +6488,7 @@ interface OverrideSiteChains {
6488
6488
  jsonc: [JsoncRuleDefaults];
6489
6489
  markdown: [MarkdownRuleDefaults];
6490
6490
  naming: FeatureChain;
6491
+ projectStructure: FeatureChain;
6491
6492
  react: ReactChain;
6492
6493
  roblox: MainChain;
6493
6494
  toml: [TomlRuleDefaults];
@@ -6497,7 +6498,7 @@ interface OverrideSiteChains {
6497
6498
  type TestChain = [TestRuleDefaults, SourceFeatureRuleDefaults, MainRuleDefaults];
6498
6499
  type ValidateOptionsWith<O, VK extends VariantKey> = ValidateSite<O, "rules", MainChain, VK> & ValidateSubOptionsFromTable<O, OverrideSiteChains, VK> & ValidateTestOption<O, TestChain, VK>;
6499
6500
  //#endregion
6500
- //#region ../../../setup-pnpm/node_modules/.bin/store/v11/links/@/eslint-config-flat-gitignore/2.3.0/1320bd07004d149fd5c550ffda02aec5ecff7717d8f1602b7e9e51dffe8368d5/node_modules/eslint-config-flat-gitignore/dist/index.d.mts
6501
+ //#region ../../../setup-pnpm/node_modules/.bin/store/v11/links/@/eslint-config-flat-gitignore/2.3.0/0a594adbd94ca75e2950b1104992894d49bc93f4f2bc935ad192d7104e4c271e/node_modules/eslint-config-flat-gitignore/dist/index.d.mts
6501
6502
  interface FlatGitignoreOptions {
6502
6503
  /**
6503
6504
  * Name of the configuration.
@@ -10470,6 +10471,21 @@ interface RuleOptions {
10470
10471
  * @see https://eslint.org/docs/latest/rules/preserve-caught-error
10471
10472
  */
10472
10473
  'preserve-caught-error'?: Linter.RuleEntry<PreserveCaughtError>;
10474
+ /**
10475
+ * Enforce advanced naming rules and prohibit the use of given selectors in a given file. Have full control over what your file can contain and the naming conventions it must follow.
10476
+ * @see https://github.com/Igorkowalski94/eslint-plugin-project-structure/wiki/project%E2%80%91structure-%E2%80%8Bfile%E2%80%91composition#root
10477
+ */
10478
+ 'project-structure/file-composition'?: Linter.RuleEntry<ProjectStructureFileComposition>;
10479
+ /**
10480
+ * Enforce rules on folder structure to keep your repository consistent, orderly and well thought out.
10481
+ * @see https://github.com/Igorkowalski94/eslint-plugin-project-structure/wiki/project%E2%80%91structure-%E2%80%8Bfolder%E2%80%91structure#root
10482
+ */
10483
+ 'project-structure/folder-structure'?: Linter.RuleEntry<ProjectStructureFolderStructure>;
10484
+ /**
10485
+ * A key principle of a healthy project is to prevent the creation of a massive dependency tree, where removing or editing one feature triggers a chain reaction that impacts the entire project. Create independent modules to keep your project scalable and easy to maintain. Get rid of dependencies between modules and create truly independent functionalities.
10486
+ * @see https://github.com/Igorkowalski94/eslint-plugin-project-structure/wiki/project%E2%80%91structure-%E2%80%8Bindependent%E2%80%91modules#root
10487
+ */
10488
+ 'project-structure/independent-modules'?: Linter.RuleEntry<ProjectStructureIndependentModules>;
10473
10489
  /**
10474
10490
  * Require returning inside each `then()` to create readable and reusable Promise chains.
10475
10491
  * @see https://github.com/eslint-community/eslint-plugin-promise/blob/main/docs/rules/always-return.md
@@ -23278,6 +23294,18 @@ type PreserveCaughtError = [] | [{
23278
23294
  argumentPosition: number;
23279
23295
  })[];
23280
23296
  }];
23297
+ // ----- project-structure/file-composition -----
23298
+ type ProjectStructureFileComposition = [] | [{
23299
+ [k: string]: unknown | undefined;
23300
+ }];
23301
+ // ----- project-structure/folder-structure -----
23302
+ type ProjectStructureFolderStructure = [] | [{
23303
+ [k: string]: unknown | undefined;
23304
+ }];
23305
+ // ----- project-structure/independent-modules -----
23306
+ type ProjectStructureIndependentModules = [] | [{
23307
+ [k: string]: unknown | undefined;
23308
+ }];
23281
23309
  // ----- promise/always-return -----
23282
23310
  type PromiseAlwaysReturn = [] | [{
23283
23311
  ignoreLastCallback?: boolean;
@@ -27158,7 +27186,7 @@ type Yoda = [] | [("always" | "never")] | [("always" | "never"), {
27158
27186
  onlyEquality?: boolean;
27159
27187
  }];
27160
27188
  // Names of all the configs
27161
- 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/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';
27189
+ 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/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';
27162
27190
  //#endregion
27163
27191
  //#region src/utils.d.ts
27164
27192
  type ExtractRuleOptions<T> = T extends Linter.RuleEntry<infer U> ? U : never;
@@ -27496,6 +27524,37 @@ interface NamingConfig extends OptionsOverridesTypeAware {
27496
27524
  */
27497
27525
  selectorsTsx?: Array<NamingSelector>;
27498
27526
  }
27527
+ interface ProjectStructureConfig extends OptionsOverrides {
27528
+ /**
27529
+ * The files each linted source file has to sit next to, as
27530
+ * `enforceExistence` templates. Takes the plugin's name placeholders
27531
+ * (`{nodeName}`, `{NodeName}`, `{node-name}`, `{node_name}`,
27532
+ * `{NODE_NAME}`) plus `{ext}` for the extension of the file that matched.
27533
+ * A file already matching a template is exempt from it.
27534
+ *
27535
+ * @default ["{node-name}.spec.{ext}"]
27536
+ */
27537
+ enforceExistence?: Array<string> | string;
27538
+ /**
27539
+ * Files exempt from the check.
27540
+ *
27541
+ * @default [...GLOB_TESTS, GLOB_DTS, ...GLOB_BUILD_CONFIGS]
27542
+ */
27543
+ ignores?: Array<string>;
27544
+ /**
27545
+ * The folder the enforced paths resolve against, and where the plugin
27546
+ * writes `projectStructure.cache.json` when it reports.
27547
+ *
27548
+ * @default process.cwd()
27549
+ */
27550
+ projectRoot?: string;
27551
+ /**
27552
+ * Restricts the check to one subtree of `projectRoot`, as a relative path.
27553
+ *
27554
+ * @default undefined - the whole of `projectRoot`
27555
+ */
27556
+ structureRoot?: string;
27557
+ }
27499
27558
  interface OptionsTypeScriptParserOptions {
27500
27559
  /**
27501
27560
  * Glob patterns for files that should be type aware.
@@ -27844,6 +27903,14 @@ interface OptionsConfig extends OptionsComponentExtensions, OptionsProjectType {
27844
27903
  * @see https://github.com/antfu/pnpm-workspace-utils
27845
27904
  */
27846
27905
  pnpm?: boolean | OptionsPnpm;
27906
+ /**
27907
+ * Require every source file to have a co-located test file, via
27908
+ * [eslint-plugin-project-structure](https://github.com/Igorkowalski94/eslint-plugin-project-structure).
27909
+ *
27910
+ * @default false
27911
+ * @requires eslint-plugin-project-structure
27912
+ */
27913
+ projectStructure?: boolean | ProjectStructureConfig;
27847
27914
  /**
27848
27915
  * Enable react rules.
27849
27916
  *
@@ -28282,6 +28349,9 @@ declare function perfectionist(config?: OptionsProjectType & PerfectionistConfig
28282
28349
  //#region src/eslint/configs/pnpm.d.ts
28283
28350
  declare function pnpm(options: OptionsPnpm & Required<OptionsIsInEditor>): Promise<Array<TypedFlatConfigItem>>;
28284
28351
  //#endregion
28352
+ //#region src/eslint/configs/project-structure.d.ts
28353
+ declare function projectStructure({ enforceExistence, files, ignores, overrides, projectRoot, structureRoot }?: ProjectStructureConfig): Promise<Array<TypedFlatConfigItem>>;
28354
+ //#endregion
28285
28355
  //#region src/eslint/configs/promise.d.ts
28286
28356
  declare function promise(): Array<TypedFlatConfigItem>;
28287
28357
  //#endregion
@@ -28388,4 +28458,4 @@ declare function unicorn({ complementIgnores, nameReplacements, roblox, root: cu
28388
28458
  //#region src/eslint/configs/yaml.d.ts
28389
28459
  declare function yaml({ files, overrides, stylistic }?: OptionsFiles & OptionsOverrides & OptionsStylistic): Promise<Array<TypedFlatConfigItem>>;
28390
28460
  //#endregion
28391
- export { type Awaitable, type ConfigNames, type ConfigSettings, type FlatConfigComposer, GLOB_ALL_JSON, GLOB_ALL_SRC, GLOB_BIN, GLOB_BUILD_CONFIGS, 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, type JsdocOptions, MARKDOWN_PROCESSOR_NAME, type NamedFlatConfigItem, type NamedOptionsConfig, type NamingConfig, type NamingSelector, type OptionsComponentExtensions, type OptionsConfig, type OptionsE18e, type OptionsFiles, type OptionsFilesTypeAware, type OptionsFormatters, type OptionsHasRoblox, type OptionsHasTypeScript, type OptionsIsInEditor, type OptionsJest, type OptionsOverrides, type OptionsOverridesTypeAware, type OptionsPnpm, type OptionsProjectType, type OptionsStylistic, type OptionsTestFramework, type OptionsTypeScriptErasableOnly, type OptionsTypeScriptParserOptions, type OptionsTypeScriptWithTypes, type OptionsTypescript, type OptionsUnicorn, type OptionsVitest, type OxfmtOptions, type PerfectionistConfig, type PrettierOptions, ROBLOX_ALLOWED_WORDS, type ReactConfig, type ReactSettings, type Rules, type SpellCheckConfig, type StylisticConfig, StylisticConfigDefaults, type TypedFlatConfigItem, comments, isentinel as default, isentinel, defaultPluginRenaming, disables, e18e, eslintPlugin, flawless, gitignore, ignores$1 as ignores, imports, isAgentAutofixDisabled, 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 };
28461
+ export { type Awaitable, type ConfigNames, type ConfigSettings, type FlatConfigComposer, GLOB_ALL_JSON, GLOB_ALL_SRC, GLOB_BIN, GLOB_BUILD_CONFIGS, 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, type JsdocOptions, MARKDOWN_PROCESSOR_NAME, type NamedFlatConfigItem, type NamedOptionsConfig, type NamingConfig, type NamingSelector, type OptionsComponentExtensions, type OptionsConfig, type OptionsE18e, type OptionsFiles, type OptionsFilesTypeAware, type OptionsFormatters, type OptionsHasRoblox, type OptionsHasTypeScript, type OptionsIsInEditor, type OptionsJest, type OptionsOverrides, type OptionsOverridesTypeAware, type OptionsPnpm, type OptionsProjectType, type OptionsStylistic, type OptionsTestFramework, type OptionsTypeScriptErasableOnly, type OptionsTypeScriptParserOptions, type OptionsTypeScriptWithTypes, type OptionsTypescript, type OptionsUnicorn, type OptionsVitest, type OxfmtOptions, type PerfectionistConfig, type PrettierOptions, type ProjectStructureConfig, ROBLOX_ALLOWED_WORDS, type ReactConfig, type ReactSettings, type Rules, type SpellCheckConfig, type StylisticConfig, StylisticConfigDefaults, type TypedFlatConfigItem, comments, isentinel as default, isentinel, defaultPluginRenaming, disables, e18e, eslintPlugin, flawless, gitignore, ignores$1 as ignores, imports, isAgentAutofixDisabled, isInAgentSession, isInEditorEnvironment, isInGitHooksOrLintStaged, javascript, jsdoc, jsonc, loadSmallRulesPlugin, markdown, naming, node, oxfmt, packageJson, perfectionist, pnpm, projectStructure, 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
@@ -1576,8 +1576,10 @@ function disables({ root, workspaceRoot }) {
1576
1576
  rules: {
1577
1577
  "antfu/no-top-level-await": "off",
1578
1578
  "no-console": "off",
1579
+ "roblox/no-null": "off",
1579
1580
  "small-rules/require-async-suffix": "off",
1580
- "ts/explicit-function-return-type": "off"
1581
+ "ts/explicit-function-return-type": "off",
1582
+ "unicorn/no-null": "off"
1581
1583
  }
1582
1584
  },
1583
1585
  {
@@ -1640,7 +1642,8 @@ function disables({ root, workspaceRoot }) {
1640
1642
  "import/no-default-export": "off",
1641
1643
  "small-rules/require-async-suffix": "off",
1642
1644
  "sonar/file-name-differ-from-class": "off",
1643
- "unicorn/filename-case": "off"
1645
+ "unicorn/filename-case": "off",
1646
+ "unicorn/no-null": "off"
1644
1647
  }
1645
1648
  },
1646
1649
  {
@@ -1886,7 +1889,7 @@ async function flawless(options = {}, prettierOptions = {}) {
1886
1889
  ];
1887
1890
  }
1888
1891
  //#endregion
1889
- //#region ../../../setup-pnpm/node_modules/.bin/store/v11/links/@eslint/compat/2.1.0/b781043dbe4db6d501df442031a61cb3b4cc6aca6bc33271bed3c8c8e1ad757d/node_modules/@eslint/compat/dist/esm/index.js
1892
+ //#region ../../../setup-pnpm/node_modules/.bin/store/v11/links/@eslint/compat/2.1.0/90f1da88f603538864cab10657b202d55290237a3b641df9ac72b842a3be07f2/node_modules/@eslint/compat/dist/esm/index.js
1890
1893
  /**
1891
1894
  * @fileoverview Ignore file utilities for the compat package.
1892
1895
  * @author Nicholas C. Zakas
@@ -1915,7 +1918,7 @@ function convertIgnorePatternToMinimatch(pattern) {
1915
1918
  }
1916
1919
  var init_esm$1 = __esmMin((() => {}));
1917
1920
  //#endregion
1918
- //#region ../../../setup-pnpm/node_modules/.bin/store/v11/links/@/eslint-config-flat-gitignore/2.3.0/1320bd07004d149fd5c550ffda02aec5ecff7717d8f1602b7e9e51dffe8368d5/node_modules/eslint-config-flat-gitignore/dist/index.mjs
1921
+ //#region ../../../setup-pnpm/node_modules/.bin/store/v11/links/@/eslint-config-flat-gitignore/2.3.0/0a594adbd94ca75e2950b1104992894d49bc93f4f2bc935ad192d7104e4c271e/node_modules/eslint-config-flat-gitignore/dist/index.mjs
1919
1922
  var dist_exports = /* @__PURE__ */ __exportAll$2({ default: () => ignore });
1920
1923
  function toArray(array) {
1921
1924
  array = array ?? [];
@@ -3947,6 +3950,68 @@ async function detectCatalogUsage() {
3947
3950
  return yaml.includes("catalog:") || yaml.includes("catalogs:");
3948
3951
  }
3949
3952
  //#endregion
3953
+ //#region src/eslint/configs/project-structure.ts
3954
+ init_globs();
3955
+ /** Stands for the extension of the file a rule matched. */
3956
+ const EXTENSION_TOKEN = "{ext}";
3957
+ const DEFAULT_ENFORCE_EXISTENCE = [`{node-name}.spec.${EXTENSION_TOKEN}`];
3958
+ /** Reusable-rule id for "a folder of any name". */
3959
+ const ANY_FOLDER = "isentinelAnyFolder";
3960
+ async function projectStructure({ enforceExistence = DEFAULT_ENFORCE_EXISTENCE, files = [GLOB_SRC], ignores = [
3961
+ ...GLOB_TESTS,
3962
+ GLOB_DTS,
3963
+ ...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
+ 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
+ }];
3989
+ }
3990
+ /**
3991
+ * The rules matching the files inside one folder, in the order
3992
+ * `folder-structure` tries them - first match wins.
3993
+ *
3994
+ * Every entry is permissive: `structure` is required by the rule and an
3995
+ * unmatched node is an error, so anything less would turn the co-location
3996
+ * check into whole-tree naming enforcement.
3997
+ *
3998
+ * @param templates - The `enforceExistence` templates, before substitution.
3999
+ * @returns The child rules, ending in the recursive folder rule.
4000
+ */
4001
+ function buildChildren(templates) {
4002
+ const children = templates.map((template) => {
4003
+ return { name: (template.split("/").pop() ?? template).replaceAll(/\{[^{}]*\}/g, "*") };
4004
+ });
4005
+ for (const extension of GLOB_SRC_EXTENSIONS) children.push({
4006
+ name: `*.${extension}`,
4007
+ enforceExistence: templates.map((template) => {
4008
+ return template.replaceAll(EXTENSION_TOKEN, () => extension);
4009
+ })
4010
+ });
4011
+ children.push({ ruleId: ANY_FOLDER });
4012
+ return children;
4013
+ }
4014
+ //#endregion
3950
4015
  //#region src/rules/promise.ts
3951
4016
  /**
3952
4017
  * Promise rules shared between the ESLint and oxlint factories.
@@ -6154,7 +6219,7 @@ async function unicorn({ complementIgnores, nameReplacements, roblox = true, roo
6154
6219
  ];
6155
6220
  }
6156
6221
  //#endregion
6157
- //#region ../../../setup-pnpm/node_modules/.bin/store/v11/links/@/eslint-plugin-yml/3.5.0/286798c5ceb234dddb418d7e51fa7c87c58efb51bf8e94fcfd0ad11404fe81c8/node_modules/eslint-plugin-yml/lib/chunk-CfYAbeIz.mjs
6222
+ //#region ../../../setup-pnpm/node_modules/.bin/store/v11/links/@/eslint-plugin-yml/3.5.0/a00fc88a04d0f2081267792f61f05ad4ceff0f52e1ff41acbbc866518b4fd9c1/node_modules/eslint-plugin-yml/lib/chunk-CfYAbeIz.mjs
6158
6223
  var __defProp$1, __exportAll$1;
6159
6224
  var init_chunk_CfYAbeIz = __esmMin((() => {
6160
6225
  __defProp$1 = Object.defineProperty;
@@ -9337,7 +9402,7 @@ var init_lib$1 = __esmMin((() => {
9337
9402
  ({ ...meta_exports$1 });
9338
9403
  }));
9339
9404
  //#endregion
9340
- //#region ../../../setup-pnpm/node_modules/.bin/store/v11/links/@/eslint-plugin-yml/3.5.0/286798c5ceb234dddb418d7e51fa7c87c58efb51bf8e94fcfd0ad11404fe81c8/node_modules/eslint-plugin-yml/lib/index.mjs
9405
+ //#region ../../../setup-pnpm/node_modules/.bin/store/v11/links/@/eslint-plugin-yml/3.5.0/a00fc88a04d0f2081267792f61f05ad4ceff0f52e1ff41acbbc866518b4fd9c1/node_modules/eslint-plugin-yml/lib/index.mjs
9341
9406
  var lib_exports = /* @__PURE__ */ __exportAll$2({
9342
9407
  configs: () => configs,
9343
9408
  default: () => src_default,
@@ -15344,6 +15409,7 @@ const typeAwarePrefixes = /* @__PURE__ */ new Set([
15344
15409
  "package-json",
15345
15410
  "perfectionist",
15346
15411
  "pnpm",
15412
+ "project-structure",
15347
15413
  "promise",
15348
15414
  "react",
15349
15415
  "react-jsx",
@@ -19919,7 +19985,7 @@ const flatConfigProps = [
19919
19985
  "settings"
19920
19986
  ];
19921
19987
  async function isentinel(options, ...userConfigs) {
19922
- 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;
19988
+ 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, projectStructure: enableProjectStructure = false, react: enableReact = false, root: customRootGlobs, spellCheck: enableSpellCheck, toml: enableToml = true, yaml: enableYaml = true } = options;
19923
19989
  const rootGlobs = mergeGlobs(GLOB_ROOT, customRootGlobs);
19924
19990
  const enableRoblox = options.roblox !== false;
19925
19991
  const reactOptions = resolveSubOptions(options, "react");
@@ -20042,6 +20108,7 @@ async function isentinel(options, ...userConfigs) {
20042
20108
  ...testOptions
20043
20109
  }));
20044
20110
  }
20111
+ if (enableProjectStructure !== false) configs.push(projectStructure(resolveSubOptions(options, "projectStructure")));
20045
20112
  if (enableReact !== false) configs.push(react({
20046
20113
  ...resolveSubOptions(options, "react"),
20047
20114
  ...getOverrides(options, "react"),
@@ -20169,4 +20236,4 @@ async function isentinel(options, ...userConfigs) {
20169
20236
  return composer;
20170
20237
  }
20171
20238
  //#endregion
20172
- export { GLOB_ALL_JSON, GLOB_ALL_SRC, GLOB_BIN, GLOB_BUILD_CONFIGS, 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, MARKDOWN_PROCESSOR_NAME, ROBLOX_ALLOWED_WORDS, StylisticConfigDefaults, comments, isentinel as default, isentinel, defaultPluginRenaming, disables, e18e, eslintPlugin, flawless, gitignore, ignores, imports, isAgentAutofixDisabled, 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 };
20239
+ export { GLOB_ALL_JSON, GLOB_ALL_SRC, GLOB_BIN, GLOB_BUILD_CONFIGS, 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, MARKDOWN_PROCESSOR_NAME, ROBLOX_ALLOWED_WORDS, StylisticConfigDefaults, comments, isentinel as default, isentinel, defaultPluginRenaming, disables, e18e, eslintPlugin, flawless, gitignore, ignores, imports, isAgentAutofixDisabled, isInAgentSession, isInEditorEnvironment, isInGitHooksOrLintStaged, javascript, jsdoc, jsonc, loadSmallRulesPlugin, markdown, naming, node, oxfmt, packageJson, perfectionist, pnpm, projectStructure, promise, react, roblox, smallRules, sonarjs, sortCspell, sortGithubAction, sortMiseToml, sortPnpmWorkspace, sortRojoProject, sortTsconfig, spelling, stylistic, test, toml, typescript, unicorn, yaml };
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.42";
41
+ var version = "6.0.0-beta.43";
42
42
  //#endregion
43
43
  //#region src/lint-cli/lib/cli/types.ts
44
44
  /**
@@ -1104,17 +1104,22 @@ const PACKAGE_RESOLUTION = {
1104
1104
  */
1105
1105
  function applyHashBust(run, bust, hash) {
1106
1106
  if (hash === void 0) return {
1107
- busted: false,
1107
+ cleared: [],
1108
1108
  firstRun: false
1109
1109
  };
1110
1110
  const swap = swapState(statePath(run.cwd, bust.name, run.key), hash);
1111
1111
  if (swap !== "changed") return {
1112
- busted: false,
1112
+ cleared: [],
1113
1113
  firstRun: swap === "first"
1114
1114
  };
1115
- for (const base of bust.caches) fs.rmSync(path.resolve(run.cwd, cacheFileFor(base, run.key)), { force: true });
1115
+ const cleared = [];
1116
+ for (const base of bust.caches) {
1117
+ const cacheFilePath = path.resolve(run.cwd, cacheFileFor(base, run.key));
1118
+ fs.rmSync(cacheFilePath, { force: true });
1119
+ cleared.push(cacheFilePath);
1120
+ }
1116
1121
  return {
1117
- busted: true,
1122
+ cleared,
1118
1123
  firstRun: false
1119
1124
  };
1120
1125
  }
@@ -4854,6 +4859,17 @@ function buildGateValue(resolution, options) {
4854
4859
  //#region src/lint-cli/lib/typescript/affected.ts
4855
4860
  const warned = /* @__PURE__ */ new Set();
4856
4861
  /**
4862
+ * State-file base name for the builder's incremental state, shared by the
4863
+ * per-project path and the prefix {@link hasBuilderState} scans for.
4864
+ */
4865
+ const BUILD_INFO_STATE = "tsbuildinfo";
4866
+ /**
4867
+ * State-file base name for the gate paired with each buildinfo. Deliberately
4868
+ * not a `tsbuildinfo-` suffix, so anything enumerating a variant's buildinfo
4869
+ * files does not pick a gate up as one (see {@link gateStatePath}).
4870
+ */
4871
+ const GATE_STATE = "tsgate";
4872
+ /**
4857
4873
  * Compute the set of files whose type-aware lint results may have changed since
4858
4874
  * the previous run, using TypeScript's builder API. The builder does a native
4859
4875
  * shape-hash BFS: it recomputes each dependent's emitted-`.d.ts` shape hash and
@@ -4911,6 +4927,44 @@ function computeAffectedFiles({ key, cwd }, mode) {
4911
4927
  }
4912
4928
  }
4913
4929
  /**
4930
+ * Whether a previous run left builder state for this mode and config variant.
4931
+ *
4932
+ * The one thing a builder pass does that nothing else can is establish this
4933
+ * state. Its affected set is disposable — a caller with an empty cache re-lints
4934
+ * regardless — but a run that skips the builder while no state exists only
4935
+ * defers the cost onto the next run, which then reports `firstRun` and throws
4936
+ * its affected set away. A file edited in between would be re-linted on its own
4937
+ * mtime while its importers kept stale type-aware cache entries, which no later
4938
+ * run revisits. Callers that skip the builder as an optimisation ask this
4939
+ * first.
4940
+ *
4941
+ * Any one project's state is enough: {@link computeAffectedFiles} reports
4942
+ * `firstRun` only when no project at all had state, and a newly added project
4943
+ * contributes its whole file set rather than an empty one.
4944
+ *
4945
+ * @param run - The run context.
4946
+ * @param mode - The active ESLint type-aware mode (never `"off"` here).
4947
+ * @returns True when at least one buildinfo for this mode and variant exists.
4948
+ */
4949
+ function hasBuilderState({ key, cwd }, mode) {
4950
+ const prefix = `${path.basename(statePath(cwd, BUILD_INFO_STATE, modeSuffix(mode), key))}-`;
4951
+ try {
4952
+ return fs.readdirSync(stateDirectory(cwd)).some((name) => name.startsWith(prefix) && !name.endsWith(".tmp"));
4953
+ } catch {
4954
+ return false;
4955
+ }
4956
+ }
4957
+ /**
4958
+ * The state-file segment discriminating the type-aware mode a builder ran
4959
+ * under. Two modes resolve different programs, so their state cannot be shared.
4960
+ *
4961
+ * @param mode - The active ESLint type-aware mode (never `"off"` here).
4962
+ * @returns The file-name segment for that mode.
4963
+ */
4964
+ function modeSuffix(mode) {
4965
+ return mode === "only" ? "typeaware" : "full";
4966
+ }
4967
+ /**
4914
4968
  * Resolve the builder incremental-state (`.tsbuildinfo`) file for a mode and
4915
4969
  * config variant.
4916
4970
  *
@@ -4934,7 +4988,7 @@ function computeAffectedFiles({ key, cwd }, mode) {
4934
4988
  * @returns The absolute path to the mode's buildinfo file.
4935
4989
  */
4936
4990
  function builderStatePath(cwd, mode, key, projectId) {
4937
- return statePath(cwd, "tsbuildinfo", mode === "only" ? "typeaware" : "full", key, projectId);
4991
+ return statePath(cwd, BUILD_INFO_STATE, modeSuffix(mode), key, projectId);
4938
4992
  }
4939
4993
  /**
4940
4994
  * Resolve the gate state paired with one {@link builderStatePath} buildinfo:
@@ -4959,7 +5013,7 @@ function builderStatePath(cwd, mode, key, projectId) {
4959
5013
  * @returns The absolute path to the gate state file.
4960
5014
  */
4961
5015
  function gateStatePath(cwd, mode, key, projectId) {
4962
- return statePath(cwd, "tsgate", mode === "only" ? "typeaware" : "full", key, projectId);
5016
+ return statePath(cwd, GATE_STATE, modeSuffix(mode), key, projectId);
4963
5017
  }
4964
5018
  /**
4965
5019
  * Emit a warning at most once per distinct message. Keyed by message rather
@@ -5291,15 +5345,17 @@ function sizePasses(descriptors, run, inputs) {
5291
5345
  * @returns The number of dirty files.
5292
5346
  */
5293
5347
  function mutatingDirtyCount(descriptor, cacheLocation, targetFiles, { clearedCaches, run }) {
5294
- if (clearedCaches.has(cacheLocation)) return targetFiles.length;
5348
+ const buildsProgram = descriptor.invalidation !== "none";
5349
+ const mode = descriptor.invalidation === "only" ? "only" : void 0;
5350
+ if (clearedCaches.has(cacheLocation) && (!buildsProgram || hasBuilderState(run, mode))) return targetFiles.length;
5295
5351
  const cache = openCache(cacheLocation, run.ci);
5296
5352
  const dirty = new Set((cache?.getUpdatedFiles(targetFiles) ?? targetFiles).map((file) => normalizePath(file)));
5297
- if (descriptor.invalidation !== "none") {
5353
+ if (buildsProgram) {
5298
5354
  const outcome = applyTypeAwareInvalidation(run, {
5299
5355
  alreadyDirty: dirty,
5300
5356
  cache,
5301
5357
  cacheLocation,
5302
- mode: descriptor.invalidation === "only" ? "only" : void 0,
5358
+ mode,
5303
5359
  targetFiles
5304
5360
  });
5305
5361
  if (outcome.busted) return targetFiles.length;
@@ -5423,9 +5479,13 @@ function plan(options, run) {
5423
5479
  const canMutateCaches = mutate && options.cache;
5424
5480
  const hasTypeAwarePass = descriptors.some((descriptor) => descriptor.invalidation !== "none");
5425
5481
  const configHash = options.cache ? computeConfigHash(cwd, files.configFiles) : void 0;
5426
- if (canMutateCaches) applyHashBust(run, CONFIG_DRIFT, configHash);
5427
- if (canMutateCaches && hasTypeAwarePass) applyHashBust(run, PACKAGE_RESOLUTION, computePackageJsonHash(cwd));
5428
- const clearedCaches = new Set(canMutateCaches ? sweepStaleCaches(cwd, newestBustMtime) : []);
5482
+ const cleared = [];
5483
+ if (canMutateCaches) {
5484
+ cleared.push(...applyHashBust(run, CONFIG_DRIFT, configHash).cleared);
5485
+ if (hasTypeAwarePass) cleared.push(...applyHashBust(run, PACKAGE_RESOLUTION, computePackageJsonHash(cwd)).cleared);
5486
+ cleared.push(...sweepStaleCaches(cwd, newestBustMtime));
5487
+ }
5488
+ const clearedCaches = new Set(cleared);
5429
5489
  const sizingFiles = withoutIgnored(files, resolveIgnoredFiles(run, configHash, files.lintable));
5430
5490
  const inputs = {
5431
5491
  clearedCaches,
package/dist/oxlint.d.mts CHANGED
@@ -7787,6 +7787,21 @@ interface RuleOptions {
7787
7787
  * @see https://eslint.org/docs/latest/rules/preserve-caught-error
7788
7788
  */
7789
7789
  'preserve-caught-error'?: Linter.RuleEntry<PreserveCaughtError>;
7790
+ /**
7791
+ * Enforce advanced naming rules and prohibit the use of given selectors in a given file. Have full control over what your file can contain and the naming conventions it must follow.
7792
+ * @see https://github.com/Igorkowalski94/eslint-plugin-project-structure/wiki/project%E2%80%91structure-%E2%80%8Bfile%E2%80%91composition#root
7793
+ */
7794
+ 'project-structure/file-composition'?: Linter.RuleEntry<ProjectStructureFileComposition>;
7795
+ /**
7796
+ * Enforce rules on folder structure to keep your repository consistent, orderly and well thought out.
7797
+ * @see https://github.com/Igorkowalski94/eslint-plugin-project-structure/wiki/project%E2%80%91structure-%E2%80%8Bfolder%E2%80%91structure#root
7798
+ */
7799
+ 'project-structure/folder-structure'?: Linter.RuleEntry<ProjectStructureFolderStructure>;
7800
+ /**
7801
+ * A key principle of a healthy project is to prevent the creation of a massive dependency tree, where removing or editing one feature triggers a chain reaction that impacts the entire project. Create independent modules to keep your project scalable and easy to maintain. Get rid of dependencies between modules and create truly independent functionalities.
7802
+ * @see https://github.com/Igorkowalski94/eslint-plugin-project-structure/wiki/project%E2%80%91structure-%E2%80%8Bindependent%E2%80%91modules#root
7803
+ */
7804
+ 'project-structure/independent-modules'?: Linter.RuleEntry<ProjectStructureIndependentModules>;
7790
7805
  /**
7791
7806
  * Require returning inside each `then()` to create readable and reusable Promise chains.
7792
7807
  * @see https://github.com/eslint-community/eslint-plugin-promise/blob/main/docs/rules/always-return.md
@@ -20595,6 +20610,18 @@ type PreserveCaughtError = [] | [{
20595
20610
  argumentPosition: number;
20596
20611
  })[];
20597
20612
  }];
20613
+ // ----- project-structure/file-composition -----
20614
+ type ProjectStructureFileComposition = [] | [{
20615
+ [k: string]: unknown | undefined;
20616
+ }];
20617
+ // ----- project-structure/folder-structure -----
20618
+ type ProjectStructureFolderStructure = [] | [{
20619
+ [k: string]: unknown | undefined;
20620
+ }];
20621
+ // ----- project-structure/independent-modules -----
20622
+ type ProjectStructureIndependentModules = [] | [{
20623
+ [k: string]: unknown | undefined;
20624
+ }];
20598
20625
  // ----- promise/always-return -----
20599
20626
  type PromiseAlwaysReturn = [] | [{
20600
20627
  ignoreLastCallback?: boolean;
@@ -24764,6 +24791,37 @@ interface NamingConfig extends OptionsOverridesTypeAware {
24764
24791
  */
24765
24792
  selectorsTsx?: Array<NamingSelector>;
24766
24793
  }
24794
+ interface ProjectStructureConfig extends OptionsOverrides {
24795
+ /**
24796
+ * The files each linted source file has to sit next to, as
24797
+ * `enforceExistence` templates. Takes the plugin's name placeholders
24798
+ * (`{nodeName}`, `{NodeName}`, `{node-name}`, `{node_name}`,
24799
+ * `{NODE_NAME}`) plus `{ext}` for the extension of the file that matched.
24800
+ * A file already matching a template is exempt from it.
24801
+ *
24802
+ * @default ["{node-name}.spec.{ext}"]
24803
+ */
24804
+ enforceExistence?: Array<string> | string;
24805
+ /**
24806
+ * Files exempt from the check.
24807
+ *
24808
+ * @default [...GLOB_TESTS, GLOB_DTS, ...GLOB_BUILD_CONFIGS]
24809
+ */
24810
+ ignores?: Array<string>;
24811
+ /**
24812
+ * The folder the enforced paths resolve against, and where the plugin
24813
+ * writes `projectStructure.cache.json` when it reports.
24814
+ *
24815
+ * @default process.cwd()
24816
+ */
24817
+ projectRoot?: string;
24818
+ /**
24819
+ * Restricts the check to one subtree of `projectRoot`, as a relative path.
24820
+ *
24821
+ * @default undefined - the whole of `projectRoot`
24822
+ */
24823
+ structureRoot?: string;
24824
+ }
24767
24825
  interface OptionsTypeScriptParserOptions {
24768
24826
  /**
24769
24827
  * Glob patterns for files that should be type aware.
@@ -25112,6 +25170,14 @@ interface OptionsConfig extends OptionsComponentExtensions, OptionsProjectType {
25112
25170
  * @see https://github.com/antfu/pnpm-workspace-utils
25113
25171
  */
25114
25172
  pnpm?: boolean | OptionsPnpm;
25173
+ /**
25174
+ * Require every source file to have a co-located test file, via
25175
+ * [eslint-plugin-project-structure](https://github.com/Igorkowalski94/eslint-plugin-project-structure).
25176
+ *
25177
+ * @default false
25178
+ * @requires eslint-plugin-project-structure
25179
+ */
25180
+ projectStructure?: boolean | ProjectStructureConfig;
25115
25181
  /**
25116
25182
  * Enable react rules.
25117
25183
  *
@@ -33239,7 +33305,7 @@ type TypedOxlintConfigItem = Omit<OxlintOverride$1, "rules"> & {
33239
33305
  * Oxlint can only lint JS/TS files, so the JSON, YAML, TOML, Markdown and
33240
33306
  * pnpm-related options are omitted, along with ESLint-only options.
33241
33307
  */
33242
- type OxlintOptionsConfig = Omit<OptionsConfig, "autoRenamePlugins" | "flawless" | "jsonc" | "markdown" | "namedConfigs" | "naming" | "oxlint" | "pnpm" | "toml" | "yaml">;
33308
+ type OxlintOptionsConfig = Omit<OptionsConfig, "autoRenamePlugins" | "flawless" | "jsonc" | "markdown" | "namedConfigs" | "naming" | "oxlint" | "pnpm" | "projectStructure" | "toml" | "yaml">;
33243
33309
  /**
33244
33310
  * Options accepted by the oxlint factory function.
33245
33311
  *
package/dist/oxlint.mjs CHANGED
@@ -3237,6 +3237,7 @@ const enabledPresetRuleNames = /* @__PURE__ */ new Set([
3237
3237
  "prefer-rest-params",
3238
3238
  "prefer-spread",
3239
3239
  "prefer-template",
3240
+ "project-structure/folder-structure",
3240
3241
  "promise/always-return",
3241
3242
  "promise/catch-or-return",
3242
3243
  "promise/no-multiple-resolved",
@@ -6742,8 +6743,10 @@ function oxlintDisables({ configDirectory, root, workspaceRoot }) {
6742
6743
  rules: {
6743
6744
  "antfu/no-top-level-await": "off",
6744
6745
  "no-console": "off",
6746
+ "roblox/no-null": "off",
6745
6747
  "small-rules/require-async-suffix": "off",
6746
- "ts/explicit-function-return-type": "off"
6748
+ "ts/explicit-function-return-type": "off",
6749
+ "unicorn/no-null": "off"
6747
6750
  }
6748
6751
  }),
6749
6752
  ...createOxlintConfigs({
@@ -6811,7 +6814,8 @@ function oxlintDisables({ configDirectory, root, workspaceRoot }) {
6811
6814
  "import/no-default-export": "off",
6812
6815
  "small-rules/require-async-suffix": "off",
6813
6816
  "sonar/file-name-differ-from-class": "off",
6814
- "unicorn/filename-case": "off"
6817
+ "unicorn/filename-case": "off",
6818
+ "unicorn/no-null": "off"
6815
6819
  }
6816
6820
  }),
6817
6821
  ...createOxlintConfigs({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isentinel/eslint-config",
3
- "version": "6.0.0-beta.42",
3
+ "version": "6.0.0-beta.43",
4
4
  "description": "iSentinel's ESLint config",
5
5
  "keywords": [
6
6
  "eslint-config",
@@ -109,7 +109,7 @@
109
109
  "yargs": "18.0.0"
110
110
  },
111
111
  "devDependencies": {
112
- "@antfu/ni": "30.2.0",
112
+ "@antfu/ni": "30.5.0",
113
113
  "@eslint-react/shared": "5.17.3",
114
114
  "@eslint/config-inspector": "3.1.0",
115
115
  "@isentinel/tsconfig": "1.2.0",
@@ -122,12 +122,13 @@
122
122
  "@types/yargs": "17.0.35",
123
123
  "@vitest/eslint-plugin": "1.6.26",
124
124
  "bumpp": "12.0.0",
125
- "eslint": "10.7.0",
125
+ "eslint": "10.8.1",
126
126
  "eslint-plugin-erasable-syntax-only": "0.4.2",
127
127
  "eslint-plugin-eslint-plugin": "7.5.0",
128
128
  "eslint-plugin-jest": "29.16.0",
129
129
  "eslint-plugin-jest-extended": "3.0.1",
130
130
  "eslint-plugin-n": "18.2.2",
131
+ "eslint-plugin-project-structure": "3.14.3",
131
132
  "eslint-plugin-react-jsx": "5.17.3",
132
133
  "eslint-plugin-react-naming-convention": "5.17.3",
133
134
  "eslint-plugin-react-x": "5.17.3",
@@ -144,7 +145,7 @@
144
145
  "typescript": "6.0.3",
145
146
  "unplugin-unused": "0.5.7",
146
147
  "vitest": "4.1.10",
147
- "@isentinel/eslint-config": "6.0.0-beta.42"
148
+ "@isentinel/eslint-config": "6.0.0-beta.43"
148
149
  },
149
150
  "peerDependencies": {
150
151
  "@vitest/eslint-plugin": "^1.6.4",
@@ -153,6 +154,7 @@
153
154
  "eslint-plugin-jest": "^29.15.0",
154
155
  "eslint-plugin-jest-extended": "^3.0.0",
155
156
  "eslint-plugin-n": "^17.0.0 || ^18.0.0",
157
+ "eslint-plugin-project-structure": "^3.14.0",
156
158
  "eslint-plugin-react-jsx": "^5.10.0",
157
159
  "eslint-plugin-react-naming-convention": "^5.10.0",
158
160
  "eslint-plugin-react-x": "^5.10.0",
@@ -176,6 +178,9 @@
176
178
  "eslint-plugin-n": {
177
179
  "optional": true
178
180
  },
181
+ "eslint-plugin-project-structure": {
182
+ "optional": true
183
+ },
179
184
  "eslint-plugin-react-jsx": {
180
185
  "optional": true
181
186
  },