podcast manager
1import {includeIgnoreFile} from '@eslint/compat'
2import js from '@eslint/js'
3import json from '@eslint/json'
4import restrictedGlobals from 'confusing-browser-globals'
5import prettier from 'eslint-plugin-prettier/recommended'
6import react from 'eslint-plugin-react'
7import reactHooks from 'eslint-plugin-react-hooks'
8import tsdoc from 'eslint-plugin-tsdoc'
9import {defineConfig} from 'eslint/config'
10import globals from 'globals'
11import path from 'node:path'
12import tseslint from 'typescript-eslint'
13
14const gitignore = path.resolve(import.meta.dirname, '.gitignore')
15
16export default defineConfig(
17 includeIgnoreFile(gitignore, '.gitignore'),
18
19 // all files by default get shared globals
20 {
21 name: 'javascript basics',
22 files: ['**/*.@(js|jsx|ts|tsx)'],
23 extends: [js.configs.recommended],
24
25 languageOptions: {
26 globals: {
27 ...globals.es2024,
28 ...globals['shared-node-browser'],
29 },
30 },
31
32 rules: {
33 // eg, `open` is a global, but probably not really intended that way in our code
34 'no-restricted-globals': ['error', ...restrictedGlobals],
35 'no-unused-vars': ['warn', {varsIgnorePattern: '(?:^_)'}],
36 },
37 },
38
39 {
40 name: 'typescript basics',
41 files: ['**/*.@(ts|tsx)'],
42 extends: [tseslint.configs.strictTypeChecked],
43 plugins: {tsdoc},
44 languageOptions: {
45 parserOptions: {
46 projectService: true,
47 tsconfigRootDir: import.meta.dirname,
48 },
49 },
50 rules: {
51 // allow leading underscore for marking unused vars
52 '@typescript-eslint/no-unused-vars': ['warn', {varsIgnorePattern: '(?:^_)'}],
53
54 // template literals default to calling toString(), but I mean what I say
55 '@typescript-eslint/restrict-template-expressions': 'off',
56
57 // I need to be able to do `while(true)`, come on yall...
58 '@typescript-eslint/no-unnecessary-condition': ['warn', {allowConstantLoopConditions: 'always'}],
59
60 // this breaks when I want to use a type parameter to allow type checking inline arguments
61 // it's "unnecessary type parameters" or an `as` declaration, which I don't like
62 '@typescript-eslint/no-unnecessary-type-parameters': 'off',
63 '@typescript-eslint/no-unnecessary-type-constraint': 'off',
64
65 // the breaks Promise.withResolvers<void>(),
66 // see https://github.com/typescript-eslint/typescript-eslint/issues/8113
67 '@typescript-eslint/no-invalid-void-type': 'off',
68
69 // make sure the docs look good
70 'tsdoc/syntax': 'warn',
71 },
72 },
73
74 {
75 name: 'node files',
76 files: ['src/server/**/*.@(js|jsx|ts|tsx)', 'src/cmd/**/*.@(js|jsx|ts|tsx)'],
77 languageOptions: {
78 globals: {
79 ...globals.es2024,
80 ...globals.node,
81 },
82 },
83 },
84
85 {
86 // mostly cribbed from preact's config, but that's not setup to handle eslint9
87 // https://github.com/preactjs/eslint-config-preact/blob/master/index.js
88 name: 'client files',
89 files: ['src/client/**/*.@(js|ts)', 'src/**/*.@(jsx|tsx)'],
90 languageOptions: {
91 globals: {
92 ...globals.es2024,
93 ...globals.browser,
94 },
95 },
96 plugins: {
97 react,
98 reactHooks,
99 },
100 settings: {
101 react: {
102 pragma: 'h',
103 version: '16.0',
104 },
105 },
106 rules: {
107 // preact / jsx rules
108 'react/no-deprecated': 2,
109 'react/react-in-jsx-scope': 0, // handled this automatically
110 'react/display-name': [1, {ignoreTranspilerName: false}],
111 'react/jsx-no-bind': [
112 1,
113 {
114 ignoreRefs: true,
115 allowFunctions: true,
116 allowArrowFunctions: true,
117 },
118 ],
119 'react/jsx-no-comment-textnodes': 2,
120 'react/jsx-no-duplicate-props': 2,
121 'react/jsx-no-target-blank': 2,
122 'react/jsx-no-undef': 2,
123 'react/jsx-tag-spacing': [2, {beforeSelfClosing: 'always'}],
124 'react/jsx-uses-react': 1, // debatable
125 'react/jsx-uses-vars': 2,
126 'react/jsx-key': [2, {checkFragmentShorthand: true}],
127 'react/self-closing-comp': 2,
128 'react/prefer-es6-class': 2,
129 'react/prefer-stateless-function': 1,
130 'react/require-render-return': 2,
131 'react/no-danger': 1,
132
133 // Legacy APIs not supported in Preact:
134 'react/no-did-mount-set-state': 2,
135 'react/no-did-update-set-state': 2,
136 'react/no-find-dom-node': 2,
137 'react/no-is-mounted': 2,
138 'react/no-string-refs': 2,
139
140 // hooks
141 'reactHooks/rules-of-hooks': 2,
142 'reactHooks/exhaustive-deps': 1,
143 },
144 },
145
146 // tests don't have jsdoc requirements
147 {
148 files: ['src/**/*.spec.{js,jsx}'],
149 rules: {},
150 },
151
152 // json (with comments in some files)
153 {
154 files: ['**/*.json'],
155 ignores: ['package-lock.json'],
156
157 plugins: {json},
158 language: 'json/json',
159 extends: [json.configs.recommended],
160 },
161 {
162 files: ['tsconfig.json'],
163 language: 'json/jsonc',
164 },
165
166 // prettier last, so it can turn everything off
167 prettier,
168)