@forthtilliath/eslint-config 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -63,19 +63,25 @@ import { angularConfig } from "@forthtilliath/eslint-config/angular";
63
63
  export default angularConfig;
64
64
  ```
65
65
 
66
- All five are **named** exports — a default import (`import config from "..."`)
66
+ ```ts
67
+ // A React Native / Expo app
68
+ import { reactNativeConfig } from "@forthtilliath/eslint-config/react-native";
69
+ export default reactNativeConfig;
70
+ ```
71
+
72
+ All six are **named** exports — a default import (`import config from "..."`)
67
73
  resolves to the whole module namespace object instead of the config array and
68
74
  crashes ESLint's flat-config loader outright. Two of the five variants had
69
75
  exactly this bug at one point; use the named-import form above.
70
76
 
71
77
  Each variant is additive: `reactConfig`/`angularConfig` extend `baseConfig`,
72
- `nextJsConfig`/`storybookConfig` extend `reactConfig`.
78
+ `nextJsConfig`/`storybookConfig`/`reactNativeConfig` extend `reactConfig`.
73
79
 
74
80
  ### Customizing a variant
75
81
 
76
82
  Each variant is also exported as a factory (`createBaseConfig`,
77
83
  `createReactConfig`, `createNextJsConfig`, `createStorybookConfig`,
78
- `createAngularConfig`) taking an options object — the plain `baseConfig`,
84
+ `createAngularConfig`, `createReactNativeConfig`) taking an options object — the plain `baseConfig`,
79
85
  `reactConfig`, etc. exports above are just that factory called with no
80
86
  arguments. Pass options to opt out of a default:
81
87
 
@@ -93,15 +99,38 @@ import { createBaseConfig } from "@forthtilliath/eslint-config";
93
99
  export default createBaseConfig({ strict: false, turbo: false });
94
100
  ```
95
101
 
96
- | Option | Default | Effect |
97
- | ---------- | ------- | ------------------------------------------------------------------------------ |
98
- | `prettier` | `true` | Append `eslint-config-prettier` at the end. |
99
- | `strict` | `true` | Use `strictTypeChecked`/`stylisticTypeChecked` instead of plain `recommended`. |
100
- | `turbo` | `true` | Enable `eslint-plugin-turbo`'s `no-undeclared-env-vars` rule. |
102
+ | Option | Default | Effect |
103
+ | ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
104
+ | `prettier` | `true` | Append `eslint-config-prettier` at the end. |
105
+ | `strict` | `true` | Use `strictTypeChecked`/`stylisticTypeChecked` instead of plain `recommended`. |
106
+ | `turbo` | `true` | Enable `eslint-plugin-turbo`'s `no-undeclared-env-vars` rule. |
107
+ | `a11y` | `true`\* | _(react/nextjs/storybook/react-native)_ Enable `eslint-plugin-jsx-a11y`'s recommended rules. \*Defaults to `false` in `createReactNativeConfig` (jsx-a11y targets DOM semantics, which RN doesn't have). |
108
+ | `i18n` | `false` | _(react/nextjs/storybook/react-native)_ Enable `eslint-plugin-i18next`'s `no-literal-string` rule. |
109
+ | `testingLibrary` | `false` | _(react/nextjs/storybook/react-native)_ Enable `eslint-plugin-testing-library` + `eslint-plugin-jest-dom` on test files. |
101
110
 
102
111
  Options are forwarded down the chain, so `createNextJsConfig({ prettier: false })`
103
112
  also disables Prettier in the `reactConfig`/`baseConfig` layers it builds on.
104
113
 
114
+ `createReactNativeConfig` also always adds `eslint-plugin-react-native`
115
+ (`no-unused-styles`, `no-single-element-style-arrays`,
116
+ `split-platform-components`, `no-raw-text`; `no-inline-styles` and
117
+ `sort-styles` stay off — stylistic, not correctness) and the RN/Metro-injected
118
+ `__DEV__` global — these aren't behind an option, unlike the toggles above.
119
+
120
+ ```ts
121
+ // Full i18n project (next-intl, react-intl...): forbid hardcoded JSX strings
122
+ import { createNextJsConfig } from "@forthtilliath/eslint-config/nextjs";
123
+ export default createNextJsConfig({ i18n: true });
124
+ ```
125
+
126
+ ```ts
127
+ // React Native / Expo, customized: same options as createReactConfig, plus
128
+ // eslint-plugin-react-native's rules (a11y is off by default here, since
129
+ // jsx-a11y targets DOM semantics and doesn't apply to RN)
130
+ import { createReactNativeConfig } from "@forthtilliath/eslint-config/react-native";
131
+ export default createReactNativeConfig({ testingLibrary: true });
132
+ ```
133
+
105
134
  ### Typed linting and non-project files
106
135
 
107
136
  Rules that need type information (`consistent-type-exports`,
package/dist/nextjs.d.ts CHANGED
@@ -3,10 +3,10 @@
3
3
  * {@link createReactConfig} (same React/naming-convention rules, plus
4
4
  * Next.js's own plugin).
5
5
  *
6
- * @param {import("./base.js").BaseConfigOptions} [options]
6
+ * @param {import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions} [options]
7
7
  * @returns {import("eslint").Linter.Config[]}
8
8
  * */
9
- export function createNextJsConfig(options?: import("./base.js").BaseConfigOptions): import("eslint").Linter.Config[];
9
+ export function createNextJsConfig(options?: import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions): import("eslint").Linter.Config[];
10
10
  /**
11
11
  * A custom ESLint configuration for Next.js applications, with the default
12
12
  * options. Use `createNextJsConfig(options)` instead to customize it.
@@ -1 +1 @@
1
- {"version":3,"file":"nextjs.d.ts","sourceRoot":"","sources":["../src/nextjs.js"],"names":[],"mappings":"AAKA;;;;;;;KAOK;AACL,6CAHW,OAAO,WAAW,EAAE,iBAAiB,GACnC,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CA6C5C;AAED;;;;;KAKK;AACL,2BAFU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAEO"}
1
+ {"version":3,"file":"nextjs.d.ts","sourceRoot":"","sources":["../src/nextjs.js"],"names":[],"mappings":"AAKA;;;;;;;KAOK;AACL,6CAHW,OAAO,WAAW,EAAE,iBAAiB,GAAG,OAAO,YAAY,EAAE,kBAAkB,GAC7E,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAmD5C;AAED;;;;;KAKK;AACL,2BAFU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAEO"}
package/dist/nextjs.js CHANGED
@@ -6,7 +6,7 @@ import { createReactConfig } from "./react.js";
6
6
  * {@link createReactConfig} (same React/naming-convention rules, plus
7
7
  * Next.js's own plugin).
8
8
  *
9
- * @param {import("./base.js").BaseConfigOptions} [options]
9
+ * @param {import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions} [options]
10
10
  * @returns {import("eslint").Linter.Config[]}
11
11
  * */
12
12
  export function createNextJsConfig(options) {
@@ -46,6 +46,12 @@ export function createNextJsConfig(options) {
46
46
  "runtime",
47
47
  "preferredRegion",
48
48
  "maxDuration",
49
+ // icon.tsx / apple-icon.tsx / opengraph-image.tsx /
50
+ // twitter-image.tsx: same convention, for the image-generation
51
+ // route handlers.
52
+ "alt",
53
+ "size",
54
+ "contentType",
49
55
  ],
50
56
  },
51
57
  ],
@@ -0,0 +1,20 @@
1
+ /**
2
+ * eslint-plugin-react-native only ships a legacy (eslintrc) `configs.all`
3
+ * export, no flat-config preset — so the plugin and its rules are wired by
4
+ * hand here instead of spreading a ready-made flat config like storybook.js
5
+ * does.
6
+ */
7
+ /**
8
+ * A custom ESLint configuration for React Native / Expo apps.
9
+ *
10
+ * @param {import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions} [options]
11
+ * @returns {import("eslint").Linter.Config[]} */
12
+ export function createReactNativeConfig({ a11y, ...options }?: import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions): import("eslint").Linter.Config[];
13
+ /**
14
+ * A custom ESLint configuration for React Native / Expo apps, with the
15
+ * default options. Use `createReactNativeConfig(options)` instead to
16
+ * customize it.
17
+ *
18
+ * @type {import("eslint").Linter.Config[]} */
19
+ export const reactNativeConfig: import("eslint").Linter.Config[];
20
+ //# sourceMappingURL=react-native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-native.d.ts","sourceRoot":"","sources":["../src/react-native.js"],"names":[],"mappings":"AAKA;;;;;GAKG;AAEH;;;;iDAIiD;AACjD,+DAFW,OAAO,WAAW,EAAE,iBAAiB,GAAG,OAAO,YAAY,EAAE,kBAAkB,GAC7E,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CA+B5C;AAED;;;;;8CAK8C;AAC9C,gCADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CACiB"}
@@ -0,0 +1,52 @@
1
+ import { defineConfig } from "eslint/config";
2
+ import pluginReactNative from "eslint-plugin-react-native";
3
+ import { createReactConfig } from "./react.js";
4
+ /**
5
+ * eslint-plugin-react-native only ships a legacy (eslintrc) `configs.all`
6
+ * export, no flat-config preset — so the plugin and its rules are wired by
7
+ * hand here instead of spreading a ready-made flat config like storybook.js
8
+ * does.
9
+ */
10
+ /**
11
+ * A custom ESLint configuration for React Native / Expo apps.
12
+ *
13
+ * @param {import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions} [options]
14
+ * @returns {import("eslint").Linter.Config[]} */
15
+ export function createReactNativeConfig({ a11y = false, ...options } = {}) {
16
+ return defineConfig([
17
+ ...createReactConfig({ a11y, ...options }),
18
+ {
19
+ languageOptions: {
20
+ globals: {
21
+ // Injected by Metro/React Native at build time, dead-code-eliminated
22
+ // in release builds — not part of globals.node or globals.browser.
23
+ __DEV__: "readonly",
24
+ },
25
+ },
26
+ },
27
+ {
28
+ plugins: {
29
+ "react-native": pluginReactNative,
30
+ },
31
+ rules: {
32
+ "react-native/no-unused-styles": "error",
33
+ "react-native/no-single-element-style-arrays": "error",
34
+ "react-native/split-platform-components": "error",
35
+ "react-native/no-raw-text": "error",
36
+ // Off by default: both are stylistic preferences rather than
37
+ // correctness checks, and inline styles in particular are a common,
38
+ // legitimate RN pattern (one-off layout tweaks) that Prettier already
39
+ // keeps readable.
40
+ "react-native/no-inline-styles": "off",
41
+ "react-native/sort-styles": "off",
42
+ },
43
+ },
44
+ ]);
45
+ }
46
+ /**
47
+ * A custom ESLint configuration for React Native / Expo apps, with the
48
+ * default options. Use `createReactNativeConfig(options)` instead to
49
+ * customize it.
50
+ *
51
+ * @type {import("eslint").Linter.Config[]} */
52
+ export const reactNativeConfig = createReactNativeConfig();
package/dist/react.d.ts CHANGED
@@ -1,14 +1,56 @@
1
+ /**
2
+ * @typedef {object} ReactConfigOptions
3
+ * @property {boolean} [a11y=true] - Enable eslint-plugin-jsx-a11y's
4
+ * recommended rules.
5
+ * @property {boolean} [i18n=false] - Enable eslint-plugin-i18next's
6
+ * `no-literal-string` rule, forbidding any hardcoded string in JSX. Only
7
+ * makes sense for a project fully committed to i18n (e.g. driven by
8
+ * next-intl) — off by default since it flags every literal otherwise.
9
+ * @property {boolean} [testingLibrary=false] - Enable
10
+ * eslint-plugin-testing-library's React rules and eslint-plugin-jest-dom's
11
+ * recommended rules, scoped to test files only. Off by default:
12
+ * `testing-library/no-manual-cleanup` in particular assumes
13
+ * `@testing-library/*`'s automatic `afterEach(cleanup)` is active, which
14
+ * only happens when `afterEach` is a real global (vitest's `test.globals:
15
+ * true`, or Jest) — without that, a manual `cleanup()` call is genuinely
16
+ * required and this rule would be a false positive.
17
+ */
1
18
  /**
2
19
  * A custom ESLint configuration for libraries that use React.
3
20
  *
4
- * @param {import("./base.js").BaseConfigOptions} [options]
21
+ * @param {import("./base.js").BaseConfigOptions & ReactConfigOptions} [options]
5
22
  * @returns {import("eslint").Linter.Config[]}
6
23
  */
7
- export function createReactConfig(options?: import("./base.js").BaseConfigOptions): import("eslint").Linter.Config[];
24
+ export function createReactConfig({ a11y, i18n, testingLibrary, ...options }?: import("./base.js").BaseConfigOptions & ReactConfigOptions): import("eslint").Linter.Config[];
8
25
  /**
9
26
  * A custom ESLint configuration for libraries that use React, with the
10
27
  * default options. Use `createReactConfig(options)` instead to customize it.
11
28
  *
12
29
  * @type {import("eslint").Linter.Config[]} */
13
30
  export const reactConfig: import("eslint").Linter.Config[];
31
+ export type ReactConfigOptions = {
32
+ /**
33
+ * - Enable eslint-plugin-jsx-a11y's
34
+ * recommended rules.
35
+ */
36
+ a11y?: boolean | undefined;
37
+ /**
38
+ * - Enable eslint-plugin-i18next's
39
+ * `no-literal-string` rule, forbidding any hardcoded string in JSX. Only
40
+ * makes sense for a project fully committed to i18n (e.g. driven by
41
+ * next-intl) — off by default since it flags every literal otherwise.
42
+ */
43
+ i18n?: boolean | undefined;
44
+ /**
45
+ * - Enable
46
+ * eslint-plugin-testing-library's React rules and eslint-plugin-jest-dom's
47
+ * recommended rules, scoped to test files only. Off by default:
48
+ * `testing-library/no-manual-cleanup` in particular assumes
49
+ * `@testing-library/*`'s automatic `afterEach(cleanup)` is active, which
50
+ * only happens when `afterEach` is a real global (vitest's `test.globals:
51
+ * true`, or Jest) — without that, a manual `cleanup()` call is genuinely
52
+ * required and this rule would be a false positive.
53
+ */
54
+ testingLibrary?: boolean | undefined;
55
+ };
14
56
  //# sourceMappingURL=react.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.js"],"names":[],"mappings":"AASA;;;;;GAKG;AACH,4CAHW,OAAO,WAAW,EAAE,iBAAiB,GACnC,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAuE5C;AAED;;;;8CAI8C;AAC9C,0BADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CACK"}
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.js"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;GAKG;AACH,+EAHW,OAAO,WAAW,EAAE,iBAAiB,GAAG,kBAAkB,GACxD,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CA+F5C;AAED;;;;8CAI8C;AAC9C,0BADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CACK"}
package/dist/react.js CHANGED
@@ -1,19 +1,66 @@
1
1
  import pluginEslintReact from "@eslint-react/eslint-plugin";
2
2
  import { defineConfig } from "eslint/config";
3
+ import pluginI18next from "eslint-plugin-i18next";
4
+ import pluginJestDom from "eslint-plugin-jest-dom";
5
+ import pluginJsxA11y from "eslint-plugin-jsx-a11y";
3
6
  import pluginReact from "eslint-plugin-react";
4
7
  import pluginReactHooks from "eslint-plugin-react-hooks";
5
8
  import pluginReactRefresh from "eslint-plugin-react-refresh";
9
+ import pluginTestingLibrary from "eslint-plugin-testing-library";
6
10
  import globals from "globals";
7
11
  import { createBaseConfig } from "./base.js";
12
+ /**
13
+ * Any file matching this is a test file, whatever runner or naming
14
+ * convention is used to suffix it (`.test.`, `.spec.`, `.dom.test.`...) —
15
+ * `*` matches across dots, so `*.test.tsx` already covers `foo.dom.test.tsx`.
16
+ * @type {string[]}
17
+ */
18
+ const TEST_FILE_GLOBS = ["**/*.{test,spec}.{ts,tsx,js,jsx}"];
19
+ /**
20
+ * @typedef {object} ReactConfigOptions
21
+ * @property {boolean} [a11y=true] - Enable eslint-plugin-jsx-a11y's
22
+ * recommended rules.
23
+ * @property {boolean} [i18n=false] - Enable eslint-plugin-i18next's
24
+ * `no-literal-string` rule, forbidding any hardcoded string in JSX. Only
25
+ * makes sense for a project fully committed to i18n (e.g. driven by
26
+ * next-intl) — off by default since it flags every literal otherwise.
27
+ * @property {boolean} [testingLibrary=false] - Enable
28
+ * eslint-plugin-testing-library's React rules and eslint-plugin-jest-dom's
29
+ * recommended rules, scoped to test files only. Off by default:
30
+ * `testing-library/no-manual-cleanup` in particular assumes
31
+ * `@testing-library/*`'s automatic `afterEach(cleanup)` is active, which
32
+ * only happens when `afterEach` is a real global (vitest's `test.globals:
33
+ * true`, or Jest) — without that, a manual `cleanup()` call is genuinely
34
+ * required and this rule would be a false positive.
35
+ */
8
36
  /**
9
37
  * A custom ESLint configuration for libraries that use React.
10
38
  *
11
- * @param {import("./base.js").BaseConfigOptions} [options]
39
+ * @param {import("./base.js").BaseConfigOptions & ReactConfigOptions} [options]
12
40
  * @returns {import("eslint").Linter.Config[]}
13
41
  */
14
- export function createReactConfig(options) {
42
+ export function createReactConfig({ a11y = true, i18n = false, testingLibrary = false, ...options } = {}) {
15
43
  return defineConfig([
16
44
  ...createBaseConfig(options),
45
+ a11y ? pluginJsxA11y.flatConfigs.recommended : [],
46
+ i18n
47
+ ? {
48
+ plugins: { i18next: pluginI18next },
49
+ rules: { "i18next/no-literal-string": "error" },
50
+ }
51
+ : [],
52
+ testingLibrary
53
+ ? [
54
+ {
55
+ ...pluginTestingLibrary.configs["flat/react"],
56
+ files: TEST_FILE_GLOBS,
57
+ },
58
+ {
59
+ ...pluginJestDom.configs["flat/recommended"],
60
+ files: TEST_FILE_GLOBS,
61
+ },
62
+ ]
63
+ : [],
17
64
  pluginReact.configs.flat["jsx-runtime"],
18
65
  pluginEslintReact.configs["recommended-type-checked"],
19
66
  {
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * A custom ESLint configuration for libraries that use React and Storybook.
3
3
  *
4
- * @param {import("./base.js").BaseConfigOptions} [options]
4
+ * @param {import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions} [options]
5
5
  * @returns {import("eslint").Linter.Config[]} */
6
- export function createStorybookConfig(options?: import("./base.js").BaseConfigOptions): import("eslint").Linter.Config[];
6
+ export function createStorybookConfig(options?: import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions): import("eslint").Linter.Config[];
7
7
  /**
8
8
  * A custom ESLint configuration for libraries that use React and Storybook,
9
9
  * with the default options. Use `createStorybookConfig(options)` instead to
@@ -1 +1 @@
1
- {"version":3,"file":"storybook.d.ts","sourceRoot":"","sources":["../src/storybook.js"],"names":[],"mappings":"AAKA;;;;iDAIiD;AACjD,gDAFW,OAAO,WAAW,EAAE,iBAAiB,GACnC,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAiB5C;AAED;;;;;8CAK8C;AAC9C,8BADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CACa"}
1
+ {"version":3,"file":"storybook.d.ts","sourceRoot":"","sources":["../src/storybook.js"],"names":[],"mappings":"AAKA;;;;iDAIiD;AACjD,gDAFW,OAAO,WAAW,EAAE,iBAAiB,GAAG,OAAO,YAAY,EAAE,kBAAkB,GAC7E,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAiB5C;AAED;;;;;8CAK8C;AAC9C,8BADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CACa"}
package/dist/storybook.js CHANGED
@@ -4,7 +4,7 @@ import { createReactConfig } from "./react.js";
4
4
  /**
5
5
  * A custom ESLint configuration for libraries that use React and Storybook.
6
6
  *
7
- * @param {import("./base.js").BaseConfigOptions} [options]
7
+ * @param {import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions} [options]
8
8
  * @returns {import("eslint").Linter.Config[]} */
9
9
  export function createStorybookConfig(options) {
10
10
  return defineConfig([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forthtilliath/eslint-config",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -26,11 +26,16 @@
26
26
  "@next/eslint-plugin-next": "^16.3.0",
27
27
  "angular-eslint": "^21.4.0",
28
28
  "eslint-config-prettier": "^10.1.8",
29
+ "eslint-plugin-i18next": "^6.1.5",
30
+ "eslint-plugin-jest-dom": "^5.10.1",
31
+ "eslint-plugin-jsx-a11y": "^6.10.2",
29
32
  "eslint-plugin-react": "^7.37.5",
30
33
  "eslint-plugin-react-hooks": "^7.1.1",
34
+ "eslint-plugin-react-native": "^5.0.0",
31
35
  "eslint-plugin-react-refresh": "^0.5.4",
32
36
  "eslint-plugin-simple-import-sort": "^13.0.0",
33
37
  "eslint-plugin-storybook": "^9.1.20",
38
+ "eslint-plugin-testing-library": "^7.16.2",
34
39
  "eslint-plugin-turbo": "^2.10.9",
35
40
  "globals": "^17.9.0",
36
41
  "typescript-eslint": "^8.67.0"