@forthtilliath/eslint-config 0.1.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 ADDED
@@ -0,0 +1,92 @@
1
+ # @forthtilliath/eslint-config
2
+
3
+ Shared ESLint flat configs (`eslint.config.js`/`.ts`) — for every package/app
4
+ in this monorepo, and for any other project that wants the same setup via a
5
+ single import instead of hand-assembling a dozen plugins. Built on
6
+ `typescript-eslint`'s `strictTypeChecked` + `stylisticTypeChecked` presets,
7
+ with import sorting, Turborepo's own lint plugin, and Prettier
8
+ conflict-resolution baked in.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install --save-dev @forthtilliath/eslint-config eslint typescript
14
+ ```
15
+
16
+ Or, from within this monorepo, as a workspace dependency:
17
+
18
+ ```json
19
+ {
20
+ "devDependencies": {
21
+ "@forthtilliath/eslint-config": "workspace:*"
22
+ }
23
+ }
24
+ ```
25
+
26
+ `eslint` and `typescript` are peer dependencies — install them yourself (any
27
+ recent flat-config-era ESLint 9+/10+, and TypeScript 5+). Every ESLint plugin
28
+ each variant actually uses (`typescript-eslint`, `eslint-plugin-react`,
29
+ `angular-eslint`, etc.) ships as a regular dependency of this package, so
30
+ there's nothing else to install per variant.
31
+
32
+ ## Usage
33
+
34
+ Pick the variant that matches the package, in its `eslint.config.ts`:
35
+
36
+ ```ts
37
+ // A plain TypeScript library (packages/lib, packages/types)
38
+ import { baseConfig } from "@forthtilliath/eslint-config";
39
+ export default baseConfig;
40
+ ```
41
+
42
+ ```ts
43
+ // A React library (packages/react/*)
44
+ import { reactConfig } from "@forthtilliath/eslint-config/react";
45
+ export default reactConfig;
46
+ ```
47
+
48
+ ```ts
49
+ // A Next.js app (apps/web)
50
+ import { nextJsConfig } from "@forthtilliath/eslint-config/nextjs";
51
+ export default nextJsConfig;
52
+ ```
53
+
54
+ ```ts
55
+ // A Storybook app (apps/react-sb)
56
+ import { storybookConfig } from "@forthtilliath/eslint-config/storybook";
57
+ export default storybookConfig;
58
+ ```
59
+
60
+ ```ts
61
+ // An Angular app/library
62
+ import { angularConfig } from "@forthtilliath/eslint-config/angular";
63
+ export default angularConfig;
64
+ ```
65
+
66
+ All five are **named** exports — a default import (`import config from "..."`)
67
+ resolves to the whole module namespace object instead of the config array and
68
+ crashes ESLint's flat-config loader outright. Two of the five variants had
69
+ exactly this bug at one point; use the named-import form above.
70
+
71
+ Each variant is additive: `reactConfig`/`angularConfig` extend `baseConfig`,
72
+ `nextJsConfig`/`storybookConfig` extend `reactConfig`.
73
+
74
+ ### Typed linting and non-project files
75
+
76
+ Rules that need type information (`consistent-type-exports`,
77
+ `naming-convention`, etc.) are scoped to `**/*.{ts,tsx,mts,cts}` only, and
78
+ `eslint.config.js`/`.ts`, `storybook-static/**` and `dist/**` are excluded
79
+ from linting entirely — none of them belong to a package's own `tsconfig`
80
+ project, so type-aware rules crash on them otherwise.
81
+
82
+ ## Scripts
83
+
84
+ ```bash
85
+ pnpm run build # tsc -> dist/ (consumers import the built output)
86
+ pnpm run dev # tsc --watch
87
+ pnpm run check-types # tsc --noEmit
88
+ pnpm run lint # eslint (lints its own src/)
89
+ ```
90
+
91
+ Run `pnpm run build` after editing `src/*.js` — consuming packages resolve
92
+ `@forthtilliath/eslint-config/*` to `dist/*.js`, not the source.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * A custom ESLint configuration for Angular applications/libraries.
3
+ *
4
+ * Assumes the standard Angular CLI selector prefix ("app") set by `ng new`;
5
+ * for a project generated with a different `--prefix`, override
6
+ * `@angular-eslint/directive-selector` / `@angular-eslint/component-selector`
7
+ * locally after spreading this config.
8
+ *
9
+ * @type {import("eslint").Linter.Config[]} */
10
+ export const angularConfig: import("eslint").Linter.Config[];
11
+ //# sourceMappingURL=angular.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"angular.d.ts","sourceRoot":"","sources":["../src/angular.js"],"names":[],"mappings":"AAMA;;;;;;;;8CAQ8C;AAC9C,4BADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAwCvC"}
@@ -0,0 +1,53 @@
1
+ import angular from "angular-eslint";
2
+ import { defineConfig } from "eslint/config";
3
+ import tseslint from "typescript-eslint";
4
+ import { baseConfig } from "./base.js";
5
+ /**
6
+ * A custom ESLint configuration for Angular applications/libraries.
7
+ *
8
+ * Assumes the standard Angular CLI selector prefix ("app") set by `ng new`;
9
+ * for a project generated with a different `--prefix`, override
10
+ * `@angular-eslint/directive-selector` / `@angular-eslint/component-selector`
11
+ * locally after spreading this config.
12
+ *
13
+ * @type {import("eslint").Linter.Config[]} */
14
+ export const angularConfig = defineConfig([
15
+ ...baseConfig,
16
+ {
17
+ // Build/cache artifacts specific to the Angular CLI (baseConfig only knows
18
+ // about generic dist/**) : without this, ESLint also lints the bundled,
19
+ // minified Vite dep cache and reports thousands of spurious errors on it.
20
+ ignores: [".angular/**", "dist/**", "coverage/**"],
21
+ },
22
+ {
23
+ files: ["**/*.ts"],
24
+ extends: [angular.configs.tsRecommended],
25
+ processor: angular.processInlineTemplates,
26
+ rules: {
27
+ "@angular-eslint/directive-selector": [
28
+ "error",
29
+ { type: "attribute", prefix: "app", style: "camelCase" },
30
+ ],
31
+ "@angular-eslint/component-selector": [
32
+ "error",
33
+ { type: "element", prefix: "app", style: "kebab-case" },
34
+ ],
35
+ // Un composant/page Angular sans logique propre (juste un template +
36
+ // decorateur) est un corps de classe legitimement vide, pas du code mort.
37
+ "@typescript-eslint/no-extraneous-class": "off",
38
+ },
39
+ },
40
+ {
41
+ // baseConfig's tseslint.configs.strictTypeChecked/stylisticTypeChecked apply
42
+ // with no `files` restriction, so without this they leak onto templates
43
+ // (parsed by angular-eslint/template-parser, which has no type information)
44
+ // and crash on the first type-aware rule ESLint tries to load.
45
+ files: ["**/*.html"],
46
+ extends: [
47
+ tseslint.configs.disableTypeChecked,
48
+ angular.configs.templateRecommended,
49
+ angular.configs.templateAccessibility,
50
+ ],
51
+ rules: {},
52
+ },
53
+ ]);
package/dist/base.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A shared ESLint configuration for the repository.
3
+ *
4
+ * @type {import("eslint").Linter.Config[]}
5
+ * */
6
+ export const baseConfig: import("eslint").Linter.Config[];
7
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../src/base.js"],"names":[],"mappings":"AAQA;;;;KAIK;AACL,yBAFU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAiIvC"}
package/dist/base.js ADDED
@@ -0,0 +1,140 @@
1
+ import eslint from "@eslint/js";
2
+ import { defineConfig } from "eslint/config";
3
+ import eslintConfigPrettier from "eslint-config-prettier";
4
+ import simpleImportSortPlugin from "eslint-plugin-simple-import-sort";
5
+ import turboPlugin from "eslint-plugin-turbo";
6
+ import globals from "globals";
7
+ import tseslint from "typescript-eslint";
8
+ /**
9
+ * A shared ESLint configuration for the repository.
10
+ *
11
+ * @type {import("eslint").Linter.Config[]}
12
+ * */
13
+ export const baseConfig = defineConfig([
14
+ eslint.configs.recommended,
15
+ tseslint.configs.strictTypeChecked,
16
+ tseslint.configs.stylisticTypeChecked,
17
+ {
18
+ ignores: [
19
+ "dist/**",
20
+ "storybook-static/**",
21
+ "postcss.config.cjs",
22
+ "postcss.config.mjs",
23
+ "eslint.config.js",
24
+ "eslint.config.mjs",
25
+ "eslint.config.cjs",
26
+ "eslint.config.ts",
27
+ ],
28
+ },
29
+ {
30
+ languageOptions: {
31
+ // Globaux Node (process, __dirname, module, require...) : la config
32
+ // partagée elle-même s'exécute sous Node (ex. process.cwd() ci-dessous),
33
+ // et la plupart des consommateurs (scripts, configs, apps Next.js côté
34
+ // serveur) en ont aussi besoin. Sans risque de collision avec les
35
+ // globaux navigateur ajoutés par les configs plus spécifiques
36
+ // (react.js, nextjs.js) qui étendent celle-ci.
37
+ globals: {
38
+ ...globals.node,
39
+ },
40
+ parserOptions: {
41
+ projectService: true,
42
+ tsconfigRootDir: process.cwd(),
43
+ },
44
+ },
45
+ },
46
+ {
47
+ files: ["**/*.js"],
48
+ extends: [tseslint.configs.disableTypeChecked],
49
+ },
50
+ {
51
+ plugins: {
52
+ "simple-import-sort": simpleImportSortPlugin,
53
+ },
54
+ rules: {
55
+ "simple-import-sort/imports": [
56
+ "error",
57
+ {
58
+ groups: [
59
+ // Node.js builtins. You could also generate this regex if you use a `.js` config.
60
+ // For example: `^(${require("module").builtinModules.join("|")})(/|$)`
61
+ // Note that if you use the `node:` prefix for Node.js builtins,
62
+ // you can avoid this complexity: You can simply use "^node:".
63
+ [
64
+ "^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)",
65
+ ],
66
+ // Packages. `react` related packages come first.
67
+ ["^react", "^@?\\w"],
68
+ // Internal packages.
69
+ ["^(@forthtilliath)(/.*|$)"],
70
+ ["^(@|@ui|components|utils|config)(/.*|$)"],
71
+ // Side effect imports.
72
+ ["^\\u0000"],
73
+ // Parent imports. Put `..` last.
74
+ ["^\\.\\.(?!/?$)", "^\\.\\./?$"],
75
+ // Other relative imports. Put same-folder imports and `.` last.
76
+ ["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"],
77
+ // Style imports.
78
+ ["^.+\\.s?css$"],
79
+ ],
80
+ },
81
+ ],
82
+ "simple-import-sort/exports": "error",
83
+ },
84
+ },
85
+ {
86
+ plugins: {
87
+ turbo: turboPlugin,
88
+ },
89
+ rules: {
90
+ "turbo/no-undeclared-env-vars": "error",
91
+ },
92
+ },
93
+ {
94
+ files: ["**/*.{ts,tsx,mts,cts}"],
95
+ plugins: {},
96
+ rules: {
97
+ // "@typescript-eslint/no-unused-vars": "error",
98
+ "@typescript-eslint/ban-ts-comment": "error",
99
+ "@typescript-eslint/ban-tslint-comment": "error",
100
+ "@typescript-eslint/consistent-type-exports": "error",
101
+ "@typescript-eslint/consistent-type-imports": "error",
102
+ "@typescript-eslint/method-signature-style": "error",
103
+ // ignoreStatic : sans ca, une reference a une methode statique (ex.
104
+ // Validators.required dans un FormBuilder Angular) est a tort consideree
105
+ // comme "unbound" alors qu'une methode statique n'a pas de `this` a lier.
106
+ "@typescript-eslint/unbound-method": ["error", { ignoreStatic: true }],
107
+ // allowNumber : tres courant de construire une URL avec un id numerique
108
+ // (`${this.baseUrl}/${id}`) ; sans ca, chaque service HTTP typique
109
+ // declenche une erreur pour un usage parfaitement sur.
110
+ "@typescript-eslint/restrict-template-expressions": [
111
+ "error",
112
+ { allowNumber: true },
113
+ ],
114
+ "@typescript-eslint/naming-convention": [
115
+ "error",
116
+ // https://typescript-eslint.io/rules/naming-convention
117
+ {
118
+ selector: "variable",
119
+ format: ["camelCase"],
120
+ leadingUnderscore: "allow",
121
+ },
122
+ {
123
+ selector: "variable",
124
+ modifiers: ["const"],
125
+ format: ["camelCase", "UPPER_CASE"],
126
+ leadingUnderscore: "allow",
127
+ },
128
+ {
129
+ selector: "function",
130
+ format: ["camelCase"],
131
+ },
132
+ {
133
+ selector: "typeLike",
134
+ format: ["PascalCase"],
135
+ },
136
+ ],
137
+ },
138
+ },
139
+ eslintConfigPrettier,
140
+ ]);
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A custom ESLint configuration for libraries that use Next.js.
3
+ *
4
+ * @type {import("eslint").Linter.Config[]}
5
+ * */
6
+ export const nextJsConfig: import("eslint").Linter.Config[];
7
+ //# sourceMappingURL=nextjs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextjs.d.ts","sourceRoot":"","sources":["../src/nextjs.js"],"names":[],"mappings":"AAWA;;;;KAIK;AACL,2BAFU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAiEvC"}
package/dist/nextjs.js ADDED
@@ -0,0 +1,78 @@
1
+ import js from "@eslint/js";
2
+ import pluginNext from "@next/eslint-plugin-next";
3
+ import { defineConfig } from "eslint/config";
4
+ import eslintConfigPrettier from "eslint-config-prettier";
5
+ import pluginReact from "eslint-plugin-react";
6
+ import pluginReactHooks from "eslint-plugin-react-hooks";
7
+ import globals from "globals";
8
+ import tseslint from "typescript-eslint";
9
+ import { baseConfig } from "./base.js";
10
+ /**
11
+ * A custom ESLint configuration for libraries that use Next.js.
12
+ *
13
+ * @type {import("eslint").Linter.Config[]}
14
+ * */
15
+ export const nextJsConfig = defineConfig([
16
+ {
17
+ ignores: [".next/**", "next-env.d.ts"],
18
+ },
19
+ ...baseConfig,
20
+ js.configs.recommended,
21
+ eslintConfigPrettier,
22
+ ...tseslint.configs.recommended,
23
+ {
24
+ ...pluginReact.configs.flat.recommended,
25
+ languageOptions: {
26
+ ...pluginReact.configs.flat.recommended.languageOptions,
27
+ globals: {
28
+ ...globals.serviceworker,
29
+ },
30
+ },
31
+ },
32
+ {
33
+ plugins: {
34
+ "@next/next": pluginNext,
35
+ },
36
+ rules: {
37
+ ...pluginNext.configs.recommended.rules,
38
+ ...pluginNext.configs["core-web-vitals"].rules,
39
+ },
40
+ },
41
+ {
42
+ plugins: {
43
+ "react-hooks": pluginReactHooks,
44
+ },
45
+ settings: { react: { version: "19" } },
46
+ rules: {
47
+ ...pluginReactHooks.configs.recommended.rules,
48
+ // React scope no longer necessary with new JSX transform.
49
+ "react/react-in-jsx-scope": "off",
50
+ },
51
+ },
52
+ {
53
+ rules: {
54
+ "@typescript-eslint/naming-convention": [
55
+ "error",
56
+ {
57
+ selector: "variable",
58
+ format: ["camelCase"],
59
+ leadingUnderscore: "allow",
60
+ },
61
+ {
62
+ selector: "variable",
63
+ modifiers: ["const"],
64
+ format: ["camelCase", "UPPER_CASE", "PascalCase"],
65
+ leadingUnderscore: "allow",
66
+ },
67
+ {
68
+ selector: "function",
69
+ format: ["camelCase", "PascalCase"],
70
+ },
71
+ {
72
+ selector: "typeLike",
73
+ format: ["PascalCase"],
74
+ },
75
+ ],
76
+ },
77
+ },
78
+ ]);
@@ -0,0 +1,6 @@
1
+ /**
2
+ * A custom ESLint configuration for libraries that use React.
3
+ *
4
+ * @type {import("eslint").Linter.Config[]} */
5
+ export const reactConfig: import("eslint").Linter.Config[];
6
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.js"],"names":[],"mappings":"AASA;;;8CAG8C;AAC9C,0BADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CA8DvC"}
package/dist/react.js ADDED
@@ -0,0 +1,73 @@
1
+ import pluginEslintReact from "@eslint-react/eslint-plugin";
2
+ import { defineConfig } from "eslint/config";
3
+ import pluginReact from "eslint-plugin-react";
4
+ import pluginReactHooks from "eslint-plugin-react-hooks";
5
+ import pluginReactRefresh from "eslint-plugin-react-refresh";
6
+ import globals from "globals";
7
+ import { baseConfig } from "./base.js";
8
+ /**
9
+ * A custom ESLint configuration for libraries that use React.
10
+ *
11
+ * @type {import("eslint").Linter.Config[]} */
12
+ export const reactConfig = defineConfig([
13
+ ...baseConfig,
14
+ pluginReact.configs.flat["jsx-runtime"],
15
+ pluginEslintReact.configs["recommended-type-checked"],
16
+ {
17
+ languageOptions: {
18
+ ...pluginReact.configs.flat.recommended.languageOptions,
19
+ globals: {
20
+ ...globals.serviceworker,
21
+ ...globals.browser,
22
+ },
23
+ },
24
+ },
25
+ {
26
+ plugins: {
27
+ "react-hooks": pluginReactHooks,
28
+ },
29
+ settings: { react: { version: "19" } },
30
+ rules: {
31
+ ...pluginReactHooks.configs.recommended.rules,
32
+ // React scope no longer necessary with new JSX transform.
33
+ "react/react-in-jsx-scope": "off",
34
+ },
35
+ },
36
+ {
37
+ // in main config for TSX/JSX source files
38
+ plugins: {
39
+ "react-refresh": pluginReactRefresh,
40
+ },
41
+ rules: {
42
+ "react-refresh/only-export-components": "error",
43
+ },
44
+ },
45
+ {
46
+ rules: {
47
+ "@typescript-eslint/no-unsafe-call": "warn",
48
+ "@typescript-eslint/restrict-template-expressions": "warn",
49
+ "@typescript-eslint/naming-convention": [
50
+ "error",
51
+ {
52
+ selector: "variable",
53
+ format: ["camelCase"],
54
+ leadingUnderscore: "allow",
55
+ },
56
+ {
57
+ selector: "variable",
58
+ modifiers: ["const"],
59
+ format: ["camelCase", "UPPER_CASE", "PascalCase"],
60
+ leadingUnderscore: "allow",
61
+ },
62
+ {
63
+ selector: "function",
64
+ format: ["camelCase", "PascalCase"],
65
+ },
66
+ {
67
+ selector: "typeLike",
68
+ format: ["PascalCase"],
69
+ },
70
+ ],
71
+ },
72
+ },
73
+ ]);
@@ -0,0 +1,6 @@
1
+ /**
2
+ * A custom ESLint configuration for libraries that use React.
3
+ *
4
+ * @type {import("eslint").Linter.Config[]} */
5
+ export const storybookConfig: import("eslint").Linter.Config[];
6
+ //# sourceMappingURL=storybook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storybook.d.ts","sourceRoot":"","sources":["../src/storybook.js"],"names":[],"mappings":"AAKA;;;8CAG8C;AAC9C,8BADU,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAiBvC"}
@@ -0,0 +1,24 @@
1
+ import { defineConfig } from "eslint/config";
2
+ import storybook from "eslint-plugin-storybook";
3
+ import { reactConfig } from "./react.js";
4
+ /**
5
+ * A custom ESLint configuration for libraries that use React.
6
+ *
7
+ * @type {import("eslint").Linter.Config[]} */
8
+ export const storybookConfig = defineConfig([
9
+ ...reactConfig,
10
+ ...storybook.configs["flat/recommended"],
11
+ {
12
+ ignores: ["!.storybook"],
13
+ },
14
+ {
15
+ // 👇 This should match the `stories` property in .storybook/main.js|ts
16
+ files: ["**/*.stories.@(ts|tsx|js|jsx|mjs|cjs)"],
17
+ rules: {
18
+ // 👇 Enable this rule
19
+ "storybook/csf-component": "error",
20
+ // 👇 Disable this rule
21
+ "storybook/default-exports": "off",
22
+ },
23
+ },
24
+ ]);
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@forthtilliath/eslint-config",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/Forthtilliath/forthtilliath-packages.git",
12
+ "directory": "packages/eslint-config"
13
+ },
14
+ "homepage": "https://github.com/Forthtilliath/forthtilliath-packages/tree/main/packages/eslint-config#readme",
15
+ "bugs": "https://github.com/Forthtilliath/forthtilliath-packages/issues",
16
+ "scripts": {
17
+ "dev": "tsc --watch",
18
+ "build": "tsc",
19
+ "clean": "rm -rf dist",
20
+ "check-types": "tsc --noEmit",
21
+ "lint": "eslint . --max-warnings 0"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "exports": {
27
+ ".": "./dist/base.js",
28
+ "./*": "./dist/*.js"
29
+ },
30
+ "dependencies": {
31
+ "@eslint-react/eslint-plugin": "^5.18.3",
32
+ "@eslint/js": "^10.0.1",
33
+ "@next/eslint-plugin-next": "^16.3.0",
34
+ "angular-eslint": "^21.4.0",
35
+ "eslint-config-prettier": "^10.1.8",
36
+ "eslint-plugin-react": "^7.37.5",
37
+ "eslint-plugin-react-hooks": "^7.1.1",
38
+ "eslint-plugin-react-refresh": "^0.5.4",
39
+ "eslint-plugin-simple-import-sort": "^13.0.0",
40
+ "eslint-plugin-storybook": "^9.1.20",
41
+ "eslint-plugin-turbo": "^2.10.9",
42
+ "globals": "^17.9.0",
43
+ "typescript-eslint": "^8.67.0"
44
+ },
45
+ "peerDependencies": {
46
+ "eslint": ">=9.0.0",
47
+ "typescript": ">=5.0.0"
48
+ },
49
+ "devDependencies": {
50
+ "@forthtilliath/typescript-config": "workspace:*",
51
+ "eslint": "catalog:",
52
+ "typescript": "catalog:"
53
+ }
54
+ }