@bastidood/eslint-config 0.4.0 → 0.6.1

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
@@ -14,26 +14,23 @@ pnpm add --save-dev @bastidood/eslint-config eslint typescript
14
14
  // eslint.config.ts
15
15
  import { compose } from '@bastidood/eslint-config';
16
16
  import { CORE } from '@bastidood/eslint-config/core';
17
- import { PROMISE } from '@bastidood/eslint-config/promise';
18
- import { UNICORN } from '@bastidood/eslint-config/unicorn';
19
17
  import { defineConfig } from 'eslint/config';
20
18
 
21
- export default defineConfig({ files: ['**/*.ts'] }, compose(CORE, UNICORN, PROMISE));
19
+ export default defineConfig({ files: ['**/*.ts'] }, compose(CORE));
22
20
  ```
23
21
 
24
22
  List `CORE` first.
25
23
 
26
24
  ## Entry Points
27
25
 
28
- | Entry point | Contents |
29
- | ---------------------------------- | ------------------------------------------------------------ |
30
- | `@bastidood/eslint-config` | `compose` |
31
- | `@bastidood/eslint-config/core` | ESLint core, `import`, and `@typescript-eslint` rules |
32
- | `@bastidood/eslint-config/unicorn` | `unicorn` rules |
33
- | `@bastidood/eslint-config/promise` | `promise` rules |
34
- | `@bastidood/eslint-config/node` | Node.js rules |
35
- | `@bastidood/eslint-config/vitest` | Vitest rules |
36
- | `@bastidood/eslint-config/jsdoc` | JSDoc rules for documentation that TypeScript does not cover |
26
+ | Entry point | Contents |
27
+ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
28
+ | `@bastidood/eslint-config` | `compose` |
29
+ | `@bastidood/eslint-config/core` | ESLint core, `import`, `jsdoc`, `promise`, `@typescript-eslint`, and `unicorn` rules |
30
+ | `@bastidood/eslint-config/node` | Node.js rules |
31
+ | `@bastidood/eslint-config/vitest` | Vitest rules |
32
+ | `@bastidood/eslint-config/tanstack/query` | Enables the slopstopper TanStack Query rules: `prefer-query-key-context`, `no-defaulted-query-key`, `no-standalone-mutate-async`, and `no-mutate-callback-cache-update` |
33
+ | `@bastidood/eslint-config/tanstack/table` | Enables the slopstopper TanStack Table rules: `prefer-table-atoms`, `require-table-state-pairs`, `no-render-scoped-table-schema`, and `require-exclusive-indeterminate`; bans `stockFeatures` and reading column templates outside `FlexRender` |
37
34
 
38
35
  ## Limitations
39
36
 
@@ -1,3 +1,4 @@
1
+ import { NewResolver } from "eslint-plugin-import-x";
1
2
  import { Linter } from "eslint";
2
3
  export interface RestrictedImportPath {
3
4
  readonly name: string;
@@ -29,24 +30,37 @@ export interface Restrictions {
29
30
  readonly properties?: RestrictedProperty[];
30
31
  readonly syntax?: RestrictedSyntax[];
31
32
  }
33
+ /** An `import-x/resolver-next` resolver. `eslint-import-resolver-typescript` widens `interfaceVersion` to `number`. */
34
+ export interface ImportResolver extends Omit<NewResolver, 'interfaceVersion'> {
35
+ readonly interfaceVersion: number;
36
+ }
32
37
  export interface ScopedRestrictions {
33
38
  readonly files: string[];
34
39
  readonly restrictions: Restrictions;
35
40
  }
36
41
  /**
37
42
  * An entry point: ESLint configuration objects plus the lists that `compose` concatenates, namely the
38
- * restriction lists and the non-standard file extensions that the TypeScript project service must resolve.
39
- * `configs` may nest a configuration list one level deep, such as a plugin's `recommended` list.
43
+ * restriction lists, the non-standard file extensions that the TypeScript project service must resolve, and the
44
+ * `import-x` settings lists. `configs` may nest a configuration list one level deep, such as a plugin's
45
+ * `recommended` list.
40
46
  */
41
47
  export interface Entry {
42
48
  readonly configs?: (Linter.Config | Linter.Config[])[];
43
49
  readonly extraFileExtensions?: string[];
50
+ /** The file extensions that the `import` rules follow, as in `import-x/extensions`. */
51
+ readonly importExtensions?: string[];
52
+ /** The folders whose modules the `import` rules treat as external, as in `import-x/external-module-folders`. */
53
+ readonly importExternalModuleFolders?: string[];
54
+ /** Maps the module name of a parser to the file extensions that it parses, as in `import-x/parsers`. */
55
+ readonly importParsers?: Record<string, string[]>;
56
+ /** The resolvers that the `import` rules try in order, as in `import-x/resolver-next`. */
57
+ readonly importResolvers?: ImportResolver[];
44
58
  readonly restrictions?: Restrictions;
45
59
  readonly scopedRestrictions?: ScopedRestrictions[];
46
60
  }
47
61
  /**
48
- * Compose entry points into one ESLint flat configuration for `extends`. Restriction lists are concatenated
49
- * instead of replaced, and every scoped configuration repeats the top-level restrictions so that it cannot
50
- * drop them.
62
+ * Compose entry points into one ESLint flat configuration for `extends`. Restriction lists, file extension
63
+ * lists, and `import-x` settings lists are concatenated instead of replaced, and every scoped configuration repeats the top-level restrictions
64
+ * so that it cannot drop them.
51
65
  */
52
66
  export declare function compose(...entries: Entry[]): Linter.Config[];
@@ -12,22 +12,21 @@ var RestrictionLists = class RestrictionLists {
12
12
  #globals = ["error"];
13
13
  #properties = ["error"];
14
14
  #syntax = ["error"];
15
- /** Append every list of an entry point's restrictions. */
16
15
  append({ importPaths, importPatterns, globals, properties, syntax }) {
17
- appendAll(this.#imports.paths, importPaths);
18
- appendAll(this.#imports.patterns, importPatterns);
19
- appendAll(this.#globals, globals);
20
- appendAll(this.#properties, properties);
21
- appendAll(this.#syntax, syntax);
16
+ appendAll$1(this.#imports.paths, importPaths);
17
+ appendAll$1(this.#imports.patterns, importPatterns);
18
+ appendAll$1(this.#globals, globals);
19
+ appendAll$1(this.#properties, properties);
20
+ appendAll$1(this.#syntax, syntax);
22
21
  }
23
22
  /** Copy these lists so that a scoped override can extend them without changing the top-level rules. */
24
23
  copy() {
25
24
  const copy = new RestrictionLists();
26
- appendAll(copy.#imports.paths, this.#imports.paths);
27
- appendAll(copy.#imports.patterns, this.#imports.patterns);
28
- appendAll(copy.#globals, this.#globals.values().drop(1));
29
- appendAll(copy.#properties, this.#properties.values().drop(1));
30
- appendAll(copy.#syntax, this.#syntax.values().drop(1));
25
+ appendAll$1(copy.#imports.paths, this.#imports.paths);
26
+ appendAll$1(copy.#imports.patterns, this.#imports.patterns);
27
+ appendAll$1(copy.#globals, this.#globals.values().drop(1));
28
+ appendAll$1(copy.#properties, this.#properties.values().drop(1));
29
+ appendAll$1(copy.#syntax, this.#syntax.values().drop(1));
31
30
  return copy;
32
31
  }
33
32
  importsRule() {
@@ -44,23 +43,38 @@ var RestrictionLists = class RestrictionLists {
44
43
  return this.#syntax.length === 1 ? null : this.#syntax;
45
44
  }
46
45
  };
47
- function appendAll(target, items) {
46
+ function appendAll$1(target, items) {
48
47
  if (typeof items === "undefined") return;
49
48
  for (const item of items) target.push(item);
50
49
  }
51
50
  // src/compose/index.ts
52
51
  /**
53
- * Compose entry points into one ESLint flat configuration for `extends`. Restriction lists are concatenated
54
- * instead of replaced, and every scoped configuration repeats the top-level restrictions so that it cannot
55
- * drop them.
52
+ * Compose entry points into one ESLint flat configuration for `extends`. Restriction lists, file extension
53
+ * lists, and `import-x` settings lists are concatenated instead of replaced, and every scoped configuration repeats the top-level restrictions
54
+ * so that it cannot drop them.
56
55
  */
57
56
  function compose(...entries) {
58
57
  const configs = [];
59
58
  const extraFileExtensions = [];
59
+ const importExtensions = [];
60
+ const importExternalModuleFolders = [];
61
+ const importParsers = {};
62
+ const importResolvers = [];
63
+ let hasImportParsers = false;
60
64
  const restrictions = new RestrictionLists();
61
- for (const { configs: entryConfigs, extraFileExtensions: entryExtensions, restrictions: entryRestrictions } of entries) {
65
+ for (const { configs: entryConfigs, extraFileExtensions: entryExtensions, importExtensions: entryImportExtensions, importExternalModuleFolders: entryImportExternalModuleFolders, importParsers: entryImportParsers, importResolvers: entryImportResolvers, restrictions: entryRestrictions } of entries) {
62
66
  appendConfigs(configs, entryConfigs);
63
- if (typeof entryExtensions !== "undefined") for (const extension of entryExtensions) extraFileExtensions.push(extension);
67
+ appendAll(extraFileExtensions, entryExtensions);
68
+ appendAll(importExtensions, entryImportExtensions);
69
+ appendAll(importExternalModuleFolders, entryImportExternalModuleFolders);
70
+ appendAll(importResolvers, entryImportResolvers);
71
+ if (typeof entryImportParsers !== "undefined") for (const parser in entryImportParsers) {
72
+ if (!Object.hasOwn(entryImportParsers, parser)) continue;
73
+ const extensions = importParsers[parser] ?? [];
74
+ appendAll(extensions, entryImportParsers[parser]);
75
+ importParsers[parser] = extensions;
76
+ hasImportParsers = true;
77
+ }
64
78
  if (typeof entryRestrictions !== "undefined") restrictions.append(entryRestrictions);
65
79
  }
66
80
  configs.push({
@@ -83,8 +97,31 @@ function compose(...entries) {
83
97
  name: "@bastidood/eslint-config/project-service",
84
98
  languageOptions: { parserOptions: { extraFileExtensions } }
85
99
  });
100
+ const importSettings = {};
101
+ let hasImportSettings = hasImportParsers;
102
+ if (hasImportParsers) importSettings["import-x/parsers"] = importParsers;
103
+ if (importExtensions.length > 0) {
104
+ importSettings["import-x/extensions"] = importExtensions;
105
+ hasImportSettings = true;
106
+ }
107
+ if (importExternalModuleFolders.length > 0) {
108
+ importSettings["import-x/external-module-folders"] = importExternalModuleFolders;
109
+ hasImportSettings = true;
110
+ }
111
+ if (importResolvers.length > 0) {
112
+ importSettings["import-x/resolver-next"] = importResolvers;
113
+ hasImportSettings = true;
114
+ }
115
+ if (hasImportSettings) configs.push({
116
+ name: "@bastidood/eslint-config/import",
117
+ settings: importSettings
118
+ });
86
119
  return configs;
87
120
  }
121
+ function appendAll(target, items) {
122
+ if (typeof items === "undefined") return;
123
+ for (const item of items) target.push(item);
124
+ }
88
125
  /** Append an entry point's configurations, flattening nested configuration lists one level. */
89
126
  function appendConfigs(target, configs) {
90
127
  if (typeof configs === "undefined") return;