Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
1// @ts-check
2import js from '@eslint/js'
3import tseslint from 'typescript-eslint'
4import { defineConfig } from 'eslint/config';
5import react from 'eslint-plugin-react'
6import reactHooks from 'eslint-plugin-react-hooks'
7// @ts-expect-error no types
8import reactNative from 'eslint-plugin-react-native'
9// @ts-expect-error no types
10import reactNativeA11y from 'eslint-plugin-react-native-a11y'
11import simpleImportSort from 'eslint-plugin-simple-import-sort'
12import importX from 'eslint-plugin-import-x'
13import lingui from 'eslint-plugin-lingui'
14import reactCompiler from 'eslint-plugin-react-compiler'
15import bskyInternal from 'eslint-plugin-bsky-internal'
16import globals from 'globals'
17import tsParser from '@typescript-eslint/parser'
18
19export default defineConfig(
20 /**
21 * Global ignores
22 */
23 {
24 ignores: [
25 '**/__mocks__/*.ts',
26 'ios/**',
27 'android/**',
28 'coverage/**',
29 '*.lock',
30 '.husky/**',
31 'patches/**',
32 '*.html',
33 'bskyweb/**',
34 'bskyembed/**',
35 'src/locale/locales/_build/**',
36 'src/locale/locales/**/*.js',
37 '*.e2e.ts',
38 '*.e2e.tsx',
39 'eslint.config.mjs',
40 'svgo.config.mjs',
41 '.jscodeshift/**',
42 'rspack.config.ts',
43 'scripts/post-web-build.js',
44 ],
45 },
46
47 /**
48 * Base configurations
49 */
50 js.configs.recommended,
51 tseslint.configs.recommendedTypeChecked,
52 reactHooks.configs.flat.recommended,
53 importX.flatConfigs.recommended,
54 importX.flatConfigs.typescript,
55 importX.flatConfigs['react-native'],
56
57 /**
58 * Main configuration for all JS/TS/JSX/TSX files
59 */
60 {
61 files: ['**/*.{js,jsx,ts,tsx}'],
62 plugins: {
63 react,
64 'react-native': reactNative,
65 'react-native-a11y': reactNativeA11y,
66 'simple-import-sort': simpleImportSort,
67 // @ts-expect-error - not sure why
68 lingui,
69 'react-compiler': reactCompiler,
70 'bsky-internal': bskyInternal,
71 },
72 languageOptions: {
73 ecmaVersion: 'latest',
74 sourceType: 'module',
75 globals: {
76 ...globals.browser,
77 ...globals.node,
78 },
79 parserOptions: {
80 parser: tsParser,
81 projectService: true,
82 tsconfigRootDir: import.meta.dirname,
83 ecmaFeatures: {
84 jsx: true,
85 },
86 },
87 },
88 settings: {
89 react: {
90 version: 'detect',
91 },
92 componentWrapperFunctions: ['observer'],
93 },
94 rules: {
95 /**
96 * Custom rules
97 */
98 'bsky-internal/avoid-unwrapped-text': [
99 'error',
100 {
101 impliedTextComponents: [
102 'H1',
103 'H2',
104 'H3',
105 'H4',
106 'H5',
107 'H6',
108 'P',
109 'Admonition',
110 'Admonition.Admonition',
111 'Toast.Action',
112 'AgeAssuranceAdmonition',
113 'Span',
114 'StackedButton',
115 ],
116 impliedTextProps: [],
117 suggestedTextWrappers: {
118 Button: 'ButtonText',
119 'ToggleButton.Button': 'ToggleButton.ButtonText',
120 'SegmentedControl.Item': 'SegmentedControl.ItemText',
121 },
122 },
123 ],
124 'bsky-internal/use-exact-imports': 'error',
125 'bsky-internal/use-prefixed-imports': 'error',
126 'bsky-internal/lingui-msg-rule': 'error',
127
128 /**
129 * React & React Native
130 */
131 ...react.configs.recommended.rules,
132 ...react.configs['jsx-runtime'].rules,
133 'react/hook-use-state': 'warn',
134 'react/no-unescaped-entities': 'off',
135 'react/prop-types': 'off',
136 'react-native/no-inline-styles': 'off',
137 ...reactNativeA11y.configs.all.rules,
138 'react-compiler/react-compiler': 'warn',
139 // TODO: Fix these and set to error
140 'react-hooks/set-state-in-effect': 'warn',
141 'react-hooks/purity': 'warn',
142 'react-hooks/refs': 'warn',
143 'react-hooks/immutability': 'warn',
144
145 /**
146 * Import sorting
147 */
148 'simple-import-sort/imports': [
149 'error',
150 {
151 groups: [
152 // Side effect imports.
153 ['^\\u0000'],
154 // Node.js builtins prefixed with `node:`.
155 ['^node:'],
156 // Packages.
157 // Things that start with a letter (or digit or underscore), or `@` followed by a letter.
158 // React/React Native prioritized, followed by expo
159 // Followed by all packages excluding unprefixed relative ones
160 [
161 '^(react\\/(.*)$)|^(react$)|^(react-native(.*)$)',
162 '^(expo(.*)$)|^(expo$)',
163 '^(?!(?:alf|components|lib|locale|logger|platform|screens|state|view)(?:$|\\/))@?\\w',
164 ],
165 // Relative imports.
166 // Ideally, anything that starts with a dot or #
167 // due to unprefixed relative imports being used, we whitelist the relative paths we use
168 // (?:$|\\/) matches end of string or /
169 [
170 '^(?:#\\/)?(?:lib|state|logger|platform|locale)(?:$|\\/)',
171 '^(?:#\\/)?view(?:$|\\/)',
172 '^(?:#\\/)?screens(?:$|\\/)',
173 '^(?:#\\/)?alf(?:$|\\/)',
174 '^(?:#\\/)?components(?:$|\\/)',
175 '^#\\/',
176 '^\\.',
177 ],
178 // anything else - hopefully we don't have any of these
179 ['^'],
180 ],
181 },
182 ],
183 'simple-import-sort/exports': 'error',
184
185 /**
186 * Import linting
187 */
188 'import-x/consistent-type-specifier-style': ['warn', 'prefer-inline'],
189 'import-x/no-unresolved': ['error', {
190 /*
191 * The `postinstall` hook runs `compile-if-needed` locally, but not in
192 * CI. For CI-sake, ignore this.
193 */
194 ignore: ['^#\/locale\/locales\/.+\/messages'],
195 }],
196 'import-x/no-extraneous-dependencies': ['error', {
197 'whitelist': [
198 // test files only
199 '@jest/globals',
200 // we only use a really simple util from this, and we know it will be present
201 'expo-modules-core',
202 // this is a dep for @atproto/api, but we absolutely need them in sync, so just
203 // rely on the transient version
204 '@atproto/common-web',
205 ]
206 }],
207 'import-x/no-nodejs-modules': 'error',
208
209 /**
210 * TypeScript-specific rules
211 */
212 'no-unused-vars': 'off', // off, we use TS-specific rule below
213 '@typescript-eslint/no-unused-vars': [
214 'error',
215 {
216 argsIgnorePattern: '^_',
217 varsIgnorePattern: '^_.+',
218 caughtErrors: 'none',
219 ignoreRestSiblings: true,
220 },
221 ],
222 '@typescript-eslint/consistent-type-imports': [
223 'warn',
224 {prefer: 'type-imports', fixStyle: 'inline-type-imports'},
225 ],
226 '@typescript-eslint/no-require-imports': 'off',
227 '@typescript-eslint/no-unused-expressions': ['error', {
228 allowTernary: true,
229 }],
230 /**
231 * Maintain previous behavior - these are stricter in typescript-eslint
232 * v8 `warn` ones are probably worth fixing. `off` ones are a bit too
233 * nit-picky
234 */
235 '@typescript-eslint/no-explicit-any': 'off',
236 '@typescript-eslint/ban-ts-comment': 'off',
237 '@typescript-eslint/no-empty-object-type': 'off',
238 '@typescript-eslint/no-unsafe-function-type': 'off',
239 '@typescript-eslint/no-unsafe-assignment': 'off',
240 '@typescript-eslint/unbound-method': 'off',
241 '@typescript-eslint/no-unsafe-argument': 'off',
242 '@typescript-eslint/no-unsafe-return': 'off',
243 '@typescript-eslint/no-unsafe-member-access': 'warn',
244 '@typescript-eslint/no-unsafe-call': 'warn',
245 '@typescript-eslint/no-floating-promises': 'warn',
246 '@typescript-eslint/no-misused-promises': 'warn',
247 '@typescript-eslint/require-await': 'warn',
248 '@typescript-eslint/no-unsafe-enum-comparison': 'warn',
249 '@typescript-eslint/no-unnecessary-type-assertion': 'warn',
250 '@typescript-eslint/no-redundant-type-constituents': 'warn',
251 '@typescript-eslint/no-duplicate-type-constituents': 'warn',
252 '@typescript-eslint/no-base-to-string': 'warn',
253 '@typescript-eslint/prefer-promise-reject-errors': 'warn',
254 '@typescript-eslint/await-thenable': 'warn',
255
256 "no-restricted-imports": ["error", {
257 "paths": [{
258 "name": "react",
259 "importNames": ["React", "default"],
260 "message": "React is already in the global type namespace. Use named imports for runtime modules."
261 }]
262 }],
263
264 /**
265 * Turn off rules that we haven't enforced thus far
266 */
267 'no-empty-pattern': 'off',
268 'no-async-promise-executor': 'off',
269 'no-constant-binary-expression': 'warn',
270 'prefer-const': 'off',
271 'no-empty': 'off',
272 'no-unsafe-optional-chaining': 'off',
273 'no-prototype-builtins': 'off',
274 'no-var': 'off',
275 'prefer-rest-params': 'off',
276 'no-case-declarations': 'off',
277 'no-irregular-whitespace': 'off',
278 'no-useless-escape': 'off',
279 'no-sparse-arrays': 'off',
280 'no-fallthrough': 'off',
281 'no-control-regex': 'off',
282 },
283 },
284
285 /**
286 * Test files configuration
287 */
288 {
289 files: ['**/__tests__/**/*.{js,jsx,ts,tsx}', '**/*.test.{js,jsx,ts,tsx}'],
290 languageOptions: {
291 globals: {
292 ...globals.jest,
293 }
294 },
295 },
296
297 /**
298 * Expo config plugins run in Node during prebuild, not in the app runtime.
299 */
300 {
301 files: ['plugins/**/*.{js,jsx,ts,tsx}'],
302 rules: {
303 'import-x/no-nodejs-modules': 'off',
304 'import-x/no-extraneous-dependencies': ['error', {
305 whitelist: [
306 '@expo/plist',
307 ],
308 }],
309 },
310 },
311)