@forthtilliath/eslint-config 0.9.0 → 0.10.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 +32 -18
- package/dist/base.d.ts +7 -5
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +8 -9
- package/dist/internal/optional-plugin.d.ts +9 -29
- package/dist/internal/optional-plugin.d.ts.map +1 -1
- package/dist/internal/optional-plugin.js +51 -38
- package/dist/internal/turborepo.d.ts +12 -0
- package/dist/internal/turborepo.d.ts.map +1 -0
- package/dist/internal/turborepo.js +20 -0
- package/dist/react-native.d.ts +20 -2
- package/dist/react-native.d.ts.map +1 -1
- package/dist/react-native.js +26 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +6 -14
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -32,24 +32,25 @@ the variant and options it actually uses. A React Native app doesn't get
|
|
|
32
32
|
`angular-eslint` (and the Angular CLI it pulls in), nor a TypeScript library
|
|
33
33
|
every React plugin.
|
|
34
34
|
|
|
35
|
-
| Variant / option
|
|
36
|
-
|
|
|
37
|
-
| `.` (base)
|
|
38
|
-
| `/react`
|
|
39
|
-
| `/react-native`
|
|
40
|
-
| `/nextjs`
|
|
41
|
-
| `/storybook`
|
|
42
|
-
| `/angular`
|
|
43
|
-
| `turbo: true
|
|
44
|
-
| `a11y: true`\*
|
|
45
|
-
| `i18n: true`
|
|
46
|
-
| `testingLibrary: true`
|
|
35
|
+
| Variant / option | Also install |
|
|
36
|
+
| ---------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
37
|
+
| `.` (base) | — |
|
|
38
|
+
| `/react` | `@eslint-react/eslint-plugin eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-refresh` |
|
|
39
|
+
| `/react-native` | the `/react` list + `eslint-plugin-react-native` |
|
|
40
|
+
| `/nextjs` | the `/react` list + `@next/eslint-plugin-next` |
|
|
41
|
+
| `/storybook` | the `/react` list + `eslint-plugin-storybook` |
|
|
42
|
+
| `/angular` | `angular-eslint` |
|
|
43
|
+
| `turbo: true`\*\* | `eslint-plugin-turbo` — \*\*default inside a Turborepo (a `turbo.json` in the working directory or above) |
|
|
44
|
+
| `a11y: true`\* | `eslint-plugin-jsx-a11y` — \*default for `/react`, `/nextjs`, `/storybook`; pass `a11y: false` to skip it |
|
|
45
|
+
| `i18n: true` | `eslint-plugin-i18next` |
|
|
46
|
+
| `testingLibrary: true` | `eslint-plugin-testing-library eslint-plugin-jest-dom` |
|
|
47
|
+
| `expo: true` | `eslint-plugin-expo` — `/react-native` only |
|
|
47
48
|
|
|
48
49
|
```bash
|
|
49
|
-
# e.g. a standalone React Native / Expo app, with createReactNativeConfig({
|
|
50
|
+
# e.g. a standalone React Native / Expo app, with createReactNativeConfig({ expo: true })
|
|
50
51
|
npm install --save-dev @forthtilliath/eslint-config eslint typescript \
|
|
51
52
|
@eslint-react/eslint-plugin eslint-plugin-react eslint-plugin-react-hooks \
|
|
52
|
-
eslint-plugin-react-refresh eslint-plugin-react-native
|
|
53
|
+
eslint-plugin-react-refresh eslint-plugin-react-native eslint-plugin-expo
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
A missing plugin fails with an explicit error naming the package to install
|
|
@@ -123,9 +124,21 @@ export default reactNativeConfig;
|
|
|
123
124
|
|
|
124
125
|
All six are **named** exports — a default import (`import config from "..."`)
|
|
125
126
|
resolves to the whole module namespace object instead of the config array and
|
|
126
|
-
crashes ESLint's flat-config loader outright. Two of the
|
|
127
|
+
crashes ESLint's flat-config loader outright. Two of the variants had
|
|
127
128
|
exactly this bug at one point; use the named-import form above.
|
|
128
129
|
|
|
130
|
+
The config loads from a CommonJS `eslint.config.js` too (the one Expo's
|
|
131
|
+
templates generate), through `require()` — no top-level `await` anywhere in
|
|
132
|
+
the package. That needs Node 20.19+ or 22.12+ (`require()` of an ES module):
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
// eslint.config.js (CommonJS)
|
|
136
|
+
const {
|
|
137
|
+
createReactNativeConfig,
|
|
138
|
+
} = require("@forthtilliath/eslint-config/react-native");
|
|
139
|
+
module.exports = createReactNativeConfig({ expo: true });
|
|
140
|
+
```
|
|
141
|
+
|
|
129
142
|
Each variant is additive: `reactConfig`/`angularConfig` extend `baseConfig`,
|
|
130
143
|
`nextJsConfig`/`storybookConfig`/`reactNativeConfig` extend `reactConfig`.
|
|
131
144
|
|
|
@@ -146,20 +159,21 @@ export default createReactConfig({ prettier: false });
|
|
|
146
159
|
```ts
|
|
147
160
|
// Fall back to typescript-eslint's `recommended` preset instead of
|
|
148
161
|
// `strictTypeChecked`/`stylisticTypeChecked` (e.g. while migrating an
|
|
149
|
-
// existing codebase)
|
|
162
|
+
// existing codebase).
|
|
150
163
|
import { createBaseConfig } from "@forthtilliath/eslint-config";
|
|
151
|
-
export default createBaseConfig({ strict: false
|
|
164
|
+
export default createBaseConfig({ strict: false });
|
|
152
165
|
```
|
|
153
166
|
|
|
154
167
|
| Option | Default | Effect |
|
|
155
168
|
| ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
156
169
|
| `prettier` | `true` | Append `eslint-config-prettier` at the end. |
|
|
157
170
|
| `strict` | `true` | Use `strictTypeChecked`/`stylisticTypeChecked` instead of plain `recommended`. |
|
|
158
|
-
| `turbo` |
|
|
171
|
+
| `turbo` | auto | Enable `eslint-plugin-turbo`'s `no-undeclared-env-vars` rule. Defaults to `true` when a `turbo.json` sits in ESLint's working directory or one of its parents, `false` otherwise. |
|
|
159
172
|
| `snakeCase` | `false` | Also accept `snake_case` variable names in `@typescript-eslint/naming-convention` (e.g. rows destructured straight from a Supabase/SQL database). |
|
|
160
173
|
| `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). |
|
|
161
174
|
| `i18n` | `false` | _(react/nextjs/storybook/react-native)_ Enable `eslint-plugin-i18next`'s `no-literal-string` rule. |
|
|
162
175
|
| `testingLibrary` | `false` | _(react/nextjs/storybook/react-native)_ Enable `eslint-plugin-testing-library` + `eslint-plugin-jest-dom` on test files. |
|
|
176
|
+
| `expo` | `false` | _(react-native)_ Enable the `eslint-plugin-expo` rules `eslint-config-expo` turns on: `use-dom-exports`, `no-env-var-destructuring`, `no-dynamic-env-var`. |
|
|
163
177
|
|
|
164
178
|
Options are forwarded down the chain, so `createNextJsConfig({ prettier: false })`
|
|
165
179
|
also disables Prettier in the `reactConfig`/`baseConfig` layers it builds on.
|
package/dist/base.d.ts
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
* `strictTypeChecked` + `stylisticTypeChecked` presets. Set to `false` to
|
|
9
9
|
* fall back to the plain `recommended` preset instead (useful while
|
|
10
10
|
* migrating an existing codebase that isn't ready for the strict rules).
|
|
11
|
-
* @property {boolean} [turbo
|
|
11
|
+
* @property {boolean} [turbo] - Enable eslint-plugin-turbo's
|
|
12
12
|
* `no-undeclared-env-vars` rule (needs `eslint-plugin-turbo` installed).
|
|
13
|
-
* Only relevant inside a Turborepo
|
|
14
|
-
*
|
|
13
|
+
* Only relevant inside a Turborepo: defaults to `true` when a `turbo.json`
|
|
14
|
+
* sits in ESLint's working directory or one of its parents, `false`
|
|
15
|
+
* otherwise.
|
|
15
16
|
* @property {boolean} [snakeCase=false] - Also accept `snake_case` variable
|
|
16
17
|
* names in `@typescript-eslint/naming-convention` — for code that
|
|
17
18
|
* destructures rows straight from a SQL database (e.g. Supabase), whose
|
|
@@ -72,8 +73,9 @@ export type BaseConfigOptions = {
|
|
|
72
73
|
/**
|
|
73
74
|
* - Enable eslint-plugin-turbo's
|
|
74
75
|
* `no-undeclared-env-vars` rule (needs `eslint-plugin-turbo` installed).
|
|
75
|
-
* Only relevant inside a Turborepo
|
|
76
|
-
*
|
|
76
|
+
* Only relevant inside a Turborepo: defaults to `true` when a `turbo.json`
|
|
77
|
+
* sits in ESLint's working directory or one of its parents, `false`
|
|
78
|
+
* otherwise.
|
|
77
79
|
*/
|
|
78
80
|
turbo?: boolean | undefined;
|
|
79
81
|
/**
|
package/dist/base.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../src/base.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../src/base.js"],"names":[],"mappings":"AA0BA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;;;GAUG;AACH,mEALG;IAA0B,SAAS;IACT,KAAK;CAE/B,GAAU,OAAO,QAAQ,EAAE,MAAM,CAAC,SAAS,CA+B7C;AAED;;;;;GAKG;AACH,2EAHW,iBAAiB,GACf,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAmH5C;AArMD;;;;;;GAMG;AACH,wCAFU,MAAM,EAAE,CAQhB;AA0LF;;;;;KAKK;AACL,yBAFU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAEqB"}
|
package/dist/base.js
CHANGED
|
@@ -5,10 +5,8 @@ import simpleImportSortPlugin from "eslint-plugin-simple-import-sort";
|
|
|
5
5
|
import globals from "globals";
|
|
6
6
|
import tseslint from "typescript-eslint";
|
|
7
7
|
import { lazyConfig } from "./internal/lazy-config.js";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
// peer dependency, the whole Turborepo binary).
|
|
11
|
-
const turboPlugin = await importOptional("eslint-plugin-turbo", () => import("eslint-plugin-turbo"));
|
|
8
|
+
import { requireOptional } from "./internal/optional-plugin.js";
|
|
9
|
+
import { isInsideTurborepo } from "./internal/turborepo.js";
|
|
12
10
|
/**
|
|
13
11
|
* Node.js builtins without the `node:` prefix, as the first
|
|
14
12
|
* `simple-import-sort/imports` group. Exported so other variants (e.g.
|
|
@@ -33,10 +31,11 @@ export const NODE_BUILTIN_IMPORT_GROUP = [
|
|
|
33
31
|
* `strictTypeChecked` + `stylisticTypeChecked` presets. Set to `false` to
|
|
34
32
|
* fall back to the plain `recommended` preset instead (useful while
|
|
35
33
|
* migrating an existing codebase that isn't ready for the strict rules).
|
|
36
|
-
* @property {boolean} [turbo
|
|
34
|
+
* @property {boolean} [turbo] - Enable eslint-plugin-turbo's
|
|
37
35
|
* `no-undeclared-env-vars` rule (needs `eslint-plugin-turbo` installed).
|
|
38
|
-
* Only relevant inside a Turborepo
|
|
39
|
-
*
|
|
36
|
+
* Only relevant inside a Turborepo: defaults to `true` when a `turbo.json`
|
|
37
|
+
* sits in ESLint's working directory or one of its parents, `false`
|
|
38
|
+
* otherwise.
|
|
40
39
|
* @property {boolean} [snakeCase=false] - Also accept `snake_case` variable
|
|
41
40
|
* names in `@typescript-eslint/naming-convention` — for code that
|
|
42
41
|
* destructures rows straight from a SQL database (e.g. Supabase), whose
|
|
@@ -86,7 +85,7 @@ export function createNamingConventionRule({ snakeCase = false, react = false, }
|
|
|
86
85
|
* @param {BaseConfigOptions} [options]
|
|
87
86
|
* @returns {import("eslint").Linter.Config[]}
|
|
88
87
|
*/
|
|
89
|
-
export function createBaseConfig({ prettier = true, strict = true, turbo =
|
|
88
|
+
export function createBaseConfig({ prettier = true, strict = true, turbo = isInsideTurborepo(), snakeCase = false, } = {}) {
|
|
90
89
|
return defineConfig([
|
|
91
90
|
eslint.configs.recommended,
|
|
92
91
|
strict ? tseslint.configs.strictTypeChecked : tseslint.configs.recommended,
|
|
@@ -157,7 +156,7 @@ export function createBaseConfig({ prettier = true, strict = true, turbo = true,
|
|
|
157
156
|
turbo
|
|
158
157
|
? {
|
|
159
158
|
plugins: {
|
|
160
|
-
turbo:
|
|
159
|
+
turbo: /** @type {typeof import("eslint-plugin-turbo").default} */ (requireOptional("eslint-plugin-turbo", "turbo")),
|
|
161
160
|
},
|
|
162
161
|
rules: {
|
|
163
162
|
"turbo/no-undeclared-env-vars": "error",
|
|
@@ -1,36 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* variant always needs are imported statically by that variant's module;
|
|
7
|
-
* plugins behind an option (`a11y`, `i18n`, `testingLibrary`, `turbo`) go
|
|
8
|
-
* through these helpers instead, so a project that leaves the option off
|
|
9
|
-
* doesn't have to install them.
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Imports an optional plugin, resolving to `undefined` when the package isn't
|
|
13
|
-
* installed. Any other failure (a broken install, a missing transitive
|
|
14
|
-
* dependency of the plugin itself) is rethrown rather than hidden.
|
|
2
|
+
* Loads an optional plugin, or throws an error explaining which package to
|
|
3
|
+
* install (or which option to turn off) when it isn't installed. Any other
|
|
4
|
+
* failure (a broken install, a missing transitive dependency of the plugin
|
|
5
|
+
* itself) is rethrown rather than hidden.
|
|
15
6
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* @param {() => Promise<{ default: T }>} load - `() => import(name)`, written
|
|
19
|
-
* out by the caller so the plugin keeps its type.
|
|
20
|
-
* @returns {Promise<T | undefined>}
|
|
21
|
-
*/
|
|
22
|
-
export function importOptional<T>(name: string, load: () => Promise<{
|
|
23
|
-
default: T;
|
|
24
|
-
}>): Promise<T | undefined>;
|
|
25
|
-
/**
|
|
26
|
-
* Returns `plugin`, or throws an error explaining which package to install
|
|
27
|
-
* (or which option to turn off) when it's missing.
|
|
7
|
+
* Callers cast the result to the package's own type (its `import()`
|
|
8
|
+
* default export) to keep the plugin's type, which `require()` loses.
|
|
28
9
|
*
|
|
29
|
-
* @template T
|
|
30
|
-
* @param {T | undefined} plugin
|
|
31
10
|
* @param {string} name - The package name.
|
|
32
11
|
* @param {string} option - The option that needs it, e.g. `a11y`.
|
|
33
|
-
* @
|
|
12
|
+
* @param {(name: string) => unknown} [load] - Stands in for `require` in tests.
|
|
13
|
+
* @returns {unknown}
|
|
34
14
|
*/
|
|
35
|
-
export function
|
|
15
|
+
export function requireOptional(name: string, option: string, load?: (name: string) => unknown): unknown;
|
|
36
16
|
//# sourceMappingURL=optional-plugin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optional-plugin.d.ts","sourceRoot":"","sources":["../../src/internal/optional-plugin.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"optional-plugin.d.ts","sourceRoot":"","sources":["../../src/internal/optional-plugin.js"],"names":[],"mappings":"AAmBA;;;;;;;;;;;;;GAaG;AACH,sCALW,MAAM,UACN,MAAM,SACN,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GACvB,OAAO,CAmBnB"}
|
|
@@ -1,33 +1,62 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
/**
|
|
2
3
|
* Plugins that only some variants or options use are optional peer
|
|
3
4
|
* dependencies (see package.json's `peerDependenciesMeta`): a React Native app
|
|
4
5
|
* must not end up installing angular-eslint (and the whole Angular CLI it
|
|
5
6
|
* pulls in), nor a plain TypeScript library every React plugin. Plugins a
|
|
6
7
|
* variant always needs are imported statically by that variant's module;
|
|
7
|
-
* plugins behind an option (`a11y`, `i18n`, `testingLibrary`,
|
|
8
|
-
* through
|
|
9
|
-
* doesn't have to install them.
|
|
8
|
+
* plugins behind an option (`a11y`, `expo`, `i18n`, `testingLibrary`,
|
|
9
|
+
* `turbo`) go through {@link requireOptional} instead, only when the option is
|
|
10
|
+
* on, so a project that leaves it off doesn't have to install them.
|
|
11
|
+
*
|
|
12
|
+
* Loaded with `require()` rather than `import()`: an `await import()` at the
|
|
13
|
+
* top of a variant module would make the whole package an async module, which
|
|
14
|
+
* Node refuses to `require()` (`ERR_REQUIRE_ASYNC_MODULE`) — and the CommonJS
|
|
15
|
+
* `eslint.config.js` generated by Expo's templates does exactly that.
|
|
10
16
|
*/
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
11
18
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
19
|
+
* Loads an optional plugin, or throws an error explaining which package to
|
|
20
|
+
* install (or which option to turn off) when it isn't installed. Any other
|
|
21
|
+
* failure (a broken install, a missing transitive dependency of the plugin
|
|
22
|
+
* itself) is rethrown rather than hidden.
|
|
23
|
+
*
|
|
24
|
+
* Callers cast the result to the package's own type (its `import()`
|
|
25
|
+
* default export) to keep the plugin's type, which `require()` loses.
|
|
15
26
|
*
|
|
16
|
-
* @
|
|
17
|
-
* @param {string}
|
|
18
|
-
* @param {() =>
|
|
19
|
-
*
|
|
20
|
-
* @returns {Promise<T | undefined>}
|
|
27
|
+
* @param {string} name - The package name.
|
|
28
|
+
* @param {string} option - The option that needs it, e.g. `a11y`.
|
|
29
|
+
* @param {(name: string) => unknown} [load] - Stands in for `require` in tests.
|
|
30
|
+
* @returns {unknown}
|
|
21
31
|
*/
|
|
22
|
-
export
|
|
32
|
+
export function requireOptional(name, option, load = require) {
|
|
33
|
+
/** @type {unknown} */
|
|
34
|
+
let loaded;
|
|
23
35
|
try {
|
|
24
|
-
|
|
36
|
+
loaded = load(name);
|
|
25
37
|
}
|
|
26
38
|
catch (error) {
|
|
27
|
-
if (isPackageNotFound(error, name))
|
|
28
|
-
|
|
39
|
+
if (isPackageNotFound(error, name)) {
|
|
40
|
+
throw new Error(`@forthtilliath/eslint-config: the \`${option}\` option needs ${name}, ` +
|
|
41
|
+
`which isn't installed. Install it as a devDependency ` +
|
|
42
|
+
`(npm install --save-dev ${name}), or pass \`${option}: false\`.`, { cause: error });
|
|
43
|
+
}
|
|
29
44
|
throw error;
|
|
30
45
|
}
|
|
46
|
+
return isModuleNamespace(loaded) ? loaded.default : loaded;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* `require()` of an ES module returns its namespace object, where `import()`'s
|
|
50
|
+
* `.default` is the plugin; a CommonJS module is returned as `module.exports`,
|
|
51
|
+
* which is already what `import()` puts in `.default`.
|
|
52
|
+
*
|
|
53
|
+
* @param {unknown} value
|
|
54
|
+
* @returns {value is { default: unknown }}
|
|
55
|
+
*/
|
|
56
|
+
function isModuleNamespace(value) {
|
|
57
|
+
return (typeof value === "object" &&
|
|
58
|
+
value !== null &&
|
|
59
|
+
Object.prototype.toString.call(value) === "[object Module]");
|
|
31
60
|
}
|
|
32
61
|
/**
|
|
33
62
|
* @param {unknown} error
|
|
@@ -37,27 +66,11 @@ export async function importOptional(name, load) {
|
|
|
37
66
|
function isPackageNotFound(error, name) {
|
|
38
67
|
return (error instanceof Error &&
|
|
39
68
|
"code" in error &&
|
|
40
|
-
error.code === "
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
* Returns `plugin`, or throws an error explaining which package to install
|
|
48
|
-
* (or which option to turn off) when it's missing.
|
|
49
|
-
*
|
|
50
|
-
* @template T
|
|
51
|
-
* @param {T | undefined} plugin
|
|
52
|
-
* @param {string} name - The package name.
|
|
53
|
-
* @param {string} option - The option that needs it, e.g. `a11y`.
|
|
54
|
-
* @returns {T}
|
|
55
|
-
*/
|
|
56
|
-
export function requirePlugin(plugin, name, option) {
|
|
57
|
-
if (plugin === undefined) {
|
|
58
|
-
throw new Error(`@forthtilliath/eslint-config: the \`${option}\` option needs ${name}, ` +
|
|
59
|
-
`which isn't installed. Install it as a devDependency ` +
|
|
60
|
-
`(npm install --save-dev ${name}), or pass \`${option}: false\`.`);
|
|
61
|
-
}
|
|
62
|
-
return plugin;
|
|
69
|
+
(error.code === "MODULE_NOT_FOUND" ||
|
|
70
|
+
error.code === "ERR_MODULE_NOT_FOUND") &&
|
|
71
|
+
// Node names the package it couldn't find on the first line (the
|
|
72
|
+
// require stack follows): checking it tells "the plugin isn't installed"
|
|
73
|
+
// apart from "the plugin is installed but one of its own dependencies is
|
|
74
|
+
// missing", which must not be swallowed.
|
|
75
|
+
(error.message.split("\n")[0] ?? "").includes(`'${name}'`));
|
|
63
76
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether `dir` or one of its parents holds a `turbo.json` — i.e. ESLint runs
|
|
3
|
+
* inside a Turborepo, where eslint-plugin-turbo's rule makes sense. Used as
|
|
4
|
+
* the default of the `turbo` option, so a standalone app doesn't have to pass
|
|
5
|
+
* `turbo: false` (nor install the plugin) to load the config.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} [dir] - Where to start looking; ESLint's working directory
|
|
8
|
+
* by default.
|
|
9
|
+
* @returns {boolean}
|
|
10
|
+
*/
|
|
11
|
+
export function isInsideTurborepo(dir?: string): boolean;
|
|
12
|
+
//# sourceMappingURL=turborepo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turborepo.d.ts","sourceRoot":"","sources":["../../src/internal/turborepo.js"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,wCAJW,MAAM,GAEJ,OAAO,CAOnB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Whether `dir` or one of its parents holds a `turbo.json` — i.e. ESLint runs
|
|
5
|
+
* inside a Turborepo, where eslint-plugin-turbo's rule makes sense. Used as
|
|
6
|
+
* the default of the `turbo` option, so a standalone app doesn't have to pass
|
|
7
|
+
* `turbo: false` (nor install the plugin) to load the config.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} [dir] - Where to start looking; ESLint's working directory
|
|
10
|
+
* by default.
|
|
11
|
+
* @returns {boolean}
|
|
12
|
+
*/
|
|
13
|
+
export function isInsideTurborepo(dir = process.cwd()) {
|
|
14
|
+
for (let current = dir;; current = dirname(current)) {
|
|
15
|
+
if (existsSync(join(current, "turbo.json")))
|
|
16
|
+
return true;
|
|
17
|
+
if (dirname(current) === current)
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
package/dist/react-native.d.ts
CHANGED
|
@@ -4,12 +4,20 @@
|
|
|
4
4
|
* hand here instead of spreading a ready-made flat config like storybook.js
|
|
5
5
|
* does.
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} ReactNativeConfigOptions
|
|
9
|
+
* @property {boolean} [expo=false] - Enable the eslint-plugin-expo rules that
|
|
10
|
+
* eslint-config-expo turns on (`use-dom-exports`,
|
|
11
|
+
* `no-env-var-destructuring`, `no-dynamic-env-var`), so an Expo app
|
|
12
|
+
* migrating from eslint-config-expo keeps them (needs `eslint-plugin-expo`
|
|
13
|
+
* installed).
|
|
14
|
+
*/
|
|
7
15
|
/**
|
|
8
16
|
* A custom ESLint configuration for React Native / Expo apps.
|
|
9
17
|
*
|
|
10
|
-
* @param {import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions} [options]
|
|
18
|
+
* @param {import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions & ReactNativeConfigOptions} [options]
|
|
11
19
|
* @returns {import("eslint").Linter.Config[]} */
|
|
12
|
-
export function createReactNativeConfig({ a11y, ...options }?: import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions): import("eslint").Linter.Config[];
|
|
20
|
+
export function createReactNativeConfig({ a11y, expo, ...options }?: import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions & ReactNativeConfigOptions): import("eslint").Linter.Config[];
|
|
13
21
|
/**
|
|
14
22
|
* A custom ESLint configuration for React Native / Expo apps, with the
|
|
15
23
|
* default options. Use `createReactNativeConfig(options)` instead to
|
|
@@ -17,4 +25,14 @@ export function createReactNativeConfig({ a11y, ...options }?: import("./base.js
|
|
|
17
25
|
*
|
|
18
26
|
* @type {import("eslint").Linter.Config[]} */
|
|
19
27
|
export const reactNativeConfig: import("eslint").Linter.Config[];
|
|
28
|
+
export type ReactNativeConfigOptions = {
|
|
29
|
+
/**
|
|
30
|
+
* - Enable the eslint-plugin-expo rules that
|
|
31
|
+
* eslint-config-expo turns on (`use-dom-exports`,
|
|
32
|
+
* `no-env-var-destructuring`, `no-dynamic-env-var`), so an Expo app
|
|
33
|
+
* migrating from eslint-config-expo keeps them (needs `eslint-plugin-expo`
|
|
34
|
+
* installed).
|
|
35
|
+
*/
|
|
36
|
+
expo?: boolean | undefined;
|
|
37
|
+
};
|
|
20
38
|
//# sourceMappingURL=react-native.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-native.d.ts","sourceRoot":"","sources":["../src/react-native.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"react-native.d.ts","sourceRoot":"","sources":["../src/react-native.js"],"names":[],"mappings":"AAOA;;;;;GAKG;AAEH;;;;;;;GAOG;AAEH;;;;iDAIiD;AACjD,qEAFW,OAAO,WAAW,EAAE,iBAAiB,GAAG,OAAO,YAAY,EAAE,kBAAkB,GAAG,wBAAwB,GACxG,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CA6D5C;AAED;;;;;8CAK8C;AAC9C,gCADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CACmC"}
|
package/dist/react-native.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineConfig } from "eslint/config";
|
|
2
2
|
import pluginReactNative from "eslint-plugin-react-native";
|
|
3
3
|
import { lazyConfig } from "./internal/lazy-config.js";
|
|
4
|
+
import { requireOptional } from "./internal/optional-plugin.js";
|
|
4
5
|
import { createReactConfig } from "./react.js";
|
|
5
6
|
/**
|
|
6
7
|
* eslint-plugin-react-native only ships a legacy (eslintrc) `configs.all`
|
|
@@ -8,14 +9,37 @@ import { createReactConfig } from "./react.js";
|
|
|
8
9
|
* hand here instead of spreading a ready-made flat config like storybook.js
|
|
9
10
|
* does.
|
|
10
11
|
*/
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {object} ReactNativeConfigOptions
|
|
14
|
+
* @property {boolean} [expo=false] - Enable the eslint-plugin-expo rules that
|
|
15
|
+
* eslint-config-expo turns on (`use-dom-exports`,
|
|
16
|
+
* `no-env-var-destructuring`, `no-dynamic-env-var`), so an Expo app
|
|
17
|
+
* migrating from eslint-config-expo keeps them (needs `eslint-plugin-expo`
|
|
18
|
+
* installed).
|
|
19
|
+
*/
|
|
11
20
|
/**
|
|
12
21
|
* A custom ESLint configuration for React Native / Expo apps.
|
|
13
22
|
*
|
|
14
|
-
* @param {import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions} [options]
|
|
23
|
+
* @param {import("./base.js").BaseConfigOptions & import("./react.js").ReactConfigOptions & ReactNativeConfigOptions} [options]
|
|
15
24
|
* @returns {import("eslint").Linter.Config[]} */
|
|
16
|
-
export function createReactNativeConfig({ a11y = false, ...options } = {}) {
|
|
25
|
+
export function createReactNativeConfig({ a11y = false, expo = false, ...options } = {}) {
|
|
17
26
|
return defineConfig([
|
|
18
27
|
...createReactConfig({ a11y, ...options }),
|
|
28
|
+
expo
|
|
29
|
+
? {
|
|
30
|
+
plugins: {
|
|
31
|
+
expo: /** @type {typeof import("eslint-plugin-expo")} */ (requireOptional("eslint-plugin-expo", "expo")),
|
|
32
|
+
},
|
|
33
|
+
rules: {
|
|
34
|
+
// Every `EXPO_PUBLIC_*` variable is inlined at build time by
|
|
35
|
+
// reading `process.env.EXPO_PUBLIC_X` literally: a destructured or
|
|
36
|
+
// computed access stays `undefined` in the bundle.
|
|
37
|
+
"expo/no-env-var-destructuring": "error",
|
|
38
|
+
"expo/no-dynamic-env-var": "error",
|
|
39
|
+
"expo/use-dom-exports": "error",
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
: [],
|
|
19
43
|
{
|
|
20
44
|
languageOptions: {
|
|
21
45
|
globals: {
|
package/dist/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.js"],"names":[],"mappings":"AAqCA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;GAKG;AACH,0FAHW,OAAO,WAAW,EAAE,iBAAiB,GAAG,kBAAkB,GACxD,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAqG5C;AAED;;;;8CAI8C;AAC9C,0BADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CACuB"}
|
package/dist/react.js
CHANGED
|
@@ -5,16 +5,8 @@ import pluginReactHooks from "eslint-plugin-react-hooks";
|
|
|
5
5
|
import pluginReactRefresh from "eslint-plugin-react-refresh";
|
|
6
6
|
import globals from "globals";
|
|
7
7
|
import { lazyConfig } from "./internal/lazy-config.js";
|
|
8
|
-
import {
|
|
8
|
+
import { requireOptional } from "./internal/optional-plugin.js";
|
|
9
9
|
import { createBaseConfig, createNamingConventionRule } from "./base.js";
|
|
10
|
-
// Optional peer dependencies, each behind an option (see
|
|
11
|
-
// ReactConfigOptions): a project leaving the option off doesn't install them.
|
|
12
|
-
const [pluginJsxA11y, pluginI18next, pluginTestingLibrary, pluginJestDom] = await Promise.all([
|
|
13
|
-
importOptional("eslint-plugin-jsx-a11y", () => import("eslint-plugin-jsx-a11y")),
|
|
14
|
-
importOptional("eslint-plugin-i18next", () => import("eslint-plugin-i18next")),
|
|
15
|
-
importOptional("eslint-plugin-testing-library", () => import("eslint-plugin-testing-library")),
|
|
16
|
-
importOptional("eslint-plugin-jest-dom", () => import("eslint-plugin-jest-dom")),
|
|
17
|
-
]);
|
|
18
10
|
/**
|
|
19
11
|
* Any file matching this is a test file, whatever runner or naming
|
|
20
12
|
* convention is used to suffix it (`.test.`, `.spec.`, `.dom.test.`...) —
|
|
@@ -68,13 +60,13 @@ export function createReactConfig({ a11y = true, i18n = false, testingLibrary =
|
|
|
68
60
|
return defineConfig([
|
|
69
61
|
...createBaseConfig({ snakeCase, ...options }),
|
|
70
62
|
a11y
|
|
71
|
-
?
|
|
72
|
-
.flatConfigs.recommended
|
|
63
|
+
? /** @type {typeof import("eslint-plugin-jsx-a11y").default} */ (requireOptional("eslint-plugin-jsx-a11y", "a11y")).flatConfigs.recommended
|
|
73
64
|
: [],
|
|
74
65
|
i18n
|
|
75
66
|
? {
|
|
76
67
|
plugins: {
|
|
77
|
-
i18next:
|
|
68
|
+
i18next:
|
|
69
|
+
/** @type {typeof import("eslint-plugin-i18next").default} */ (requireOptional("eslint-plugin-i18next", "i18n")),
|
|
78
70
|
},
|
|
79
71
|
rules: { "i18next/no-literal-string": "error" },
|
|
80
72
|
}
|
|
@@ -82,11 +74,11 @@ export function createReactConfig({ a11y = true, i18n = false, testingLibrary =
|
|
|
82
74
|
testingLibrary
|
|
83
75
|
? [
|
|
84
76
|
{
|
|
85
|
-
...
|
|
77
|
+
... /** @type {typeof import("eslint-plugin-testing-library").default} */(requireOptional("eslint-plugin-testing-library", "testingLibrary")).configs["flat/react"],
|
|
86
78
|
files: TEST_FILE_GLOBS,
|
|
87
79
|
},
|
|
88
80
|
{
|
|
89
|
-
...
|
|
81
|
+
... /** @type {typeof import("eslint-plugin-jest-dom").default} */(requireOptional("eslint-plugin-jest-dom", "testingLibrary")).configs["flat/recommended"],
|
|
90
82
|
files: TEST_FILE_GLOBS,
|
|
91
83
|
},
|
|
92
84
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forthtilliath/eslint-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@next/eslint-plugin-next": "^16.3.0",
|
|
34
34
|
"angular-eslint": "^21.4.0",
|
|
35
35
|
"eslint": "^9.0.0 || ^10.0.0",
|
|
36
|
+
"eslint-plugin-expo": "^1.2.1",
|
|
36
37
|
"eslint-plugin-i18next": "^6.1.5",
|
|
37
38
|
"eslint-plugin-jest-dom": "^5.10.1",
|
|
38
39
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
@@ -55,6 +56,9 @@
|
|
|
55
56
|
"angular-eslint": {
|
|
56
57
|
"optional": true
|
|
57
58
|
},
|
|
59
|
+
"eslint-plugin-expo": {
|
|
60
|
+
"optional": true
|
|
61
|
+
},
|
|
58
62
|
"eslint-plugin-i18next": {
|
|
59
63
|
"optional": true
|
|
60
64
|
},
|
|
@@ -87,11 +91,12 @@
|
|
|
87
91
|
}
|
|
88
92
|
},
|
|
89
93
|
"devDependencies": {
|
|
90
|
-
"@angular/core": "^21.
|
|
94
|
+
"@angular/core": "^21.2.25",
|
|
91
95
|
"@eslint-react/eslint-plugin": "^5.18.3",
|
|
92
96
|
"@next/eslint-plugin-next": "^16.3.0",
|
|
93
97
|
"angular-eslint": "^21.4.0",
|
|
94
98
|
"eslint": "10.7.0",
|
|
99
|
+
"eslint-plugin-expo": "^1.2.1",
|
|
95
100
|
"eslint-plugin-i18next": "^6.1.5",
|
|
96
101
|
"eslint-plugin-jest-dom": "^5.10.1",
|
|
97
102
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
@@ -104,7 +109,7 @@
|
|
|
104
109
|
"eslint-plugin-turbo": "^2.10.9",
|
|
105
110
|
"rxjs": "^7.8.0",
|
|
106
111
|
"typescript": "^5.9.3",
|
|
107
|
-
"vitest": "^5.0.
|
|
112
|
+
"vitest": "^5.0.5",
|
|
108
113
|
"@forthtilliath/typescript-config": "0.1.1"
|
|
109
114
|
},
|
|
110
115
|
"scripts": {
|