@luxfi/eslint-config 1.0.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/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ Copyright 2022 Uniswap Labs
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # ESLint
2
+
3
+ ## Custom Rules
4
+
5
+ ### Prelude
6
+
7
+ In most cases, a [core](https://eslint.org/docs/latest/rules/) or community rule will suffice for your use case, as these rules tend to be robust and performant; use a custom rule when these rules cover too little for your specific use case (ie not robust/applicable) or too much (ie not performant).
8
+
9
+ ### Steps
10
+
11
+ 1. Ensure there is no available rule that sufficiently covers your use case
12
+ 2. Create two files in `plugins/`: `<your-lint-name>.js` and `<your-lint-name>.test.js`
13
+ - align naming with [ESLint core naming conventions](https://eslint.org/docs/latest/contribute/core-rules#rule-naming-conventions)
14
+ 3. Create your lint rule using steps 2-4 [here](https://eslint.org/docs/latest/extend/custom-rule-tutorial#step-2-stub-out-the-rule-file)
15
+ - it's much easier to find the node names (eg, JSXText, JSXExpressionContainer) for the visitor methods using [this explorer](https://astexplorer.net/)
16
+ 4. [Add tests](https://eslint.org/docs/latest/extend/custom-rule-tutorial#step-6-write-the-test)
17
+ 5. Import new rule to [`universe/eslint-local-rules`](../../eslint-local-rules.js)
18
+ 6. Add your shiny new rule to [`native.js`](./native.js), [`base.js`](./base.js), and – if it's a react lint rule - [`react.js`](./react.js)
19
+ 7. **Test, test, test**; cover all your bases
20
+ - remember that this rule will checked against nearly every single LOC in the codebase
21
+ 8. Profile with `TIMING=1 bun g:lint` and ensure your rule is **performant**
22
+ - rules should generally not exceed 25 ms (~1%) for each package
23
+
24
+ ### Tips and resources
25
+
26
+ - For configurable lists (eg greenlisted elements), lean towards passing a variable as a rule config rather than using a const, so as to avoid having to update the rule itself as the codebase evolves
27
+ - Avoid traversing children in a custom rule to avoid performance issues. Ideally, you target the nodes directly
28
+ - As with any lint rule, make sure the tradeoff in DevX (eg slower lint times, more styling considerations) is worth the benefits
29
+ - Utilize the [ESLint docs](https://eslint.org/docs/latest/extend/custom-rules)
package/base.js ADDED
@@ -0,0 +1,177 @@
1
+ // The ESLint browser environment defines all browser globals as valid,
2
+ // even though most people don't know some of them exist (e.g. `name` or `status`).
3
+ // This is dangerous as it hides accidentally undefined variables.
4
+ // We blocklist the globals that we deem potentially confusing.
5
+ // To use them, explicitly reference them, e.g. `window.name` or `window.status`.
6
+ const restrictedGlobals = require('confusing-browser-globals')
7
+
8
+ module.exports = {
9
+ env: {
10
+ es6: true,
11
+ node: true,
12
+ },
13
+ plugins: [
14
+ 'import',
15
+ 'unused-imports',
16
+ 'check-file',
17
+ 'local-rules',
18
+ 'react',
19
+ 'react-hooks',
20
+ 'security',
21
+ 'no-unsanitized',
22
+ '@typescript-eslint',
23
+ ],
24
+ extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:react-hooks/recommended'],
25
+ parserOptions: {
26
+ ecmaVersion: 2020,
27
+ sourceType: 'module',
28
+ },
29
+ rules: {
30
+ // Imports and file naming
31
+ 'check-file/no-index': ['error', { ignoreMiddleExtensions: true }],
32
+ 'unused-imports/no-unused-imports': 'error',
33
+
34
+ // Basics
35
+ 'react/display-name': 'error',
36
+ 'no-shadow': 'off',
37
+ 'no-ex-assign': 'error',
38
+ 'no-eval': 'error',
39
+ 'guard-for-in': 'error',
40
+ 'no-extra-boolean-cast': 'error',
41
+ 'object-shorthand': ['error', 'always'],
42
+ 'consistent-return': ['error', { treatUndefinedAsUnspecified: false }],
43
+ 'max-lines': ['error', 500], // cap file length
44
+ // Disallow unnecessary curly braces in JSX props and children
45
+ 'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }],
46
+ 'max-params': ['error', { max: 2 }],
47
+
48
+ // Rules within the standard React plugin
49
+ 'react/no-danger': 'error',
50
+ 'react/no-danger-with-children': 'error',
51
+ 'react/no-unsafe': 'error',
52
+
53
+ // Security Linting
54
+ // Mozilla's No Unsanitized - https://github.com/mozilla/eslint-plugin-no-unsanitized
55
+ 'no-unsanitized/method': 'error',
56
+ 'no-unsanitized/property': 'error',
57
+ // Generic Security Linting - https://www.npmjs.com/package/eslint-plugin-security
58
+ 'security/detect-unsafe-regex': 'error',
59
+ 'security/detect-buffer-noassert': 'error',
60
+ 'security/detect-child-process': 'error',
61
+ 'security/detect-disable-mustache-escape': 'error',
62
+ 'security/detect-eval-with-expression': 'error',
63
+ 'security/detect-non-literal-regexp': 'error',
64
+ 'security/detect-pseudoRandomBytes': 'error',
65
+ 'security/detect-new-buffer': 'error',
66
+
67
+ // Globals.
68
+ // The Extension config overrides this rule, so make sure to verify if we need to
69
+ // update `apps/extension/.eslintrc.js` if you make any changes here.
70
+ 'no-restricted-globals': ['error'].concat(restrictedGlobals, [
71
+ {
72
+ name: 'chrome',
73
+ message:
74
+ 'Direct `chrome` access is restricted to prevent accidental usage in the wrong context. Use `getChrome()` or `getChromeWithThrow()` instead.',
75
+ },
76
+ ]),
77
+
78
+ // Custom Rules
79
+ 'local-rules/no-unwrapped-t': ['error', { blockedElements: ['Flex', 'AnimatedFlex', 'TouchableArea', 'Trace'] }],
80
+
81
+ '@typescript-eslint/no-unused-vars': [
82
+ 'error',
83
+ {
84
+ args: 'all',
85
+ argsIgnorePattern: '^_',
86
+ caughtErrors: 'all',
87
+ caughtErrorsIgnorePattern: 'e',
88
+ destructuredArrayIgnorePattern: '^_',
89
+ varsIgnorePattern: '^_',
90
+ ignoreRestSiblings: true,
91
+ },
92
+ ],
93
+ },
94
+ overrides: [
95
+ // All Typescript Files
96
+ {
97
+ files: ['*.ts', '*.tsx'],
98
+ parser: '@typescript-eslint/parser',
99
+ plugins: ['@typescript-eslint/eslint-plugin'],
100
+ extends: ['plugin:@typescript-eslint/recommended', 'plugin:import/typescript'],
101
+ settings: {
102
+ 'import/parsers': {
103
+ '@typescript-eslint/parser': ['.ts', '.tsx'],
104
+ },
105
+ 'import/resolver': {
106
+ typescript: {
107
+ alwaysTryTypes: true,
108
+ },
109
+ },
110
+ },
111
+ rules: {
112
+ 'local-rules/prevent-this-method-destructure': 'error',
113
+ 'local-rules/enforce-query-options-result': [
114
+ 'error',
115
+ {
116
+ importPath: 'utilities/src/reactQuery/queryOptions',
117
+ },
118
+ ],
119
+ curly: 'error',
120
+
121
+ // Disable dot-notation to allow TypeScript's noPropertyAccessFromIndexSignature
122
+ 'dot-notation': 'off',
123
+ '@typescript-eslint/dot-notation': 'off',
124
+
125
+ '@typescript-eslint/prefer-enum-initializers': 'error',
126
+ '@typescript-eslint/no-explicit-any': 'off',
127
+ '@typescript-eslint/ban-ts-comment': 'off',
128
+ '@typescript-eslint/ban-ts-ignore': 'off',
129
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
130
+ '@typescript-eslint/explicit-function-return-type': 'off',
131
+ '@typescript-eslint/no-unnecessary-condition': [
132
+ 'error',
133
+ {
134
+ allowConstantLoopConditions: true,
135
+ },
136
+ ],
137
+ },
138
+ },
139
+ // Non-Test Typescript Files
140
+ {
141
+ files: ['*.ts', '*.tsx'],
142
+ excludedFiles: ['*.test.ts', '*.test.tsx'],
143
+ rules: {
144
+ 'no-console': 'error',
145
+ 'local-rules/no-hex-string-casting': 'error',
146
+ 'react/forbid-elements': [
147
+ 'error',
148
+ {
149
+ forbid: [
150
+ {
151
+ element: 'div',
152
+ message: 'Please avoid using div when possible, even in web code! Use `Flex` or Fragments (`<>`).',
153
+ },
154
+ ],
155
+ },
156
+ ],
157
+ },
158
+ },
159
+ // Test Files
160
+ {
161
+ files: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[tj]s?(x)', '*.e2e.js'],
162
+ env: {
163
+ jest: true,
164
+ 'jest/globals': true,
165
+ },
166
+ extends: ['plugin:jest/recommended'],
167
+ plugins: ['jest'],
168
+ },
169
+ {
170
+ // Allow test files to exceed max-lines limit
171
+ files: ['**/*.test.ts', '**/*.test.tsx', '**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[tj]s?(x)'],
172
+ rules: {
173
+ 'max-lines': 'off',
174
+ },
175
+ },
176
+ ],
177
+ }
@@ -0,0 +1,241 @@
1
+ // Below are the rules, we still need to migrate to biome
2
+ //
3
+ // Rules that can be migrated to an inspired rule using --include-inspired:
4
+ //
5
+ // - no-cond-assign
6
+ // - no-labels
7
+ // - object-shorthand
8
+ // - @typescript-eslint/ban-ts-comment
9
+ // - @typescript-eslint/explicit-function-return-type
10
+ // - @typescript-eslint/naming-convention
11
+ // - @typescript-eslint/no-empty-interface
12
+ // - @typescript-eslint/no-this-alias
13
+ // - jest/no-focused-tests
14
+ // - jest/no-standalone-expect
15
+ // - react/jsx-curly-brace-presence
16
+ // - react/jsx-no-target-blank
17
+ //
18
+ // Rules that can be migrated to a nursery rule using --include-nursery:
19
+ //
20
+ // - max-params
21
+ // - @typescript-eslint/no-floating-promises
22
+ // - @typescript-eslint/no-non-null-asserted-optional-chain
23
+ // - @typescript-eslint/no-unnecessary-condition
24
+ //
25
+ // Stylistic rules that the formatter may support (manual migration required):
26
+ //
27
+ // - eol-last
28
+ // - jsx-quotes
29
+ // - keyword-spacing
30
+ // - new-parens
31
+ // - no-extra-semi
32
+ // - no-floating-decimal
33
+ // - no-mixed-spaces-and-tabs
34
+ // - semi-spacing
35
+ // - space-infix-ops
36
+ // - space-unary-ops
37
+ //
38
+ // Unsupported rules:
39
+ //
40
+ // - complexity
41
+ // - consistent-return
42
+ // - consistent-this
43
+ // - handle-callback-err
44
+ // - max-depth
45
+ // - max-lines
46
+ // - max-nested-callbacks
47
+ // - no-caller
48
+ // - no-catch-shadow
49
+ // - no-delete-var
50
+ // - no-div-regex
51
+ // - no-extend-native
52
+ // - no-extra-bind
53
+ // - no-implied-eval
54
+ // - no-invalid-regexp
55
+ // - no-iterator
56
+ // - no-mixed-requires
57
+ // - no-negated-in-lhs
58
+ // - no-new
59
+ // - no-new-func
60
+ // - no-new-object
61
+ // - no-new-require
62
+ // - no-octal
63
+ // - no-path-concat
64
+ // - no-proto
65
+ // - no-restricted-modules
66
+ // - no-restricted-syntax
67
+ // - no-return-assign
68
+ // - no-script-url
69
+ // - prefer-spread
70
+ // - @jambit/typed-redux-saga/delegate-effects
71
+ // - @jambit/typed-redux-saga/use-typed-effects
72
+ // - @typescript-eslint/func-call-spacing
73
+ // - @typescript-eslint/no-duplicate-enum-values
74
+ // - @typescript-eslint/no-shadow
75
+ // - @typescript-eslint/no-unsafe-return
76
+ // - @typescript-eslint/no-unused-expressions
77
+ // - @typescript-eslint/triple-slash-reference
78
+ // - check-file/no-index
79
+ // - eslint-comments/no-aggregating-enable
80
+ // - eslint-comments/no-unlimited-disable
81
+ // - eslint-comments/no-unused-disable
82
+ // - eslint-comments/no-unused-enable
83
+ // - import/no-unused-modules
84
+ // - jest/expect-expect
85
+ // - jest/no-alias-methods
86
+ // - jest/no-commented-out-tests
87
+ // - jest/no-deprecated-functions
88
+ // - jest/no-identical-title
89
+ // - jest/no-interpolation-in-snapshots
90
+ // - jest/no-jasmine-globals
91
+ // - jest/no-mocks-import
92
+ // - jest/no-test-prefixes
93
+ // - jest/valid-expect
94
+ // - jest/valid-expect-in-promise
95
+ // - jest/valid-title
96
+ // - local-rules/enforce-query-options-result
97
+ // - local-rules/no-hex-string-casting
98
+ // - local-rules/no-unwrapped-t
99
+ // - local-rules/prevent-this-method-destructure
100
+ // - no-relative-import-paths/no-relative-import-paths
101
+ // - no-unsanitized/method
102
+ // - no-unsanitized/property
103
+ // - react/jsx-no-undef
104
+ // - react/jsx-sort-props
105
+ // - react/jsx-uses-react
106
+ // - react/jsx-uses-vars
107
+ // - react/no-deprecated
108
+ // - react/no-did-mount-set-state
109
+ // - react/no-did-update-set-state
110
+ // - react/no-direct-mutation-state
111
+ // - react/no-find-dom-node
112
+ // - react/no-is-mounted
113
+ // - react/no-render-return-value
114
+ // - react/no-string-refs
115
+ // - react/no-unescaped-entities
116
+ // - react/no-unsafe
117
+ // - react/no-unstable-nested-components
118
+ // - react/require-render-return
119
+ // - react/self-closing-comp
120
+ // - react-native/no-unused-styles
121
+ // - react-native/sort-styles
122
+ // - rulesdir/i18n
123
+ // - rulesdir/no-redux-modals
124
+ // - security/detect-buffer-noassert
125
+ // - security/detect-child-process
126
+ // - security/detect-disable-mustache-escape
127
+ // - security/detect-eval-with-expression
128
+ // - security/detect-new-buffer
129
+ // - security/detect-non-literal-regexp
130
+ // - security/detect-pseudoRandomBytes
131
+ // - security/detect-unsafe-regex
132
+ //
133
+ // Paritally migrated!
134
+ // - @typescript-eslint/no-restricted-imports - biome doesn't have allowTypeImports param.
135
+ // So, keep this eslint rule for @lux/smart-order-router and react-native related imports
136
+
137
+ // Rules that have been migrated to Biome and should be disabled in ESLint
138
+ // by overriding them inside .eslintrc.* files, so eslint does not check them
139
+ module.exports = {
140
+ // Standard ESLint rules
141
+ curly: 'off',
142
+ 'dot-notation': 'off',
143
+ eqeqeq: 'off',
144
+ 'for-direction': 'off',
145
+ 'no-alert': 'off',
146
+ 'no-async-promise-executor': 'off',
147
+ 'no-bitwise': 'off',
148
+ 'no-case-declarations': 'off',
149
+ 'no-class-assign': 'off',
150
+ 'no-compare-neg-zero': 'off',
151
+ 'no-console': 'off',
152
+ 'no-control-regex': 'off',
153
+ 'no-debugger': 'off',
154
+ 'no-dupe-else-if': 'off',
155
+ 'no-duplicate-case': 'off',
156
+ 'no-empty-character-class': 'off',
157
+ 'no-empty-pattern': 'off',
158
+ 'no-eval': 'off',
159
+ 'no-ex-assign': 'off',
160
+ 'no-extra-boolean-cast': 'off',
161
+ 'no-fallthrough': 'off',
162
+ 'no-global-assign': 'off',
163
+ 'no-irregular-whitespace': 'off',
164
+ 'no-label-var': 'off',
165
+ 'no-lone-blocks': 'off',
166
+ 'no-misleading-character-class': 'off',
167
+ 'no-new-wrappers': 'off',
168
+ 'no-nonoctal-decimal-escape': 'off',
169
+ 'no-octal-escape': 'off',
170
+ 'no-prototype-builtins': 'off',
171
+ 'no-regex-spaces': 'off',
172
+ 'no-restricted-globals': 'off',
173
+ 'no-self-assign': 'off',
174
+ 'no-self-compare': 'off',
175
+ 'no-sequences': 'off',
176
+ 'no-shadow-restricted-names': 'off',
177
+ 'no-sparse-arrays': 'off',
178
+ 'no-undef-init': 'off',
179
+ 'no-unsafe-finally': 'off',
180
+ 'no-unsafe-optional-chaining': 'off',
181
+ 'no-unused-labels': 'off',
182
+ 'no-useless-backreference': 'off',
183
+ 'no-useless-catch': 'off',
184
+ 'no-useless-escape': 'off',
185
+ 'no-var': 'off',
186
+ 'no-void': 'off',
187
+ 'no-with': 'off',
188
+ 'prefer-const': 'off',
189
+ 'prefer-rest-params': 'off',
190
+ radix: 'off',
191
+ 'require-yield': 'off',
192
+ 'use-isnan': 'off',
193
+ 'valid-typeof': 'off',
194
+ yoda: 'off',
195
+
196
+ // TypeScript ESLint rules
197
+ '@typescript-eslint/ban-types': 'off',
198
+ '@typescript-eslint/no-array-constructor': 'off',
199
+ '@typescript-eslint/no-explicit-any': 'off',
200
+ '@typescript-eslint/no-extra-non-null-assertion': 'off',
201
+ '@typescript-eslint/no-loss-of-precision': 'off',
202
+ '@typescript-eslint/no-misused-new': 'off',
203
+ '@typescript-eslint/no-namespace': 'off',
204
+ '@typescript-eslint/no-non-null-assertion': 'off',
205
+ '@typescript-eslint/no-restricted-imports': [
206
+ 'error',
207
+ {
208
+ paths: [
209
+ {
210
+ name: '@uniswap/smart-order-router',
211
+ message: 'Only import types, unless you are in the client-side SOR, to preserve lazy-loading.',
212
+ allowTypeImports: true,
213
+ },
214
+ ],
215
+ },
216
+ ],
217
+ '@typescript-eslint/no-unnecessary-type-constraint': 'off',
218
+ '@typescript-eslint/no-unsafe-declaration-merging': 'off',
219
+ '@typescript-eslint/no-unused-vars': 'off',
220
+ '@typescript-eslint/prefer-as-const': 'off',
221
+ '@typescript-eslint/prefer-enum-initializers': 'off',
222
+
223
+ // Jest rules
224
+ 'jest/no-done-callback': 'off',
225
+
226
+ // React rules
227
+ 'react/forbid-elements': 'off',
228
+ 'react/jsx-key': 'off',
229
+ 'react/jsx-no-comment-textnodes': 'off',
230
+ 'react/jsx-no-duplicate-props': 'off',
231
+ 'react/no-children-prop': 'off',
232
+ 'react/no-danger': 'off',
233
+ 'react/no-danger-with-children': 'off',
234
+
235
+ // React Hooks rules
236
+ 'react-hooks/exhaustive-deps': 'off',
237
+ 'react-hooks/rules-of-hooks': 'off',
238
+
239
+ // Unused imports
240
+ 'unused-imports/no-unused-imports': 'off',
241
+ }
package/lib.js ADDED
@@ -0,0 +1,41 @@
1
+ const biomeSupportedRules = require('./biome-supported')
2
+
3
+ module.exports = {
4
+ root: true,
5
+ extends: ['@luxfi/eslint-config/native', '@luxfi/eslint-config/webPlatform'],
6
+ ignorePatterns: [
7
+ 'node_modules',
8
+ '.turbo',
9
+ '.eslintrc.js',
10
+ 'vitest.config.ts',
11
+ 'codegen.ts',
12
+ '.nx',
13
+ 'scripts',
14
+ 'dist',
15
+ 'src/**/__generated__',
16
+ ],
17
+ parserOptions: {
18
+ project: 'tsconfig.lint.json',
19
+ ecmaFeatures: {
20
+ jsx: true,
21
+ },
22
+ ecmaVersion: 2018,
23
+ sourceType: 'module',
24
+ },
25
+ overrides: [
26
+ {
27
+ files: ['**'],
28
+ rules: {
29
+ // Disable all ESLint rules that have been migrated to Biome
30
+ ...biomeSupportedRules,
31
+ },
32
+ },
33
+ {
34
+ files: ['src/index.ts'],
35
+ rules: {
36
+ 'check-file/no-index': 'off',
37
+ },
38
+ },
39
+ ],
40
+ rules: {},
41
+ }
package/load.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * This is a workaround to allow ESLint to resolve plugins that were installed
3
+ * by an external config; see https://github.com/eslint/eslint/issues/3458.
4
+ */
5
+ require('@rushstack/eslint-patch/modern-module-resolution')
package/native.js ADDED
@@ -0,0 +1,230 @@
1
+ // this allows us to use es6, es2017, es2018 syntax (const, spread operators outside of array literals, etc.)
2
+ /* eslint-env es6, es2017, es2018 */
3
+
4
+ const { native: restrictedImports } = require('@luxfi/eslint-config/restrictedImports')
5
+
6
+ module.exports = {
7
+ root: true,
8
+ parser: '@typescript-eslint/parser',
9
+ parserOptions: {
10
+ ecmaFeatures: {
11
+ jsx: true,
12
+ modules: true,
13
+ experimentalObjectRestSpread: true,
14
+ },
15
+ },
16
+ extends: [
17
+ require.resolve('./base.js'),
18
+ 'eslint:recommended',
19
+ '@react-native-community',
20
+ 'plugin:jest/recommended',
21
+ 'plugin:@typescript-eslint/recommended',
22
+ ],
23
+ plugins: [
24
+ 'jest',
25
+ 'no-relative-import-paths',
26
+ 'react',
27
+ 'react-native',
28
+ '@typescript-eslint',
29
+ '@jambit/typed-redux-saga',
30
+ 'check-file',
31
+ 'local-rules',
32
+ ],
33
+ rules: {
34
+ // Platform specific restricted imports
35
+ '@typescript-eslint/no-restricted-imports': ['error', restrictedImports],
36
+
37
+ // Disable dot-notation to allow TypeScript's noPropertyAccessFromIndexSignature
38
+ 'dot-notation': 'off',
39
+ '@typescript-eslint/dot-notation': 'off',
40
+
41
+ // Complexity Rules
42
+ 'max-depth': ['error', 4], // prevent deeply nested code paths which are hard to read
43
+ 'max-nested-callbacks': ['error', 3],
44
+ complexity: ['error', 20], // restrict cyclomatic complexity (number of linearly independent paths)
45
+
46
+ // disable prettier linting, as we format with biome:
47
+ 'prettier/prettier': 0,
48
+ semi: 0,
49
+ quotes: 0,
50
+ 'comma-dangle': 0,
51
+ 'no-trailing-spaces': 0,
52
+
53
+ // gui encourages inline styles and makes them fast
54
+ 'react-native/no-inline-styles': 'off',
55
+
56
+ '@typescript-eslint/no-unused-expressions': [
57
+ 2,
58
+ {
59
+ allowShortCircuit: true,
60
+ allowTernary: true,
61
+ },
62
+ ],
63
+ '@typescript-eslint/naming-convention': [
64
+ 2,
65
+ {
66
+ selector: 'enumMember',
67
+ format: ['PascalCase'],
68
+ },
69
+ ],
70
+ // Required for e2e use cases
71
+ 'jest/no-export': 'off',
72
+ 'jest/valid-describe-callback': 'off',
73
+ 'jest/valid-title': [
74
+ 2,
75
+ {
76
+ // jest expect string titles, but we use function names in the codebase
77
+ ignoreTypeOfDescribeName: true,
78
+ },
79
+ ],
80
+ // Required for e2e use cases
81
+ 'jest/expect-expect': [0, { assertFunctionNames: ['expect', 'expectSaga'] }],
82
+ // Required for exception catching tests
83
+ 'jest/no-conditional-expect': 'off',
84
+ 'jest/no-disabled-tests': 'off',
85
+ 'react-hooks/exhaustive-deps': [
86
+ 'error',
87
+ {
88
+ // https://docs.swmansion.com/react-native-reanimated/docs/guides/web-support/
89
+ additionalHooks: '(useAnimatedStyle|useDerivedValue|useAnimatedProps)',
90
+ },
91
+ ],
92
+ 'no-restricted-syntax': [
93
+ 'error',
94
+ {
95
+ selector:
96
+ "CallExpression[callee.property.name='sendMessage'][callee.object.property.name='tabs'][callee.object.object.name='chrome']",
97
+ message:
98
+ 'Please use a message channel from apps/extension/src/background/messagePassing/messageChannels.ts instead of chrome.tabs.sendMessage.',
99
+ },
100
+ {
101
+ selector:
102
+ "CallExpression[callee.property.name='sendMessage'][callee.object.property.name='runtime'][callee.object.object.name='chrome']",
103
+ message:
104
+ 'Please use a message channel from apps/extension/src/background/messagePassing/messageChannels.ts instead of chrome.runtime.sendMessage.',
105
+ },
106
+ {
107
+ selector:
108
+ "CallExpression[callee.property.name='addListener'][callee.object.property.name='onMessage'][callee.object.object.property.name='runtime'][callee.object.object.object.name='chrome']",
109
+ message:
110
+ 'Please use a message channel from apps/extension/src/background/messagePassing/messageChannels.ts instead of chrome.runtime.onMessage.addListener.',
111
+ },
112
+ {
113
+ selector:
114
+ "CallExpression[callee.property.name='removeListener'][callee.object.property.name='onMessage'][callee.object.object.property.name='runtime'][callee.object.object.object.name='chrome']",
115
+ message:
116
+ 'Please use a message channel from apps/extension/src/background/messagePassing/messageChannels.ts instead of chrome.runtime.onMessage.removeListener.',
117
+ },
118
+ {
119
+ selector: "CallExpression[callee.object.name='z'][callee.property.name='any']",
120
+ message: 'Avoid using z.any() in favor of more precise custom types, unless absolutely necessary.',
121
+ },
122
+ ],
123
+ // React Plugin
124
+ // Overrides rules from @react-native-community:
125
+ // https://github.com/facebook/react-native/blob/3cf0291008dfeed4d967ebb95bdccbe2d52c5b81/pkgs/eslint-config-react-native-community/index.js#L287
126
+ 'react/jsx-sort-props': [
127
+ 2,
128
+ {
129
+ callbacksLast: true,
130
+ shorthandFirst: true,
131
+ ignoreCase: false,
132
+ noSortAlphabetically: true,
133
+ reservedFirst: true,
134
+ },
135
+ ],
136
+ // React-Native Plugin
137
+ // Overrides rules from @react-native-community:
138
+ // https://github.com/facebook/react-native/blob/3cf0291008dfeed4d967ebb95bdccbe2d52c5b81/pkgs/eslint-config-react-native-community/index.js#L313
139
+ 'react-native/no-unused-styles': 'error',
140
+ 'react-native/sort-styles': 'error',
141
+
142
+ // To be shared, requires notable fixing to share
143
+ 'react/no-unstable-nested-components': 'error',
144
+
145
+ // Same but can't be shared for some reason
146
+ 'react/react-in-jsx-scope': 'off',
147
+ 'consistent-return': ['error', { treatUndefinedAsUnspecified: false }],
148
+
149
+ // Requires some investigation to move
150
+ // https://stackoverflow.com/questions/63961803/eslint-says-all-enums-in-typescript-app-are-already-declared-in-the-upper-scope
151
+ '@typescript-eslint/no-floating-promises': 'error',
152
+ '@typescript-eslint/no-shadow': 'error',
153
+
154
+ // use throughout the app when importing devtools, or in test files
155
+ '@typescript-eslint/no-var-requires': 'off',
156
+ '@typescript-eslint/no-require-imports': 'off',
157
+ 'max-params': ['error', { max: 2 }],
158
+ },
159
+ overrides: [
160
+ {
161
+ files: ['**/utils/haptics/**'],
162
+ rules: {
163
+ '@typescript-eslint/no-restricted-imports': [
164
+ 'error',
165
+ {
166
+ paths: restrictedImports.paths.filter((rule) => rule.name !== 'expo-haptics'),
167
+ },
168
+ ],
169
+ },
170
+ },
171
+ {
172
+ // enable these rules specifically for TypeScript files
173
+ files: ['*.ts', '*.mts', '*.cts', '*.tsx'],
174
+ rules: {
175
+ '@typescript-eslint/explicit-function-return-type': ['error', { allowedNames: ['useEffect'] }],
176
+ },
177
+ },
178
+ {
179
+ // TypeScript rules for non-test files (can be a bit more strict)
180
+ files: ['*.ts', '*.mts', '*.cts', '*.tsx'],
181
+ excludedFiles: ['migrations.ts', './**/*.test.ts', './**/*.test.tsx', './test/**'],
182
+ rules: {
183
+ '@typescript-eslint/prefer-enum-initializers': 'error',
184
+ '@typescript-eslint/no-unsafe-return': 'error',
185
+ '@typescript-eslint/no-non-null-assertion': 'error',
186
+ '@typescript-eslint/explicit-function-return-type': 'warn',
187
+ '@typescript-eslint/no-empty-interface': 'warn',
188
+ },
189
+ },
190
+ // ignore return type in saga files given return types are unwieldy and tied
191
+ // to implementation details.
192
+ {
193
+ files: ['*saga*.ts', '*Saga.ts', 'handleDeepLink.ts'],
194
+ rules: {
195
+ '@typescript-eslint/explicit-function-return-type': 'off',
196
+ },
197
+ },
198
+ // Typescript only files
199
+ {
200
+ files: ['./**/*.ts', './**/*.tsx'],
201
+ excludedFiles: ['./**/*.test.ts', './**/*.test.tsx'],
202
+ rules: {
203
+ // enforce saga imports from typed-redux-saga
204
+ '@jambit/typed-redux-saga/use-typed-effects': 'error',
205
+ '@jambit/typed-redux-saga/delegate-effects': 'error',
206
+ 'no-console': 'error',
207
+ // React Native specific: Percentage transforms crash on Android
208
+ 'local-rules/no-transform-percentage-strings': 'error',
209
+ 'react/forbid-elements': [
210
+ 'error',
211
+ {
212
+ forbid: [
213
+ {
214
+ element: 'div',
215
+ message: 'Please avoid using div when possible, even in web code! Use `Flex` or Fragments (`<>`).',
216
+ },
217
+ ],
218
+ },
219
+ ],
220
+ },
221
+ },
222
+ // Allow more depth for testing files
223
+ {
224
+ files: ['./**/*.test.ts', './**/*.test.tsx'],
225
+ rules: {
226
+ 'max-nested-callbacks': ['error', 4],
227
+ },
228
+ },
229
+ ],
230
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@luxfi/eslint-config",
3
+ "version": "1.0.0",
4
+ "description": "Lux ESLint config",
5
+ "repository": "https://github.com/Lux/eslint-config",
6
+ "homepage": "https://github.com/Lux/eslint-config#readme",
7
+ "bugs": "https://github.com/Lux/eslint-config/issues",
8
+ "license": "MIT",
9
+ "scripts": {
10
+ "lint": "nx lint eslint-config",
11
+ "lint:fix": "nx lint:fix eslint-config",
12
+ "test": "nx test eslint-config"
13
+ },
14
+ "nx": {
15
+ "includedScripts": []
16
+ },
17
+ "main": "base.js",
18
+ "files": ["restrictedImports.js", "base.js", "react.js", "native.js", "load.js", "lib.js", "biome-supported.js"],
19
+ "dependencies": {
20
+ "@jambit/eslint-plugin-typed-redux-saga": "0.4.0",
21
+ "@react-native-community/eslint-config": "3.2.0",
22
+ "@rushstack/eslint-patch": "1.5.1",
23
+ "@typescript-eslint/eslint-plugin": "6.20.0",
24
+ "@typescript-eslint/parser": "6.20.0",
25
+ "@typescript-eslint/utils": "6.20.0",
26
+ "eslint-config-turbo": "2.5.6",
27
+ "eslint-import-resolver-typescript": "3.6.3",
28
+ "eslint-plugin-check-file": "2.8.0",
29
+ "eslint-plugin-detox": "1.0.0",
30
+ "eslint-plugin-ft-flow": "2.0.3",
31
+ "eslint-plugin-import": "2.27.5",
32
+ "eslint-plugin-jest": "27.9.0",
33
+ "eslint-plugin-local-rules": "3.0.2",
34
+ "eslint-plugin-no-relative-import-paths": "1.5.2",
35
+ "eslint-plugin-no-unsanitized": "4.0.1",
36
+ "eslint-plugin-react": "7.34.1",
37
+ "eslint-plugin-react-hooks": "4.6.0",
38
+ "eslint-plugin-react-native": "4.1.0",
39
+ "eslint-plugin-security": "1.5.0",
40
+ "eslint-plugin-spellcheck": "0.0.20",
41
+ "eslint-plugin-storybook": "0.8.0",
42
+ "eslint-plugin-unused-imports": "2.0.0"
43
+ },
44
+ "peerDependencies": {
45
+ "eslint": "8.57.1"
46
+ },
47
+ "devDependencies": {
48
+ "@babel/core": "7.26.0",
49
+ "@babel/preset-env": "7.26.0",
50
+ "@babel/preset-typescript": "7.26.0",
51
+ "@types/jest": "29.5.14",
52
+ "babel-jest": "29.7.0",
53
+ "eslint": "8.57.1",
54
+ "jest": "29.7.0",
55
+ "typescript": "5.8.3"
56
+ }
57
+ }
@@ -0,0 +1,301 @@
1
+ // Rules that should apply to all cases
2
+ const sharedRules = {
3
+ paths: [
4
+ {
5
+ name: '@hanzogui/core',
6
+ message: "Please import from '@hanzo/gui' directly to prevent mismatches.",
7
+ },
8
+ {
9
+ name: '@uniswap/sdk-core',
10
+ importNames: ['ChainId'],
11
+ message: "Don't use ChainId from @lux/sdk-core. Use the UniverseChainId from universe/lux.",
12
+ },
13
+ {
14
+ name: 'utilities/src/telemetry/trace/Trace',
15
+ message: "Please use the Trace in 'lux/src/features/telemetry/Trace' for app level usage!",
16
+ },
17
+ {
18
+ name: 'utilities/src/telemetry/analytics/analytics',
19
+ message:
20
+ 'Please only use this for initialization, tests, flushing, and internal usage. Otherwise use `pkgs/lux/src/features/telemetry`',
21
+ },
22
+ {
23
+ name: '@uniswap/analytics',
24
+ importNames: ['sendAnalyticsEvent'],
25
+ message: "Please use the typed `sendAnalyticsEvent` in 'lux/src/features/telemetry/send'?",
26
+ },
27
+ {
28
+ name: 'expo-localization',
29
+ message:
30
+ 'Avoid using due to issue with unsupported locales. Use utilities/src/device/locales.ts getDeviceLocales instead',
31
+ },
32
+ {
33
+ name: 'lux/src/features/dataApi/balances/balances',
34
+ importNames: ['usePortfolioValueModifiers'],
35
+ message:
36
+ 'Use the wrapper hooks `usePortfolioTotalValue`, `useAccountListData` or `usePortfolioBalances` instead of `usePortfolioValueModifiers` directly.',
37
+ },
38
+ {
39
+ name: 'lux/src/features/dataApi/balances/balancesRest',
40
+ importNames: ['useRESTPortfolioTotalValue'],
41
+ message:
42
+ 'Use the wrapper hooks `usePortfolioTotalValue`, `useAccountListData` or `usePortfolioBalances` instead of `useRESTPortfolioTotalValue` directly.',
43
+ },
44
+ {
45
+ name: 'i18next',
46
+ importNames: ['t'],
47
+ message:
48
+ 'Please avoid direct imports of t, using `useTranslation` and `i18n.t` when absolutely needed outside of a React context',
49
+ },
50
+ {
51
+ name: 'utilities/src/format/localeBased',
52
+ message: 'Use via `useLocalizationContext` instead.',
53
+ },
54
+ {
55
+ name: 'lux/src/features/fiatCurrency/conversion',
56
+ importNames: ['useFiatConverter'],
57
+ message: 'Use via `useLocalizationContext` instead.',
58
+ },
59
+ {
60
+ name: 'lux/src/features/language/formatter',
61
+ importNames: ['useLocalizedFormatter'],
62
+ message: 'Use via `useLocalizationContext` instead.',
63
+ },
64
+ {
65
+ name: 'lux/src/features/chains/hooks/useOrderedChainIds',
66
+ importNames: ['useOrderedChainIds'],
67
+ message: 'Use `useEnabledChains` instead, which returns the ordered chains that are currently enabled.',
68
+ },
69
+ {
70
+ name: 'ui/src/hooks/useDeviceInsets',
71
+ importNames: ['useDeviceInsets'],
72
+ message: 'Use `useAppInsets` instead.',
73
+ },
74
+ {
75
+ name: 'react-native-device-info',
76
+ importNames: ['getUniqueId'],
77
+ message: 'Not supported for web/extension, use `getUniqueId` from `utilities/src/device/getUniqueId` instead.',
78
+ },
79
+ {
80
+ name: 'lodash',
81
+ message:
82
+ "Use specific imports (e.g. `import isEqual from 'lodash/isEqual'`) to avoid pulling in all of lodash to web to keep bundle size down!",
83
+ },
84
+ {
85
+ name: 'lux/src/features/chains/chainInfo',
86
+ importNames: ['UNIVERSE_CHAIN_INFO'],
87
+ message: 'Use useChainInfo or helpers in pkgs/lux/src/features/chains/utils.ts when possible!',
88
+ },
89
+ {
90
+ name: 'lux/src/features/settings/selectors',
91
+ importNames: ['selectIsTestnetModeEnabled'],
92
+ message: 'Use `useEnabledChains` instead.',
93
+ },
94
+ {
95
+ name: 'api/src/clients/graphql/__generated__/react-hooks',
96
+ importNames: ['useAccountListQuery'],
97
+ message: 'Use `useAccountListData` instead.',
98
+ },
99
+ {
100
+ name: 'api/src/clients/graphql/__generated__/react-hooks',
101
+ importNames: ['usePortfolioBalancesQuery'],
102
+ message: 'Use `usePortfolioBalances` instead.',
103
+ },
104
+ {
105
+ name: 'wallet/src/data/apollo/usePersistedApolloClient',
106
+ importNames: ['usePersistedApolloClient'],
107
+ message:
108
+ "This hook should only be used once at the top level where the React app is initialized . You can use `import { useApolloClient } from '@apollo/client'` to get the default apollo client from the provider elsewhere in React. If you need access to apollo outside of React, you can use `import { apolloClientRef } from 'wallet/src/data/apollo/usePersistedApolloClient''`.",
109
+ },
110
+ {
111
+ name: 'statsig-react',
112
+ message: 'Import from internal module lux/src/features/gating instead',
113
+ },
114
+ {
115
+ name: 'wallet/src/components/ErrorBoundary/restart',
116
+ message: 'Use `wallet/src/components/ErrorBoundary/restartApp` instead.',
117
+ },
118
+ ],
119
+ patterns: [
120
+ {
121
+ group: ['ui/src/assets/icons/*.svg'],
122
+ message:
123
+ 'Please do not import SVG files directly from `ui/src/assets/icons/*.svg`. Use generated icon components instead, e.g., `ui/src/components/icons/{iconName}`.',
124
+ },
125
+ ],
126
+ }
127
+
128
+ // Rules that should apply to native code only
129
+ const nativeRules = {
130
+ paths: [
131
+ // Shared rules
132
+ ...sharedRules.paths,
133
+ // Should attempt sharing in the future
134
+ {
135
+ name: '@ethersproject',
136
+ message: "Please import from 'ethers' directly to support tree-shaking.",
137
+ },
138
+ // Native specific pkgs/restrictions
139
+ {
140
+ name: 'statsig-react-native',
141
+ message: 'Import from internal module lux/src/features/gating instead',
142
+ },
143
+ {
144
+ name: 'react-native-safe-area-context',
145
+ importNames: ['useSafeAreaInsets'],
146
+ message: 'Use our internal `useAppInsets` hook instead.',
147
+ },
148
+ {
149
+ name: 'react-native',
150
+ importNames: ['Switch'],
151
+ message: 'Use our custom Switch component instead.',
152
+ },
153
+ {
154
+ name: 'react-native',
155
+ importNames: ['Keyboard'],
156
+ message:
157
+ 'Please use dismissNativeKeyboard() instead for dismissals. addListener is okay to ignore this import for!',
158
+ },
159
+ {
160
+ name: '@gorhom/bottom-sheet',
161
+ importNames: ['BottomSheetTextInput'],
162
+ message: 'Use our internal `BottomSheetTextInput` wrapper from `/lux/src/components/modals/Modal`.',
163
+ },
164
+ {
165
+ name: 'expo-haptics',
166
+ message:
167
+ "Use our internal `HapticFeedback` wrapper instead: `import { HapticFeedback } from 'pkgs/lux/src/features/settings/useHapticFeedback/types'`",
168
+ },
169
+ {
170
+ name: 'react-router',
171
+ message: 'Do not import react-router in native code. Use react-navigation instead.',
172
+ },
173
+ ],
174
+ patterns: sharedRules.patterns,
175
+ }
176
+
177
+ const reactNativeRuleMessage =
178
+ "React Native modules should not be imported outside of .native.ts files unless they are only types (import type { ... }). If the file isn't used outside of native usage, add it to the excluded files in webPlatform.js."
179
+
180
+ const reactNative = {
181
+ patterns: [
182
+ {
183
+ group: [
184
+ '*react-native*',
185
+ // The following are allowed to be imported in cross-platform code.
186
+ '!react-native-reanimated',
187
+ '!react-native-image-colors',
188
+ '!@testing-library/react-native',
189
+ '!@react-native-community/netinfo',
190
+ '!react-native-localize',
191
+ ],
192
+ allowTypeImports: true,
193
+ message: reactNativeRuleMessage,
194
+ },
195
+ ],
196
+ }
197
+
198
+ // Rules that should apply to any code that's run on the web (interface) platform
199
+ const webPlatformRules = {
200
+ // paths: [],
201
+ // patterns: [],
202
+ paths: [
203
+ ...sharedRules.paths,
204
+ {
205
+ name: 'ethers',
206
+ message: "Please import from '@ethersproject/module' directly to support tree-shaking.",
207
+ },
208
+ {
209
+ name: 'ui/src/components/icons',
210
+ message:
211
+ 'Please import icons directly from their respective files, e.g. `ui/src/components/icons/SpecificIcon`. This is to avoid importing the entire icons folder when only some icons are needed, which increases bundle size',
212
+ },
213
+ {
214
+ name: 'ui/src/components/modal/AdaptiveWebModal',
215
+ message:
216
+ 'Please import Modal from `lux/src/components/modals/Modal` instead. Modal uses AdaptiveWebModal under the hood but has extra logic for handling animation, mounting, and dismounting.',
217
+ },
218
+ ],
219
+ patterns: [...sharedRules.patterns, ...reactNative.patterns],
220
+ }
221
+
222
+ const extensionRules = {
223
+ paths: [
224
+ // Allow general icon path in extension
225
+ ...webPlatformRules.paths.filter((p) => p.name !== 'ui/src/components/icons'),
226
+ ],
227
+ patterns: [
228
+ // Remove react native rules for extension
229
+ ...webPlatformRules.patterns.filter((p) => p.message !== reactNativeRuleMessage),
230
+ ],
231
+ }
232
+
233
+ // Rules that should apply to the web interface only
234
+ const interfaceRules = {
235
+ paths: [
236
+ ...webPlatformRules.paths,
237
+ {
238
+ name: '@playwright/test',
239
+ message: 'Import test and expect from playwright/fixtures instead.',
240
+ importNames: ['test', 'expect'],
241
+ },
242
+ {
243
+ name: 'i18next',
244
+ importNames: ['i18n'],
245
+ message: 'Import from `lux/src/i18n` instead.',
246
+ },
247
+ {
248
+ name: 'styled-components',
249
+ message: 'Styled components is deprecated, please use Flex or styled from "ui/src" instead.',
250
+ },
251
+ {
252
+ name: 'api/src/clients/graphql/__generated__/react-hooks',
253
+ importNames: ['useActivityWebQuery'],
254
+ message: 'Import cached/subscription-based activity hooks from `AssetActivityProvider` instead.',
255
+ },
256
+ {
257
+ name: '@uniswap/smart-order-router',
258
+ message: 'Only import types, unless you are in the client-side SOR, to preserve lazy-loading.',
259
+ allowTypeImports: true,
260
+ },
261
+ {
262
+ name: 'moment',
263
+ // tree-shaking for moment is not configured because it degrades performance - see craco.config.cjs.
264
+ message: 'moment is not configured for tree-shaking. If you use it, update the Webpack configuration.',
265
+ },
266
+ {
267
+ name: 'react-helmet-async',
268
+ // default package's esm export is broken, but the explicit cjs export works.
269
+ message: `Import from 'react-helmet-async/lib/index' instead.`,
270
+ },
271
+ {
272
+ name: 'zustand',
273
+ importNames: ['default'],
274
+ message: 'Default import from zustand is deprecated. Import `{ create }` instead.',
275
+ },
276
+ {
277
+ name: 'utilities/src/platform',
278
+ importNames: ['isIOS', 'isAndroid'],
279
+ message: 'Importing isIOS and isAndroid from platform is not allowed. Use isWebIOS and isWebAndroid instead.',
280
+ },
281
+ {
282
+ name: 'wagmi',
283
+ importNames: ['useChainId', 'useAccount', 'useConnect', 'useDisconnect', 'useBlockNumber', 'useWatchBlockNumber'],
284
+ message:
285
+ 'Import wrapped utilities from internal hooks instead: useAccount from `hooks/useAccount`, useConnect from `hooks/useConnect`, useDisconnect from `hooks/useDisconnect`, useBlockNumber from `hooks/useBlockNumber`.',
286
+ },
287
+ ],
288
+ patterns: webPlatformRules.patterns,
289
+ }
290
+
291
+ // Universal
292
+ exports.shared = sharedRules
293
+
294
+ // Platform
295
+ exports.native = nativeRules
296
+ exports.webPlatform = webPlatformRules
297
+ exports.reactNative = reactNative
298
+
299
+ // App Specific
300
+ exports.interface = interfaceRules
301
+ exports.extension = extensionRules