@timobechtel/style 1.14.0 → 2.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 CHANGED
@@ -2,66 +2,127 @@
2
2
 
3
3
  > Roll in style.
4
4
 
5
- Highly opinionated configuration files for typescript projects. Inspired by [@vercel/style-guide](https://github.com/vercel/style-guide)
5
+ Highly opinionated configuration files for typescript projects.
6
6
 
7
- > [!WARNING]
8
- > Make sure to first commit your code before running the following commands. To allow you to easily revert the changes.
7
+ > [!TIP]
8
+ > Let your agent set this up for you:
9
+ >
10
+ > ```
11
+ > Read https://raw.githubusercontent.com/TimoBechtel/style/main/skills/setup-style/SKILL.md and configure.
12
+ > ```
9
13
 
10
14
  ## Usage
11
15
 
16
+ > Make sure to first commit your code before running the following commands. This allows you to revert changes easily.
17
+
12
18
  ```bash
13
- npm i -D @timobechtel/style prettier "eslint@^8.57.1" typescript
19
+ npm i -D @timobechtel/style typescript
14
20
  ```
15
21
 
16
- ### Prettier
22
+ Install gh-get to make it easier to download the template files:
23
+
24
+ ```bash
25
+ gh extension install timobechtel/gh-get
26
+ ```
27
+
28
+ ### [Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html)
29
+
30
+ > Code formatter, replaces Prettier.
17
31
 
18
32
  ```bash
19
- curl -O https://raw.githubusercontent.com/TimoBechtel/style/refs/heads/main/templates/.prettierrc
33
+ npm i -D oxfmt
20
34
  ```
21
35
 
36
+ ```bash
37
+ gh get timobechtel/style templates/.oxfmtrc.json
38
+ ```
39
+
40
+ - [oxfmt template](https://github.com/TimoBechtel/style/blob/main/templates/.oxfmtrc.json)
41
+
42
+ ### [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
43
+
44
+ > Faster ESLint alternative. 5x faster in personal testing.
45
+
46
+ ```bash
47
+ npm i -D oxlint
48
+ ```
49
+
50
+ Core:
51
+
52
+ ```bash
53
+ gh get timobechtel/style templates/.oxlintrc.jsonc
54
+ ```
55
+
56
+ - [oxlint core template](https://github.com/TimoBechtel/style/blob/main/templates/.oxlintrc.jsonc)
57
+
58
+ React:
59
+
60
+ ```bash
61
+ gh get timobechtel/style templates/react/.oxlintrc.jsonc
62
+ ```
63
+
64
+ - [oxlint react template](https://github.com/TimoBechtel/style/blob/main/templates/react/.oxlintrc.jsonc)
65
+
22
66
  <details>
23
- <summary>Extend / customize config</summary>
67
+ <summary>Migrating to Oxlint? - `File '@timobechtel/style/tsconfig/core' not found.`</summary>
24
68
 
25
- Need to extend the config, e.g. adding plugins?
69
+ When migrating from ESLint to Oxlint, you might need to update the `tsconfig.json` file:
26
70
 
27
- ```bash
28
- curl -O https://raw.githubusercontent.com/TimoBechtel/style/refs/heads/main/templates/.prettierrc.mjs
71
+ ```diff
72
+ - "extends": ["@timobechtel/style/tsconfig/core"]
73
+ + "extends": ["@timobechtel/style/tsconfig/core.json"]
29
74
  ```
30
75
 
31
- Create a .prettierrc.mjs file and import the config, like this:
32
-
33
- ```js
34
- import config from '@timobechtel/style/prettier/index.mjs';
35
-
36
- /**
37
- * @type {import("prettier").Config}
38
- */
39
- export default {
40
- ...config,
41
- // your config
42
- }
76
+ ```diff
77
+ - "extends": ["@timobechtel/style/tsconfig/react"]
78
+ + "extends": ["@timobechtel/style/tsconfig/react.json"]
43
79
  ```
44
-
80
+
81
+ > tsgolint requires a file extension to resolve the config file.
82
+
45
83
  </details>
46
84
 
47
85
  ### Typescript
48
86
 
87
+ > Pre-configured tsconfig files.
88
+
49
89
  #### Existing tsconfig
50
90
 
51
91
  For existing projects or templates, I recomment leaving the config as-is and adding this preset to the extends array.
52
92
 
53
93
  ```json
54
94
  {
55
- "extends": ["@timobechtel/style/tsconfig/core"]
95
+ "extends": ["@timobechtel/style/tsconfig/core.json"]
56
96
  }
57
97
  ```
58
98
 
59
99
  #### New tsconfig
60
100
 
61
101
  ```bash
62
- curl -O https://raw.githubusercontent.com/TimoBechtel/style/refs/heads/main/templates/tsconfig/core/tsconfig.json
102
+ gh get timobechtel/style templates/tsconfig.json
103
+ ```
104
+
105
+ - [tsconfig core template](https://github.com/TimoBechtel/style/blob/main/templates/tsconfig.json)
106
+
107
+ #### Or with React
108
+
109
+ ```bash
110
+ gh get timobechtel/style templates/react/tsconfig.json
111
+ ```
112
+
113
+ <details>
114
+ <summary>Or manually</summary>
115
+
116
+ Copy to `tsconfig.json`:
117
+
118
+ ```json
119
+ {
120
+ "extends": "@timobechtel/style/tsconfig/react.json"
121
+ }
63
122
  ```
64
123
 
124
+ </details>
125
+
65
126
  #### Expo
66
127
 
67
128
  With expo make sure to add `"moduleResolution": "bundler"` to the `compilerOptions`, otherwise certain routing types might break.
@@ -69,141 +130,194 @@ With expo make sure to add `"moduleResolution": "bundler"` to the `compilerOptio
69
130
  <details>
70
131
  <summary>Example</summary>
71
132
 
72
- Copy to `tsconfig.json`:
133
+ Copy to `tsconfig.json`:
73
134
 
74
- ```json
75
- {
76
- "extends": ["expo/tsconfig.base", "@timobechtel/style/tsconfig/core"],
77
- "compilerOptions": {
78
- "moduleResolution": "bundler", // <-- this is important
79
- "strict": true,
80
- "paths": {
81
- "@/*": [
82
- "./*"
83
- ]
84
- }
85
- },
86
- "include": [
87
- "**/*.ts",
88
- "**/*.tsx",
89
- ".expo/types/**/*.ts",
90
- "expo-env.d.ts"
91
- ]
92
- }
93
- ```
135
+ ```json
136
+ {
137
+ "extends": ["expo/tsconfig.base", "@timobechtel/style/tsconfig/core.json"],
138
+ "compilerOptions": {
139
+ "moduleResolution": "bundler", // <-- this is important
140
+ "strict": true,
141
+ "paths": {
142
+ "@/*": ["./*"]
143
+ }
144
+ },
145
+ "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
146
+ }
147
+ ```
94
148
 
95
149
  </details>
96
150
 
151
+ ### Prettier
97
152
 
98
- #### Or with React
153
+ > Prettier config will not be updated anymore. I recommend using [Oxfmt](#oxfmt) instead.
154
+ > oxfmt has been configured to match prettier rules, however this might drift in future versions.
155
+
156
+ <details>
157
+ <summary>Setup prettier anyways</summary>
158
+
159
+ ```bash
160
+ npm i -D prettier
161
+ ```
99
162
 
100
163
  ```bash
101
- curl -O https://raw.githubusercontent.com/TimoBechtel/style/refs/heads/main/templates/tsconfig/react/tsconfig.json
164
+ gh get timobechtel/style templates/.prettierrc
102
165
  ```
103
166
 
167
+ - [prettier template](https://github.com/TimoBechtel/style/blob/main/templates/.prettierrc)
168
+
104
169
  <details>
105
- <summary>Or manually</summary>
170
+ <summary>Extend / customize config</summary>
106
171
 
107
- Copy to `tsconfig.json`:
172
+ Need to extend the config, e.g. adding plugins?
108
173
 
109
- ```json
110
- {
111
- "extends": "@timobechtel/style/tsconfig/react"
112
- }
113
- ```
174
+ ```bash
175
+ gh get timobechtel/style templates/.prettierrc.mjs
176
+ ```
114
177
 
115
- </details>
178
+ Create a .prettierrc.mjs file and import the config, like this:
116
179
 
117
- ### Eslint
180
+ ```js
181
+ import config from '@timobechtel/style/prettier/index.mjs';
118
182
 
119
- ```bash
120
- curl -O https://raw.githubusercontent.com/TimoBechtel/style/refs/heads/main/templates/eslint/core/.eslintrc.cjs
183
+ /**
184
+ * @type {import("prettier").Config}
185
+ */
186
+ export default {
187
+ ...config,
188
+ // your config
189
+ };
121
190
  ```
122
191
 
123
- #### Fix Parsing errors for config files
192
+ </details>
193
+ </details>
194
+
195
+ ### Eslint
124
196
 
125
- You may get a `Parsing error: <FILE> was not found by the project service.` for config files like .eslintrc.cjs when not included in the tsconfig.
197
+ > Eslint config will be removed in a future version. Use [Oxlint](#oxlint) instead.
198
+ > oxlint has been configured to match existing eslint rules, however this might drift in future versions.
126
199
 
127
- To fix, either add to tsconfig or add them to the eslint config:
200
+ <details>
201
+ <summary>Setup eslint anyways</summary>
128
202
 
129
- ```diff
130
- //...
131
- parserOptions: {
132
- + projectService: {
133
- + allowDefaultProject: ['.eslintrc.cjs'],
134
- + },
135
- //...
136
- },
137
- //...
203
+ ```bash
204
+ npm i -D eslint
205
+ ```
206
+
207
+ ```bash
208
+ gh get timobechtel/style templates/eslint.config.js
138
209
  ```
139
210
 
211
+ - [eslint core template](https://github.com/TimoBechtel/style/blob/main/templates/eslint.config.js)
212
+
213
+ Note: If your project is not ESM (no `"type": "module"` in `package.json`), rename the file to `eslint.config.mjs`.
140
214
 
141
215
  <details>
142
216
  <summary>Or manually</summary>
143
217
 
144
- Copy the following to a `.eslintrc.cjs`:
218
+ Copy the following to an `eslint.config.js`:
145
219
 
146
- ```js
147
- const { resolve } = require('node:path');
220
+ ```js
221
+ import path from 'node:path';
222
+ import { fileURLToPath } from 'node:url';
223
+ import { defineConfig } from 'eslint/config';
224
+ import styleCore from '@timobechtel/style/eslint/core.js';
225
+ import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript';
226
+ import { createNodeResolver } from 'eslint-plugin-import-x';
148
227
 
149
- const project = resolve(process.cwd(), 'tsconfig.json');
228
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
150
229
 
151
- module.exports = {
152
- root: true,
153
- extends: [require.resolve('@timobechtel/style/eslint/core.cjs')],
154
- parserOptions: {
155
- tsconfigRootDir: process.cwd(),
230
+ export default defineConfig([
231
+ ...styleCore,
232
+ {
233
+ languageOptions: {
234
+ parserOptions: {
235
+ tsconfigRootDir: __dirname,
236
+ },
156
237
  },
157
238
  settings: {
158
- 'import/resolver': {
159
- typescript: {
160
- project,
161
- },
162
- },
239
+ 'import-x/resolver-next': [
240
+ createTypeScriptImportResolver({
241
+ project: path.resolve(__dirname, 'tsconfig.json'),
242
+ }),
243
+ createNodeResolver(),
244
+ ],
163
245
  },
164
- };
165
- ```
246
+ },
247
+ ]);
248
+ ```
166
249
 
167
250
  </details>
168
251
 
169
252
  #### React
170
253
 
171
254
  ```bash
172
- curl -O https://raw.githubusercontent.com/TimoBechtel/style/refs/heads/main/templates/eslint/react/.eslintrc.cjs
255
+ gh get timobechtel/style templates/react/eslint.config.js
173
256
  ```
174
257
 
258
+ - [eslint react template](https://github.com/TimoBechtel/style/blob/main/templates/react/eslint.config.js)
259
+
175
260
  <details>
176
261
  <summary>Or manually</summary>
177
262
 
178
- Also add `require.resolve('@timobechtel/style/eslint/react.cjs')` to the `extends` array.
263
+ Also spread `styleReact` from `@timobechtel/style/eslint/react.js`:
264
+
265
+ ```js
266
+ import styleCore from '@timobechtel/style/eslint/core.js';
267
+ import styleReact from '@timobechtel/style/eslint/react.js';
268
+ import { defineConfig } from 'eslint/config';
269
+
270
+ export default defineConfig([
271
+ ...styleCore,
272
+ ...styleReact,
273
+ // ... your config
274
+ ]);
275
+ ```
179
276
 
180
- Example config:
181
- <https://raw.githubusercontent.com/TimoBechtel/style/refs/heads/main/templates/eslint/react/.eslintrc.cjs>
182
277
  </details>
183
278
 
279
+ #### Migration from v1.x
280
+
281
+ If you're upgrading from v1.x, you'll need to:
282
+
283
+ 1. Upgrade to ESLint v9+
284
+ 2. Replace `.eslintrc.cjs` with `eslint.config.js`
285
+ 3. Update imports to use `.js` extension (e.g., `@timobechtel/style/eslint/core.js`)
286
+ 4. Note: Import plugin rules now use `import-x/` prefix instead of `import/`
287
+
184
288
  #### VSCode
185
289
 
186
- Note: You should disable `source.organizeImports` in your VSCode config, as this collides with the `import/order` rule.
290
+ Note: You should disable `source.organizeImports` in your VSCode config, as this collides with the `import-x/order` rule.
187
291
 
188
292
  Add the following to your VSCode config, e.g. `.vscode/settings.json`
189
293
 
190
294
  ```json
191
295
  {
192
296
  "editor.codeActionsOnSave": {
193
- // use eslint import/order instead
297
+ // use eslint import-x/order instead
194
298
  "source.sortImports": "never"
195
299
  }
196
300
  }
197
301
  ```
198
302
 
303
+ </details>
304
+
199
305
  ### semantic-release
200
306
 
201
307
  This repo also contains a [semantic-release](https://github.com/semantic-release/semantic-release) configuration.
202
308
 
203
309
  ```bash
204
- npm i -D semantic-release
310
+ npm i -D semantic-release @semantic-release/changelog @semantic-release/git
311
+ ```
312
+
313
+ ```bash
314
+ gh get timobechtel/style templates/.releaserc.json
205
315
  ```
206
316
 
317
+ - [semantic-release template](https://github.com/TimoBechtel/style/blob/main/templates/.releaserc.json)
318
+
319
+ ### Agent Skills
320
+
207
321
  ```bash
208
- echo '{ "extends": "@timobechtel/style/semantic-release/index.cjs" }' > .releaserc.json
322
+ npx skills add timobechtel/style@setup-style
209
323
  ```
package/eslint/core.js ADDED
@@ -0,0 +1,86 @@
1
+ import js from '@eslint/js';
2
+ import timobechtelRulesPlugin from '@timobechtel/eslint-plugin-rules';
3
+ import prettierConfig from 'eslint-config-prettier/flat';
4
+ import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript';
5
+ import { createNodeResolver, importX } from 'eslint-plugin-import-x';
6
+ import unicornPlugin from 'eslint-plugin-unicorn';
7
+ import { defineConfig } from 'eslint/config';
8
+ import globals from 'globals';
9
+ import tseslint from 'typescript-eslint';
10
+
11
+ import baseRules from './rules/base.js';
12
+ import importRules from './rules/import.js';
13
+ import typescriptRules from './rules/typescript.js';
14
+ import unicornRules from './rules/unicorn.js';
15
+
16
+ const mergeRules = (configs) =>
17
+ Object.assign({}, ...configs.map((config) => config?.rules ?? {}));
18
+
19
+ export default defineConfig([
20
+ js.configs.recommended,
21
+ importX.flatConfigs.recommended,
22
+ importX.flatConfigs.typescript,
23
+ timobechtelRulesPlugin.configs['flat/all'],
24
+
25
+ {
26
+ languageOptions: {
27
+ globals: {
28
+ ...globals.browser,
29
+ ...globals.node,
30
+ ...globals.es2021,
31
+ },
32
+ ecmaVersion: 2021,
33
+ sourceType: 'module',
34
+ },
35
+ plugins: {
36
+ unicorn: unicornPlugin,
37
+ },
38
+ rules: {
39
+ ...mergeRules(baseRules),
40
+ ...mergeRules(importRules),
41
+ ...mergeRules(unicornRules),
42
+ },
43
+ linterOptions: {
44
+ reportUnusedDisableDirectives: true,
45
+ },
46
+ settings: {
47
+ 'import-x/resolver-next': [
48
+ createTypeScriptImportResolver(),
49
+ createNodeResolver(),
50
+ ],
51
+ },
52
+ },
53
+
54
+ ...tseslint.configs.recommended,
55
+ ...tseslint.configs.recommendedTypeChecked,
56
+ ...tseslint.configs.strict,
57
+ ...tseslint.configs.strictTypeChecked,
58
+ ...tseslint.configs.stylistic,
59
+ ...tseslint.configs.stylisticTypeChecked,
60
+ {
61
+ languageOptions: {
62
+ parserOptions: {
63
+ projectService: true,
64
+ },
65
+ },
66
+ },
67
+ {
68
+ files: ['**/*.js'],
69
+ extends: [tseslint.configs.disableTypeChecked],
70
+ },
71
+ {
72
+ files: ['**/*.ts', '**/*.tsx'],
73
+ rules: {
74
+ ...mergeRules(typescriptRules),
75
+ },
76
+ },
77
+
78
+ {
79
+ files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'],
80
+ rules: {
81
+ '@typescript-eslint/ban-ts-comment': 'off',
82
+ },
83
+ },
84
+
85
+ prettierConfig,
86
+ ]);
@@ -0,0 +1,32 @@
1
+ import prettierConfig from 'eslint-config-prettier/flat';
2
+ import reactPlugin from 'eslint-plugin-react';
3
+ import reactHooksPlugin from 'eslint-plugin-react-hooks';
4
+ import { defineConfig } from 'eslint/config';
5
+ import reactRules from './rules/react.js';
6
+
7
+ const mergeRules = (configs) =>
8
+ Object.assign({}, ...configs.map((config) => config?.rules ?? {}));
9
+
10
+ export default defineConfig([
11
+ reactPlugin.configs.flat.recommended,
12
+ reactPlugin.configs.flat['jsx-runtime'],
13
+
14
+ {
15
+ plugins: {
16
+ react: reactPlugin,
17
+ 'react-hooks': reactHooksPlugin,
18
+ },
19
+ settings: {
20
+ react: {
21
+ version: 'detect',
22
+ },
23
+ linkComponents: ['Link'],
24
+ },
25
+ rules: {
26
+ ...reactHooksPlugin.configs.flat.recommended.rules,
27
+ ...mergeRules(reactRules),
28
+ },
29
+ },
30
+
31
+ prettierConfig,
32
+ ]);
@@ -0,0 +1,52 @@
1
+ import { defineConfig } from 'eslint/config';
2
+
3
+ export default defineConfig({
4
+ rules: {
5
+ 'prefer-arrow-callback': [
6
+ 'warn',
7
+ {
8
+ allowNamedFunctions: true,
9
+ allowUnboundThis: true,
10
+ },
11
+ ],
12
+ 'no-console': [
13
+ 'error',
14
+ {
15
+ allow: ['warn', 'error', 'clear', 'info'],
16
+ },
17
+ ],
18
+ 'max-depth': ['warn', 4],
19
+ 'no-restricted-globals': ['error', 'event', 'name'],
20
+ curly: ['warn', 'multi-line'],
21
+ 'default-case-last': 'error',
22
+ eqeqeq: 'error',
23
+ 'no-alert': 'error',
24
+ 'no-useless-rename': 'warn',
25
+ 'no-var': 'error',
26
+ 'object-shorthand': 'warn',
27
+ 'prefer-const': 'warn',
28
+ 'prefer-rest-params': 'error',
29
+ 'prefer-spread': 'error',
30
+ 'prefer-template': 'warn',
31
+ 'no-promise-executor-return': 'error',
32
+ 'no-unreachable-loop': 'error',
33
+ 'new-cap': ['error', { capIsNew: false }],
34
+ 'new-parens': 'warn',
35
+ 'no-lonely-if': 'warn',
36
+ 'no-unneeded-ternary': 'error',
37
+ 'prefer-object-spread': 'warn',
38
+ 'no-label-var': 'error',
39
+ 'no-undef-init': 'warn',
40
+ 'no-unused-vars': [
41
+ 'error',
42
+ {
43
+ args: 'after-used',
44
+ argsIgnorePattern: '^_',
45
+ ignoreRestSiblings: false,
46
+ vars: 'all',
47
+ varsIgnorePattern: '^_',
48
+ },
49
+ ],
50
+ 'no-constant-binary-expression': 'error',
51
+ },
52
+ });
@@ -1,12 +1,10 @@
1
- // @ts-check
2
- const { defineConfig } = require('eslint-define-config');
1
+ import { defineConfig } from 'eslint/config';
3
2
 
4
- module.exports = defineConfig({
3
+ export default defineConfig({
5
4
  rules: {
6
5
  'no-restricted-syntax': [
7
6
  'error',
8
7
  {
9
- // catches common mistakes when writing comments in CSS
10
8
  selector:
11
9
  'TemplateElement[value.cooked=/\\s+\\u002F\\u002F\\s*/], Literal[value=/\\s+\\u002F\\u002F\\s*/]',
12
10
  message: 'Invalid comment syntax. Use `/* */` instead of `//`.',
@@ -0,0 +1,28 @@
1
+ import { defineConfig } from 'eslint/config';
2
+
3
+ export default defineConfig({
4
+ rules: {
5
+ 'import-x/first': 'error',
6
+ 'import-x/newline-after-import': 'warn',
7
+ 'import-x/no-self-import': 'error',
8
+ 'import-x/no-useless-path-segments': ['error'],
9
+ 'import-x/order': [
10
+ 'warn',
11
+ {
12
+ groups: [
13
+ 'builtin',
14
+ 'external',
15
+ 'internal',
16
+ 'parent',
17
+ 'sibling',
18
+ 'index',
19
+ ],
20
+ },
21
+ ],
22
+ 'import-x/no-default-export': 'off',
23
+ 'import-x/default': 'off',
24
+ 'import-x/export': 'off',
25
+ 'import-x/namespace': 'off',
26
+ 'import-x/no-unresolved': 'off',
27
+ },
28
+ });
@@ -0,0 +1,40 @@
1
+ import { defineConfig } from 'eslint/config';
2
+
3
+ export default defineConfig({
4
+ rules: {
5
+ 'react/prop-types': 'off',
6
+ 'react/react-in-jsx-scope': 'off',
7
+ 'react/button-has-type': 'error',
8
+ 'react/function-component-definition': 'warn',
9
+ 'react/hook-use-state': 'warn',
10
+ 'react/jsx-boolean-value': 'warn',
11
+ 'react/jsx-curly-brace-presence': [
12
+ 'warn',
13
+ {
14
+ props: 'never',
15
+ children: 'ignore',
16
+ propElementValues: 'always',
17
+ },
18
+ ],
19
+ 'react/jsx-fragments': 'warn',
20
+ 'react/jsx-no-leaked-render': 'error',
21
+ 'react/jsx-no-target-blank': [
22
+ 'error',
23
+ {
24
+ allowReferrer: true,
25
+ },
26
+ ],
27
+ 'react/jsx-no-useless-fragment': ['warn', { allowExpressions: true }],
28
+ 'react/jsx-pascal-case': 'warn',
29
+ 'react/jsx-sort-props': [
30
+ 'warn',
31
+ {
32
+ callbacksLast: true,
33
+ },
34
+ ],
35
+ 'react/no-unstable-nested-components': 'error',
36
+ 'react/self-closing-comp': 'warn',
37
+ 'react-hooks/exhaustive-deps': 'error',
38
+ 'react/destructuring-assignment': ['warn', 'always'],
39
+ },
40
+ });