@ashstack/lint 0.1.0 → 0.2.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 +1 -1
- package/dist/core/index.js +1 -222
- package/dist/core/rules/core/components-tsx-only.js +1 -37
- package/dist/core/rules/core/hoist-intl.js +1 -32
- package/dist/core/rules/core/index.js +1 -18
- package/dist/core/rules/core/no-comments.js +1 -194
- package/dist/core/rules/core/no-naming-convention.js +1 -143
- package/dist/core/rules/core/use-design-system.js +1 -147
- package/dist/core/rules/zod/index.js +1 -12
- package/dist/core/rules/zod/prefer-enum.js +1 -44
- package/dist/index.js +1 -3
- package/dist/lib/ast.js +1 -162
- package/dist/lib/detect.js +1 -42
- package/dist/lib/merge.js +1 -18
- package/dist/lib/module.js +1 -49
- package/dist/lib/types.js +1 -1
- package/dist/react/index.js +1 -126
- package/dist/react/rules/i18n/index.js +1 -16
- package/dist/react/rules/i18n/no-bare-attrs.js +1 -38
- package/dist/react/rules/i18n/no-bare-text.js +1 -26
- package/dist/react/rules/i18n/no-bare-toast.js +1 -38
- package/dist/react/rules/query/index.js +1 -20
- package/dist/react/rules/query/next-page-param-undefined.js +1 -26
- package/dist/react/rules/query/no-deprecated-filters.js +1 -63
- package/dist/react/rules/query/no-fetch-in-query-fn.js +1 -22
- package/dist/react/rules/query/no-inline-keys.js +1 -107
- package/dist/react/rules/query/require-destructured-hooks.js +1 -41
- package/dist/react/rules/query/shared.js +1 -10
- package/dist/react/rules/zustand/index.js +1 -12
- package/dist/react/rules/zustand/require-selector.js +1 -62
- package/dist/react-native/index.js +1 -113
- package/dist/react-native/rules/keyboard/index.js +1 -18
- package/dist/react-native/rules/legend-list/index.js +1 -51
- package/dist/react-native/rules/legend-list/no-flex-in-content-container.js +1 -77
- package/dist/react-native/rules/legend-list/no-index-key-extractor.js +1 -26
- package/dist/react-native/rules/legend-list/no-inline-data.js +1 -25
- package/dist/react-native/rules/legend-list/no-inline-extra-data.js +1 -21
- package/dist/react-native/rules/legend-list/no-inline-render-item-props.js +1 -35
- package/dist/react-native/rules/legend-list/no-mixed-children.js +1 -24
- package/dist/react-native/rules/legend-list/no-remount-key.js +1 -18
- package/dist/react-native/rules/legend-list/no-scrollview-map.js +1 -27
- package/dist/react-native/rules/legend-list/no-unsupported-props.js +1 -29
- package/dist/react-native/rules/legend-list/required-props.js +1 -23
- package/dist/react-native/rules/legend-list/shared.js +1 -25
- package/dist/react-native/rules/legend-list/typed-items-need-item-type.js +1 -49
- package/dist/react-native/rules/legend-state/index.js +1 -35
- package/dist/react-native/rules/legend-state/naming.js +1 -26
- package/dist/react-native/rules/legend-state/no-assignment.js +1 -31
- package/dist/react-native/rules/legend-state/no-nested-observable.js +1 -23
- package/dist/react-native/rules/legend-state/no-object-selector.js +1 -47
- package/dist/react-native/rules/legend-state/no-observable-in-component.js +1 -46
- package/dist/react-native/rules/legend-state/no-peek-in-selector.js +1 -67
- package/dist/react-native/rules/legend-state/no-react-mirror.js +1 -33
- package/dist/react-native/rules/legend-state/no-untracked-get-in-jsx.js +1 -52
- package/dist/react-native/rules/legend-state/shared.js +1 -23
- package/dist/react-native/rules/react-native/hoist-stateless-function.js +1 -59
- package/dist/react-native/rules/react-native/index.js +1 -50
- package/dist/react-native/rules/react-native/no-conditional-style-array.js +1 -23
- package/dist/react-native/rules/react-native/no-dynamic-import.js +1 -18
- package/dist/react-native/rules/react-native/no-keyboard-will-events.js +1 -18
- package/dist/react-native/rules/react-native/no-leaked-render.js +1 -43
- package/dist/react-native/rules/react-native/no-manual-memo.js +1 -87
- package/dist/react-native/rules/react-native/no-redundant-view-nesting.js +1 -36
- package/dist/react-native/rules/react-native/no-rn-image-network-source.js +1 -58
- package/dist/react-native/rules/react-native/no-rn-namespace-import.js +1 -38
- package/dist/react-native/rules/react-native/no-scroll-position-state.js +1 -26
- package/dist/react-native/rules/react-native/no-unlabeled-icon-pressable.js +1 -62
- package/dist/react-native/rules/reanimated/animated-reaction-safety.js +1 -110
- package/dist/react-native/rules/reanimated/animated-style-needs-animated-component.js +1 -72
- package/dist/react-native/rules/reanimated/animated-updater-purity.js +1 -38
- package/dist/react-native/rules/reanimated/gpu-properties-only.js +1 -80
- package/dist/react-native/rules/reanimated/hoist-layout-animation-builder.js +1 -43
- package/dist/react-native/rules/reanimated/index.js +1 -51
- package/dist/react-native/rules/reanimated/interpolate-needs-clamp.js +1 -41
- package/dist/react-native/rules/reanimated/no-react-state-from-continuous-worklet.js +1 -86
- package/dist/react-native/rules/reanimated/no-shared-value-dot-value.js +1 -112
- package/dist/react-native/rules/reanimated/prefer-lazy-shared-value-initializer.js +1 -24
- package/dist/react-native/rules/reanimated/schedule-on-rn-scope.js +1 -21
- package/dist/react-native/rules/reanimated/shared-value-usage.js +1 -105
- package/dist/react-native/rules/reanimated/shared.js +1 -13
- package/dist/react-native/rules/skia/canvas-opaque.js +1 -49
- package/dist/react-native/rules/skia/index.js +1 -21
- package/dist/react-native/rules/turbo-image/index.js +1 -14
- package/dist/react-native/rules/turbo-image/require-cache-policy.js +1 -21
- package/dist/react-native/rules/turbo-image/require-resize.js +1 -22
- package/dist/react-native/rules/unistyles/animated-theme.js +1 -37
- package/dist/react-native/rules/unistyles/content-container.js +1 -73
- package/dist/react-native/rules/unistyles/in-sheet.js +1 -116
- package/dist/react-native/rules/unistyles/index.js +1 -58
- package/dist/react-native/rules/unistyles/insets.js +1 -92
- package/dist/react-native/rules/unistyles/no-hardcoded-color.js +1 -30
- package/dist/react-native/rules/unistyles/no-hardcoded-spacing.js +1 -78
- package/dist/react-native/rules/unistyles/no-margin.js +1 -19
- package/dist/react-native/rules/unistyles/no-style-spread.js +1 -41
- package/dist/react-native/rules/unistyles/no-unused-styles.js +1 -99
- package/dist/react-native/rules/unistyles/rtl-style-call.js +1 -42
- package/dist/react-native/rules/unistyles/shared.js +1 -70
- package/dist/react-native/rules/unistyles/theme-screen-component.js +1 -22
- package/dist/react-native/rules/unistyles/theme-style-attr.js +1 -18
- package/dist/react-native/stylesheet.js +1 -44
- package/package.json +3 -4
- package/RULES.md +0 -2881
package/RULES.md
DELETED
|
@@ -1,2881 +0,0 @@
|
|
|
1
|
-
<!-- GENERATED by scripts/generate-rule-docs.ts - do not edit by hand. `bun run docs:rules` regenerates it. -->
|
|
2
|
-
|
|
3
|
-
# @ashstack/lint rules
|
|
4
|
-
|
|
5
|
-
Find a rule by the id in its diagnostic, e.g. `@ashstack/unistyles/no-margin`. Each one lists what it enforces, its options, and a failing and a passing example. The diagnostic itself names the fix.
|
|
6
|
-
|
|
7
|
-
`core()`, `react()` and `react-native()` each contain the one before, so a section's built-in table lists only the settings that entry changes. Built-in rules link to their upstream page.
|
|
8
|
-
|
|
9
|
-
Turn any rule off by id in your own `rules` block: `"@ashstack/unistyles/no-margin": "off"`.
|
|
10
|
-
|
|
11
|
-
- [`core()`](#core)
|
|
12
|
-
- [`@ashstack/core`](#ashstackcore) — 5 rules
|
|
13
|
-
- [`@ashstack/zod`](#ashstackzod) — 1 rule
|
|
14
|
-
- [`react()`](#react)
|
|
15
|
-
- [`@ashstack/query`](#ashstackquery) — 5 rules
|
|
16
|
-
- [`@ashstack/zustand`](#ashstackzustand) — 1 rule
|
|
17
|
-
- [`@ashstack/i18n`](#ashstacki18n) — 3 rules
|
|
18
|
-
- [`react-native()`](#react-native)
|
|
19
|
-
- [`@ashstack/react-native`](#ashstackreact-native) — 11 rules
|
|
20
|
-
- [`@ashstack/unistyles`](#ashstackunistyles) — 12 rules
|
|
21
|
-
- [`@ashstack/legend-list`](#ashstacklegend-list) — 11 rules
|
|
22
|
-
- [`@ashstack/legend-state`](#ashstacklegend-state) — 8 rules
|
|
23
|
-
- [`@ashstack/reanimated`](#ashstackreanimated) — 11 rules
|
|
24
|
-
- [`@ashstack/turbo-image`](#ashstackturbo-image) — 2 rules
|
|
25
|
-
- [`@ashstack/skia`](#ashstackskia) — 1 rule
|
|
26
|
-
- [`@ashstack/keyboard`](#ashstackkeyboard) — import bans only
|
|
27
|
-
|
|
28
|
-
## `core()`
|
|
29
|
-
|
|
30
|
-
Any TypeScript project — backend, CLI, library.
|
|
31
|
-
|
|
32
|
-
Plugins: `eslint`, `typescript`, `import`, `unicorn`, `promise`, `oxc`.
|
|
33
|
-
|
|
34
|
-
### Built-in rules
|
|
35
|
-
|
|
36
|
-
| Rule | Setting |
|
|
37
|
-
| --- | --- |
|
|
38
|
-
| [`array-callback-return`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/array-callback-return.html) | `"error"` |
|
|
39
|
-
| [`constructor-super`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/constructor-super.html) | `"error"` |
|
|
40
|
-
| [`default-param-last`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/default-param-last.html) | `"error"` |
|
|
41
|
-
| [`eqeqeq`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/eqeqeq.html) | `["error","always",{"null":"ignore"}]` |
|
|
42
|
-
| [`for-direction`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/for-direction.html) | `"error"` |
|
|
43
|
-
| [`getter-return`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/getter-return.html) | `"error"` |
|
|
44
|
-
| [`func-style`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/func-style.html) | `["error","expression"]` |
|
|
45
|
-
| [`no-array-constructor`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-array-constructor.html) | `"error"` |
|
|
46
|
-
| [`no-async-promise-executor`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-async-promise-executor.html) | `"error"` |
|
|
47
|
-
| [`no-case-declarations`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-case-declarations.html) | `"error"` |
|
|
48
|
-
| [`no-class-assign`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-class-assign.html) | `"error"` |
|
|
49
|
-
| [`no-compare-neg-zero`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-compare-neg-zero.html) | `"error"` |
|
|
50
|
-
| [`no-cond-assign`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-cond-assign.html) | `"error"` |
|
|
51
|
-
| [`no-const-assign`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-const-assign.html) | `"error"` |
|
|
52
|
-
| [`no-constant-condition`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-constant-condition.html) | `"error"` |
|
|
53
|
-
| [`no-control-regex`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-control-regex.html) | `"error"` |
|
|
54
|
-
| [`no-debugger`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger.html) | `"error"` |
|
|
55
|
-
| [`no-dupe-class-members`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-dupe-class-members.html) | `"error"` |
|
|
56
|
-
| [`no-dupe-else-if`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-dupe-else-if.html) | `"error"` |
|
|
57
|
-
| [`no-dupe-keys`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-dupe-keys.html) | `"error"` |
|
|
58
|
-
| [`no-duplicate-case`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-duplicate-case.html) | `"error"` |
|
|
59
|
-
| [`no-empty`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-empty.html) | `"error"` |
|
|
60
|
-
| [`no-empty-character-class`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-empty-character-class.html) | `"error"` |
|
|
61
|
-
| [`no-empty-pattern`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-empty-pattern.html) | `"error"` |
|
|
62
|
-
| [`no-ex-assign`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-ex-assign.html) | `"error"` |
|
|
63
|
-
| [`no-extra-boolean-cast`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-extra-boolean-cast.html) | `"error"` |
|
|
64
|
-
| [`no-fallthrough`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-fallthrough.html) | `"error"` |
|
|
65
|
-
| [`no-func-assign`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-func-assign.html) | `"error"` |
|
|
66
|
-
| [`no-global-assign`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-global-assign.html) | `"error"` |
|
|
67
|
-
| [`no-import-assign`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-import-assign.html) | `"error"` |
|
|
68
|
-
| [`no-irregular-whitespace`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-irregular-whitespace.html) | `"error"` |
|
|
69
|
-
| [`no-loss-of-precision`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-loss-of-precision.html) | `"error"` |
|
|
70
|
-
| [`no-misleading-character-class`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-misleading-character-class.html) | `"error"` |
|
|
71
|
-
| [`no-negated-condition`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-negated-condition.html) | `"error"` |
|
|
72
|
-
| [`no-nested-ternary`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-nested-ternary.html) | `"error"` |
|
|
73
|
-
| [`no-new-native-nonconstructor`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-new-native-nonconstructor.html) | `"error"` |
|
|
74
|
-
| [`no-nonoctal-decimal-escape`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-nonoctal-decimal-escape.html) | `"error"` |
|
|
75
|
-
| [`no-obj-calls`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-obj-calls.html) | `"error"` |
|
|
76
|
-
| [`no-param-reassign`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-param-reassign.html) | `"error"` |
|
|
77
|
-
| [`no-prototype-builtins`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-prototype-builtins.html) | `"error"` |
|
|
78
|
-
| [`no-redeclare`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-redeclare.html) | `"off"` |
|
|
79
|
-
| [`no-regex-spaces`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-regex-spaces.html) | `"error"` |
|
|
80
|
-
| [`no-self-assign`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-self-assign.html) | `"error"` |
|
|
81
|
-
| [`no-setter-return`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-setter-return.html) | `"error"` |
|
|
82
|
-
| [`no-shadow-restricted-names`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-shadow-restricted-names.html) | `"error"` |
|
|
83
|
-
| [`no-sparse-arrays`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-sparse-arrays.html) | `"error"` |
|
|
84
|
-
| [`no-this-before-super`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-this-before-super.html) | `"error"` |
|
|
85
|
-
| [`no-undef`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-undef.html) | `"off"` |
|
|
86
|
-
| [`no-unreachable`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unreachable.html) | `"error"` |
|
|
87
|
-
| [`no-unsafe-finally`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unsafe-finally.html) | `"error"` |
|
|
88
|
-
| [`no-unsafe-negation`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unsafe-negation.html) | `"error"` |
|
|
89
|
-
| [`no-unsafe-optional-chaining`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unsafe-optional-chaining.html) | `"error"` |
|
|
90
|
-
| [`no-unused-labels`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unused-labels.html) | `"error"` |
|
|
91
|
-
| [`no-unused-private-class-members`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unused-private-class-members.html) | `"error"` |
|
|
92
|
-
| [`no-useless-catch`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-useless-catch.html) | `"error"` |
|
|
93
|
-
| [`no-useless-constructor`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-useless-constructor.html) | `"error"` |
|
|
94
|
-
| [`no-useless-escape`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-useless-escape.html) | `"error"` |
|
|
95
|
-
| [`no-var`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-var.html) | `"error"` |
|
|
96
|
-
| [`no-with`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-with.html) | `"error"` |
|
|
97
|
-
| [`object-shorthand`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/object-shorthand.html) | `["error","methods"]` |
|
|
98
|
-
| [`prefer-rest-params`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/prefer-rest-params.html) | `"error"` |
|
|
99
|
-
| [`require-yield`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/require-yield.html) | `"error"` |
|
|
100
|
-
| [`use-isnan`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/use-isnan.html) | `"error"` |
|
|
101
|
-
| [`valid-typeof`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/valid-typeof.html) | `"error"` |
|
|
102
|
-
| [`no-unused-vars`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unused-vars.html) | `["error",{"args":"after-used","argsIgnorePattern":"^_","caughtErrors":"all","caughtErrorsIgnorePattern":"^_","varsIgnorePattern":"^_","destructuredArrayIgnorePattern":"^_","ignoreRestSiblings":true,"fix":{"imports":"safe-fix"}}]` |
|
|
103
|
-
| [`no-use-before-define`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-use-before-define.html) | `"off"` |
|
|
104
|
-
| [`complexity`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/complexity.html) | `["error",{"max":12}]` |
|
|
105
|
-
| [`max-depth`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/max-depth.html) | `["error",3]` |
|
|
106
|
-
| [`max-params`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/max-params.html) | `["error",4]` |
|
|
107
|
-
| [`max-lines`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/max-lines.html) | `["error",{"max":300,"skipBlankLines":true,"skipComments":true}]` |
|
|
108
|
-
| [`max-lines-per-function`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/max-lines-per-function.html) | `["error",{"max":250,"skipBlankLines":true,"skipComments":true}]` |
|
|
109
|
-
| [`eslint/no-await-in-loop`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-await-in-loop.html) | `"error"` |
|
|
110
|
-
| [`eslint/no-console`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-console.html) | `"off"` |
|
|
111
|
-
| [`eslint/no-constant-binary-expression`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-constant-binary-expression.html) | `"off"` |
|
|
112
|
-
| [`eslint/no-control-regex`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-control-regex.html) | `"error"` |
|
|
113
|
-
| [`eslint/no-else-return`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-else-return.html) | `"error"` |
|
|
114
|
-
| [`eslint/no-empty`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-empty.html) | `"error"` |
|
|
115
|
-
| [`eslint/no-empty-function`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-empty-function.html) | `"error"` |
|
|
116
|
-
| [`eslint/no-new`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-new.html) | `"off"` |
|
|
117
|
-
| [`eslint/no-self-compare`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-self-compare.html) | `"error"` |
|
|
118
|
-
| [`eslint/no-shadow`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-shadow.html) | `"error"` |
|
|
119
|
-
| [`eslint/no-template-curly-in-string`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-template-curly-in-string.html) | `"error"` |
|
|
120
|
-
| [`eslint/no-unassigned-vars`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unassigned-vars.html) | `"off"` |
|
|
121
|
-
| [`eslint/no-underscore-dangle`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-underscore-dangle.html) | `"off"` |
|
|
122
|
-
| [`eslint/no-unmodified-loop-condition`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unmodified-loop-condition.html) | `"off"` |
|
|
123
|
-
| [`eslint/no-unused-expressions`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unused-expressions.html) | `"off"` |
|
|
124
|
-
| [`eslint/no-useless-concat`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-useless-concat.html) | `"off"` |
|
|
125
|
-
| [`eslint/preserve-caught-error`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/preserve-caught-error.html) | `"off"` |
|
|
126
|
-
| [`eslint/prefer-arrow-callback`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/prefer-arrow-callback.html) | `"error"` |
|
|
127
|
-
| [`eslint/prefer-const`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/prefer-const.html) | `"error"` |
|
|
128
|
-
| [`eslint/prefer-template`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/prefer-template.html) | `"error"` |
|
|
129
|
-
| [`typescript/consistent-type-imports`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/consistent-type-imports.html) | `"error"` |
|
|
130
|
-
| [`typescript/dot-notation`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/dot-notation.html) | `"error"` |
|
|
131
|
-
| [`typescript/no-confusing-void-expression`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-confusing-void-expression.html) | `"error"` |
|
|
132
|
-
| [`typescript/no-empty-object-type`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-empty-object-type.html) | `["error",{"allowInterfaces":"with-single-extends"}]` |
|
|
133
|
-
| [`typescript/no-explicit-any`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-explicit-any.html) | `"error"` |
|
|
134
|
-
| [`typescript/no-extra-non-null-assertion`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-extra-non-null-assertion.html) | `"error"` |
|
|
135
|
-
| [`typescript/no-extraneous-class`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-extraneous-class.html) | `"error"` |
|
|
136
|
-
| [`typescript/no-floating-promises`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-floating-promises.html) | `"error"` |
|
|
137
|
-
| [`typescript/no-import-type-side-effects`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-import-type-side-effects.html) | `"error"` |
|
|
138
|
-
| [`typescript/no-misused-new`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-misused-new.html) | `"error"` |
|
|
139
|
-
| [`typescript/no-misused-promises`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-misused-promises.html) | `["error",{"checksVoidReturn":false}]` |
|
|
140
|
-
| [`typescript/no-namespace`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-namespace.html) | `"error"` |
|
|
141
|
-
| [`typescript/no-non-null-asserted-optional-chain`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-non-null-asserted-optional-chain.html) | `"error"` |
|
|
142
|
-
| [`typescript/no-non-null-assertion`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-non-null-assertion.html) | `"error"` |
|
|
143
|
-
| [`typescript/no-unnecessary-boolean-literal-compare`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-unnecessary-boolean-literal-compare.html) | `"error"` |
|
|
144
|
-
| [`typescript/no-unnecessary-condition`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-unnecessary-condition.html) | `"error"` |
|
|
145
|
-
| [`typescript/no-unnecessary-type-assertion`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-unnecessary-type-assertion.html) | `"error"` |
|
|
146
|
-
| [`typescript/no-unnecessary-type-constraint`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-unnecessary-type-constraint.html) | `"error"` |
|
|
147
|
-
| [`typescript/no-unsafe-declaration-merging`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-unsafe-declaration-merging.html) | `"error"` |
|
|
148
|
-
| [`typescript/no-unsafe-function-type`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-unsafe-function-type.html) | `"error"` |
|
|
149
|
-
| [`typescript/no-unsafe-type-assertion`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-unsafe-type-assertion.html) | `"error"` |
|
|
150
|
-
| [`typescript/no-wrapper-object-types`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-wrapper-object-types.html) | `"error"` |
|
|
151
|
-
| [`typescript/prefer-as-const`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/prefer-as-const.html) | `"error"` |
|
|
152
|
-
| [`typescript/prefer-namespace-keyword`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/prefer-namespace-keyword.html) | `"error"` |
|
|
153
|
-
| [`typescript/prefer-nullish-coalescing`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/prefer-nullish-coalescing.html) | `"error"` |
|
|
154
|
-
| [`typescript/require-await`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/require-await.html) | `"error"` |
|
|
155
|
-
| [`typescript/switch-exhaustiveness-check`](https://oxc.rs/docs/guide/usage/linter/rules/typescript/switch-exhaustiveness-check.html) | `["error",{"considerDefaultExhaustiveForUnions":true}]` |
|
|
156
|
-
| [`import/no-commonjs`](https://oxc.rs/docs/guide/usage/linter/rules/import/no-commonjs.html) | `"off"` |
|
|
157
|
-
| [`import/no-cycle`](https://oxc.rs/docs/guide/usage/linter/rules/import/no-cycle.html) | `"error"` |
|
|
158
|
-
| [`import/no-duplicates`](https://oxc.rs/docs/guide/usage/linter/rules/import/no-duplicates.html) | `"error"` |
|
|
159
|
-
| [`import/no-named-as-default`](https://oxc.rs/docs/guide/usage/linter/rules/import/no-named-as-default.html) | `"off"` |
|
|
160
|
-
| [`import/no-named-as-default-member`](https://oxc.rs/docs/guide/usage/linter/rules/import/no-named-as-default-member.html) | `"off"` |
|
|
161
|
-
| [`import/no-self-import`](https://oxc.rs/docs/guide/usage/linter/rules/import/no-self-import.html) | `"off"` |
|
|
162
|
-
| [`import/no-unassigned-import`](https://oxc.rs/docs/guide/usage/linter/rules/import/no-unassigned-import.html) | `"off"` |
|
|
163
|
-
| [`unicorn/consistent-function-scoping`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/consistent-function-scoping.html) | `"off"` |
|
|
164
|
-
| [`unicorn/no-array-reverse`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/no-array-reverse.html) | `"off"` |
|
|
165
|
-
| [`unicorn/no-array-sort`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/no-array-sort.html) | `"off"` |
|
|
166
|
-
| [`unicorn/no-empty-file`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/no-empty-file.html) | `"off"` |
|
|
167
|
-
| [`unicorn/no-new-array`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/no-new-array.html) | `"off"` |
|
|
168
|
-
| [`unicorn/no-useless-fallback-in-spread`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/no-useless-fallback-in-spread.html) | `"off"` |
|
|
169
|
-
| [`unicorn/no-useless-spread`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/no-useless-spread.html) | `"off"` |
|
|
170
|
-
| [`unicorn/prefer-add-event-listener`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/prefer-add-event-listener.html) | `"off"` |
|
|
171
|
-
| [`unicorn/prefer-array-flat-map`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/prefer-array-flat-map.html) | `"error"` |
|
|
172
|
-
| [`unicorn/prefer-date-now`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/prefer-date-now.html) | `"error"` |
|
|
173
|
-
| [`unicorn/prefer-logical-operator-over-ternary`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/prefer-logical-operator-over-ternary.html) | `"error"` |
|
|
174
|
-
| [`unicorn/prefer-node-protocol`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/prefer-node-protocol.html) | `"error"` |
|
|
175
|
-
| [`unicorn/prefer-optional-catch-binding`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/prefer-optional-catch-binding.html) | `"error"` |
|
|
176
|
-
| [`unicorn/require-module-specifiers`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/require-module-specifiers.html) | `"off"` |
|
|
177
|
-
| [`unicorn/throw-new-error`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/throw-new-error.html) | `"error"` |
|
|
178
|
-
| [`promise/always-return`](https://oxc.rs/docs/guide/usage/linter/rules/promise/always-return.html) | `"off"` |
|
|
179
|
-
| [`promise/no-callback-in-promise`](https://oxc.rs/docs/guide/usage/linter/rules/promise/no-callback-in-promise.html) | `"off"` |
|
|
180
|
-
| [`promise/no-multiple-resolved`](https://oxc.rs/docs/guide/usage/linter/rules/promise/no-multiple-resolved.html) | `"off"` |
|
|
181
|
-
| [`promise/no-promise-in-callback`](https://oxc.rs/docs/guide/usage/linter/rules/promise/no-promise-in-callback.html) | `"off"` |
|
|
182
|
-
| [`oxc/no-async-endpoint-handlers`](https://oxc.rs/docs/guide/usage/linter/rules/oxc/no-async-endpoint-handlers.html) | `"off"` |
|
|
183
|
-
|
|
184
|
-
### `@ashstack/core`
|
|
185
|
-
|
|
186
|
-
_always on via `core()` and every entry above it (opt-in rules noted per rule)._
|
|
187
|
-
|
|
188
|
-
#### `@ashstack/core/no-comments`
|
|
189
|
-
|
|
190
|
-
Disallow every comment that is neither a `// what: <fact>` line nor a tooling directive. The message names the refactoring that removes it: Rename, Extract Function, Guard Clause. Surviving `// what:` lines are held to one short line each, at most `budget` per file (default 2); `escapeHatch: false` removes the hatch entirely, so no prose survives at all. With `jsdoc: "allow"`, a `/** */` block documenting the declaration directly beneath it is kept, while a floating one still reports.
|
|
191
|
-
|
|
192
|
-
> Off by default — opt in per project.
|
|
193
|
-
|
|
194
|
-
**Options**
|
|
195
|
-
|
|
196
|
-
```jsonc
|
|
197
|
-
[
|
|
198
|
-
{
|
|
199
|
-
"type": "object",
|
|
200
|
-
"properties": {
|
|
201
|
-
"jsdoc": {
|
|
202
|
-
"enum": [
|
|
203
|
-
"allow",
|
|
204
|
-
"report"
|
|
205
|
-
]
|
|
206
|
-
},
|
|
207
|
-
"escapeHatch": {
|
|
208
|
-
"type": "boolean"
|
|
209
|
-
},
|
|
210
|
-
"budget": {
|
|
211
|
-
"type": "integer",
|
|
212
|
-
"minimum": 0
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
"additionalProperties": false
|
|
216
|
-
}
|
|
217
|
-
]
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
**Fails**
|
|
221
|
-
|
|
222
|
-
```tsx
|
|
223
|
-
import { View } from "react-native";
|
|
224
|
-
|
|
225
|
-
// this panel is the one the settings screen renders
|
|
226
|
-
export const Panel = () => {
|
|
227
|
-
/** the flag that decides whether the footer shows */
|
|
228
|
-
const flag = true;
|
|
229
|
-
// what: short
|
|
230
|
-
// what: iOS 17 reports the wrong inset for the first layout pass here.
|
|
231
|
-
// what: Android fires this layout pass twice below API 31 on Samsung devices.
|
|
232
|
-
// what: The upstream contract sends the cursor as a string, never a number.
|
|
233
|
-
return <View>{flag}</View>;
|
|
234
|
-
};
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
**Passes**
|
|
238
|
-
|
|
239
|
-
```tsx
|
|
240
|
-
import { View } from "react-native";
|
|
241
|
-
|
|
242
|
-
/** Renders the settings screen's panel. */
|
|
243
|
-
export const SettingsPanel = () => {
|
|
244
|
-
// what: Android fires this layout pass twice below API 31.
|
|
245
|
-
const showsFooter = true;
|
|
246
|
-
|
|
247
|
-
// what: The upstream contract sends the cursor as a string, never a number.
|
|
248
|
-
return <View>{showsFooter}</View>;
|
|
249
|
-
};
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
#### `@ashstack/core/no-naming-convention`
|
|
253
|
-
|
|
254
|
-
Require a variable, object property, type member or enum member to use one of the casings allowed for its kind.
|
|
255
|
-
|
|
256
|
-
**Fails**
|
|
257
|
-
|
|
258
|
-
```tsx
|
|
259
|
-
import { View } from "react-native";
|
|
260
|
-
|
|
261
|
-
export const bad_Name = 1;
|
|
262
|
-
|
|
263
|
-
export const styles = {
|
|
264
|
-
mixed_Snake_Case: 1,
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
export const Panel = () => <View />;
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
**Passes**
|
|
271
|
-
|
|
272
|
-
```tsx
|
|
273
|
-
import { View } from "react-native";
|
|
274
|
-
|
|
275
|
-
export const camelVariable = 1;
|
|
276
|
-
export const CONSTANT_VARIABLE = 2;
|
|
277
|
-
export const unstable_settings = { initialRouteName: "index" };
|
|
278
|
-
export const settings$ = { theme$: "dark" };
|
|
279
|
-
|
|
280
|
-
export const styles = {
|
|
281
|
-
camelCase: 1,
|
|
282
|
-
snake_case: 2,
|
|
283
|
-
CONSTANT_CASE: 3,
|
|
284
|
-
PascalCase: 4,
|
|
285
|
-
experimental_backgroundImage: 5,
|
|
286
|
-
unstable_conditionNames: 6,
|
|
287
|
-
enableFullScreenImage_legacy: 7,
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
export const Panel = () => <View />;
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
#### `@ashstack/core/use-design-system`
|
|
294
|
-
|
|
295
|
-
Disallow importing a raw primitive your design system already wraps. Wrapped components come from scanning the design-system directory, plus the explicit `use` map for names, paths and source modules the scan cannot infer. Files under the design-system directory (or `exempt`) are skipped.
|
|
296
|
-
|
|
297
|
-
> Off by default — opt in per project.
|
|
298
|
-
|
|
299
|
-
**Options**
|
|
300
|
-
|
|
301
|
-
```jsonc
|
|
302
|
-
[
|
|
303
|
-
{
|
|
304
|
-
"type": "object",
|
|
305
|
-
"properties": {
|
|
306
|
-
"dir": {
|
|
307
|
-
"type": "string",
|
|
308
|
-
"minLength": 1
|
|
309
|
-
},
|
|
310
|
-
"alias": {
|
|
311
|
-
"type": "string",
|
|
312
|
-
"minLength": 1
|
|
313
|
-
},
|
|
314
|
-
"use": {
|
|
315
|
-
"type": "object",
|
|
316
|
-
"additionalProperties": {
|
|
317
|
-
"anyOf": [
|
|
318
|
-
{
|
|
319
|
-
"type": "string"
|
|
320
|
-
},
|
|
321
|
-
{
|
|
322
|
-
"type": "array",
|
|
323
|
-
"items": {
|
|
324
|
-
"type": "string"
|
|
325
|
-
}
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
"type": "object",
|
|
329
|
-
"properties": {
|
|
330
|
-
"replaces": {
|
|
331
|
-
"anyOf": [
|
|
332
|
-
{
|
|
333
|
-
"type": "string"
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
"type": "array",
|
|
337
|
-
"items": {
|
|
338
|
-
"type": "string"
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
]
|
|
342
|
-
},
|
|
343
|
-
"from": {
|
|
344
|
-
"type": "string"
|
|
345
|
-
},
|
|
346
|
-
"path": {
|
|
347
|
-
"type": "string"
|
|
348
|
-
},
|
|
349
|
-
"reason": {
|
|
350
|
-
"type": "string"
|
|
351
|
-
}
|
|
352
|
-
},
|
|
353
|
-
"required": [
|
|
354
|
-
"replaces"
|
|
355
|
-
],
|
|
356
|
-
"additionalProperties": false
|
|
357
|
-
}
|
|
358
|
-
]
|
|
359
|
-
}
|
|
360
|
-
},
|
|
361
|
-
"exempt": {
|
|
362
|
-
"type": "array",
|
|
363
|
-
"items": {
|
|
364
|
-
"type": "string"
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
},
|
|
368
|
-
"additionalProperties": false
|
|
369
|
-
}
|
|
370
|
-
]
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
**Fails**
|
|
374
|
-
|
|
375
|
-
```tsx
|
|
376
|
-
import { Text, View } from "react-native";
|
|
377
|
-
|
|
378
|
-
export const Panel = () => (
|
|
379
|
-
<View>
|
|
380
|
-
<Text>{"hello"}</Text>
|
|
381
|
-
</View>
|
|
382
|
-
);
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
**Passes**
|
|
386
|
-
|
|
387
|
-
```tsx
|
|
388
|
-
import { View } from "react-native";
|
|
389
|
-
|
|
390
|
-
import { Text } from "@/components/ui/text";
|
|
391
|
-
|
|
392
|
-
export const Panel = () => (
|
|
393
|
-
<View>
|
|
394
|
-
<Text>{"hello"}</Text>
|
|
395
|
-
</View>
|
|
396
|
-
);
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
#### `@ashstack/core/components-tsx-only`
|
|
400
|
-
|
|
401
|
-
Require every file under the components directory to render JSX or be a re-export barrel. `dir` says which directory, defaulting to `src/components`.
|
|
402
|
-
|
|
403
|
-
> Off by default — opt in per project.
|
|
404
|
-
|
|
405
|
-
**Options**
|
|
406
|
-
|
|
407
|
-
```jsonc
|
|
408
|
-
[
|
|
409
|
-
{
|
|
410
|
-
"type": "object",
|
|
411
|
-
"properties": {
|
|
412
|
-
"dir": {
|
|
413
|
-
"type": "string",
|
|
414
|
-
"minLength": 1
|
|
415
|
-
}
|
|
416
|
-
},
|
|
417
|
-
"additionalProperties": false
|
|
418
|
-
}
|
|
419
|
-
]
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
**Fails**
|
|
423
|
-
|
|
424
|
-
```tsx
|
|
425
|
-
export const formatFullName = (first: string, last: string) => `${first} ${last}`;
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
**Passes**
|
|
429
|
-
|
|
430
|
-
```tsx
|
|
431
|
-
import { View } from "react-native";
|
|
432
|
-
|
|
433
|
-
export const Panel = () => <View />;
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
#### `@ashstack/core/hoist-intl`
|
|
437
|
-
|
|
438
|
-
Disallow `new Intl.*` inside a function that renders JSX, unless the call already sits in `useMemo` or `useCallback`.
|
|
439
|
-
|
|
440
|
-
**Fails**
|
|
441
|
-
|
|
442
|
-
```tsx
|
|
443
|
-
import { View } from "react-native";
|
|
444
|
-
|
|
445
|
-
export const Price = () => {
|
|
446
|
-
const formatter = new Intl.NumberFormat("en-US");
|
|
447
|
-
return <View accessibilityValue={{ text: formatter.format(10) }} />;
|
|
448
|
-
};
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
**Passes**
|
|
452
|
-
|
|
453
|
-
```tsx
|
|
454
|
-
import { View } from "react-native";
|
|
455
|
-
|
|
456
|
-
const formatter = new Intl.NumberFormat("en-US");
|
|
457
|
-
|
|
458
|
-
export const Price = () => <View accessibilityValue={{ text: formatter.format(10) }} />;
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
### `@ashstack/zod`
|
|
462
|
-
|
|
463
|
-
_auto-enabled when `zod` is a dependency._
|
|
464
|
-
|
|
465
|
-
#### `@ashstack/zod/prefer-enum`
|
|
466
|
-
|
|
467
|
-
Disallow `z.nativeEnum()` and any `z.union()` whose members are all `z.literal()` strings.
|
|
468
|
-
|
|
469
|
-
**Fails**
|
|
470
|
-
|
|
471
|
-
```tsx
|
|
472
|
-
import { View } from "react-native";
|
|
473
|
-
import { z } from "zod";
|
|
474
|
-
|
|
475
|
-
enum AlertType {
|
|
476
|
-
LowMoisture = "LOW_MOISTURE",
|
|
477
|
-
MissedDelivery = "MISSED_DELIVERY",
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
export const alertTypeSchema = z.nativeEnum(AlertType);
|
|
481
|
-
|
|
482
|
-
export const statusSchema = z.union([z.literal("draft"), z.literal("published")]);
|
|
483
|
-
|
|
484
|
-
export const Panel = () => <View />;
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
**Passes**
|
|
488
|
-
|
|
489
|
-
```tsx
|
|
490
|
-
import { View } from "react-native";
|
|
491
|
-
import { z } from "zod";
|
|
492
|
-
|
|
493
|
-
enum AlertType {
|
|
494
|
-
LowMoisture = "LOW_MOISTURE",
|
|
495
|
-
MissedDelivery = "MISSED_DELIVERY",
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
export const alertTypeSchema = z.enum(AlertType);
|
|
499
|
-
|
|
500
|
-
export const statusSchema = z.enum(["draft", "published"]);
|
|
501
|
-
|
|
502
|
-
export const mixedUnion = z.union([z.literal("manual"), z.number()]);
|
|
503
|
-
|
|
504
|
-
export const Panel = () => <View />;
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
## `react()`
|
|
508
|
-
|
|
509
|
-
React on the web. Adds the you-might-not-need-an-effect plugin (`react-effect/`) alongside oxlint's own.
|
|
510
|
-
|
|
511
|
-
Plugins: `eslint`, `typescript`, `import`, `unicorn`, `promise`, `oxc`, `react`, `jsx-a11y`.
|
|
512
|
-
|
|
513
|
-
### Built-in rules
|
|
514
|
-
|
|
515
|
-
| Rule | Setting |
|
|
516
|
-
| --- | --- |
|
|
517
|
-
| [`max-lines-per-function`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/max-lines-per-function.html) | `["error",{"max":120,"skipBlankLines":true,"skipComments":true}]` |
|
|
518
|
-
| [`eslint/no-empty`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-empty.html) | `"off"` |
|
|
519
|
-
| [`eslint/no-empty-function`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-empty-function.html) | `"off"` |
|
|
520
|
-
| [`react/react-in-jsx-scope`](https://oxc.rs/docs/guide/usage/linter/rules/react/react-in-jsx-scope.html) | `"off"` |
|
|
521
|
-
| [`react/style-prop-object`](https://oxc.rs/docs/guide/usage/linter/rules/react/style-prop-object.html) | `"off"` |
|
|
522
|
-
| [`react/only-export-components`](https://oxc.rs/docs/guide/usage/linter/rules/react/only-export-components.html) | `"off"` |
|
|
523
|
-
| [`react/rules-of-hooks`](https://oxc.rs/docs/guide/usage/linter/rules/react/rules-of-hooks.html) | `"error"` |
|
|
524
|
-
| [`react/exhaustive-deps`](https://oxc.rs/docs/guide/usage/linter/rules/react/exhaustive-deps.html) | `"error"` |
|
|
525
|
-
| [`react/button-has-type`](https://oxc.rs/docs/guide/usage/linter/rules/react/button-has-type.html) | `"error"` |
|
|
526
|
-
| [`react/checked-requires-onchange-or-readonly`](https://oxc.rs/docs/guide/usage/linter/rules/react/checked-requires-onchange-or-readonly.html) | `"error"` |
|
|
527
|
-
| [`react/iframe-missing-sandbox`](https://oxc.rs/docs/guide/usage/linter/rules/react/iframe-missing-sandbox.html) | `"error"` |
|
|
528
|
-
| [`react/jsx-boolean-value`](https://oxc.rs/docs/guide/usage/linter/rules/react/jsx-boolean-value.html) | `"error"` |
|
|
529
|
-
| [`react/jsx-key`](https://oxc.rs/docs/guide/usage/linter/rules/react/jsx-key.html) | `"error"` |
|
|
530
|
-
| [`react/jsx-no-comment-textnodes`](https://oxc.rs/docs/guide/usage/linter/rules/react/jsx-no-comment-textnodes.html) | `"error"` |
|
|
531
|
-
| [`react/jsx-no-constructed-context-values`](https://oxc.rs/docs/guide/usage/linter/rules/react/jsx-no-constructed-context-values.html) | `"error"` |
|
|
532
|
-
| [`react/jsx-no-duplicate-props`](https://oxc.rs/docs/guide/usage/linter/rules/react/jsx-no-duplicate-props.html) | `"error"` |
|
|
533
|
-
| [`react/jsx-no-target-blank`](https://oxc.rs/docs/guide/usage/linter/rules/react/jsx-no-target-blank.html) | `"error"` |
|
|
534
|
-
| [`react/jsx-no-useless-fragment`](https://oxc.rs/docs/guide/usage/linter/rules/react/jsx-no-useless-fragment.html) | `"error"` |
|
|
535
|
-
| [`react/no-array-index-key`](https://oxc.rs/docs/guide/usage/linter/rules/react/no-array-index-key.html) | `"error"` |
|
|
536
|
-
| [`react/no-children-prop`](https://oxc.rs/docs/guide/usage/linter/rules/react/no-children-prop.html) | `"error"` |
|
|
537
|
-
| [`react/no-danger`](https://oxc.rs/docs/guide/usage/linter/rules/react/no-danger.html) | `"error"` |
|
|
538
|
-
| [`react/no-danger-with-children`](https://oxc.rs/docs/guide/usage/linter/rules/react/no-danger-with-children.html) | `"error"` |
|
|
539
|
-
| [`react/no-direct-mutation-state`](https://oxc.rs/docs/guide/usage/linter/rules/react/no-direct-mutation-state.html) | `"error"` |
|
|
540
|
-
| [`react/no-is-mounted`](https://oxc.rs/docs/guide/usage/linter/rules/react/no-is-mounted.html) | `"error"` |
|
|
541
|
-
| [`react/no-render-return-value`](https://oxc.rs/docs/guide/usage/linter/rules/react/no-render-return-value.html) | `"error"` |
|
|
542
|
-
| [`react/no-string-refs`](https://oxc.rs/docs/guide/usage/linter/rules/react/no-string-refs.html) | `"error"` |
|
|
543
|
-
| [`react/no-this-in-sfc`](https://oxc.rs/docs/guide/usage/linter/rules/react/no-this-in-sfc.html) | `"error"` |
|
|
544
|
-
| [`react/no-unsafe`](https://oxc.rs/docs/guide/usage/linter/rules/react/no-unsafe.html) | `"error"` |
|
|
545
|
-
| [`react/no-unescaped-entities`](https://oxc.rs/docs/guide/usage/linter/rules/react/no-unescaped-entities.html) | `"error"` |
|
|
546
|
-
| [`react/no-unstable-nested-components`](https://oxc.rs/docs/guide/usage/linter/rules/react/no-unstable-nested-components.html) | `["error",{"allowAsProps":true}]` |
|
|
547
|
-
| [`react/require-render-return`](https://oxc.rs/docs/guide/usage/linter/rules/react/require-render-return.html) | `"error"` |
|
|
548
|
-
| [`react/self-closing-comp`](https://oxc.rs/docs/guide/usage/linter/rules/react/self-closing-comp.html) | `"error"` |
|
|
549
|
-
| [`react/void-dom-elements-no-children`](https://oxc.rs/docs/guide/usage/linter/rules/react/void-dom-elements-no-children.html) | `"error"` |
|
|
550
|
-
| [`react/error-boundaries`](https://oxc.rs/docs/guide/usage/linter/rules/react/error-boundaries.html) | `"error"` |
|
|
551
|
-
| [`react/globals`](https://oxc.rs/docs/guide/usage/linter/rules/react/globals.html) | `"error"` |
|
|
552
|
-
| [`react/immutability`](https://oxc.rs/docs/guide/usage/linter/rules/react/immutability.html) | `"error"` |
|
|
553
|
-
| [`react/incompatible-library`](https://oxc.rs/docs/guide/usage/linter/rules/react/incompatible-library.html) | `"error"` |
|
|
554
|
-
| [`react/preserve-manual-memoization`](https://oxc.rs/docs/guide/usage/linter/rules/react/preserve-manual-memoization.html) | `"error"` |
|
|
555
|
-
| [`react/purity`](https://oxc.rs/docs/guide/usage/linter/rules/react/purity.html) | `"error"` |
|
|
556
|
-
| [`react/refs`](https://oxc.rs/docs/guide/usage/linter/rules/react/refs.html) | `"error"` |
|
|
557
|
-
| [`react/set-state-in-effect`](https://oxc.rs/docs/guide/usage/linter/rules/react/set-state-in-effect.html) | `"error"` |
|
|
558
|
-
| [`react/set-state-in-render`](https://oxc.rs/docs/guide/usage/linter/rules/react/set-state-in-render.html) | `"error"` |
|
|
559
|
-
| [`react/static-components`](https://oxc.rs/docs/guide/usage/linter/rules/react/static-components.html) | `"error"` |
|
|
560
|
-
| [`react/use-memo`](https://oxc.rs/docs/guide/usage/linter/rules/react/use-memo.html) | `"error"` |
|
|
561
|
-
| [`react/void-use-memo`](https://oxc.rs/docs/guide/usage/linter/rules/react/void-use-memo.html) | `"error"` |
|
|
562
|
-
| [`react/capitalized-calls`](https://oxc.rs/docs/guide/usage/linter/rules/react/capitalized-calls.html) | `"error"` |
|
|
563
|
-
| [`react/exhaustive-effect-dependencies`](https://oxc.rs/docs/guide/usage/linter/rules/react/exhaustive-effect-dependencies.html) | `"error"` |
|
|
564
|
-
| [`react/hooks`](https://oxc.rs/docs/guide/usage/linter/rules/react/hooks.html) | `"error"` |
|
|
565
|
-
| [`react/memo-dependencies`](https://oxc.rs/docs/guide/usage/linter/rules/react/memo-dependencies.html) | `"error"` |
|
|
566
|
-
| [`react/no-deriving-state-in-effects`](https://oxc.rs/docs/guide/usage/linter/rules/react/no-deriving-state-in-effects.html) | `"error"` |
|
|
567
|
-
| [`react/invariant`](https://oxc.rs/docs/guide/usage/linter/rules/react/invariant.html) | `"error"` |
|
|
568
|
-
| [`react/rule-suppression`](https://oxc.rs/docs/guide/usage/linter/rules/react/rule-suppression.html) | `"error"` |
|
|
569
|
-
| [`react/syntax`](https://oxc.rs/docs/guide/usage/linter/rules/react/syntax.html) | `"error"` |
|
|
570
|
-
| [`react/todo`](https://oxc.rs/docs/guide/usage/linter/rules/react/todo.html) | `"error"` |
|
|
571
|
-
| [`react/unsupported-syntax`](https://oxc.rs/docs/guide/usage/linter/rules/react/unsupported-syntax.html) | `"error"` |
|
|
572
|
-
| [`jsx-a11y/alt-text`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/alt-text.html) | `"error"` |
|
|
573
|
-
| [`jsx-a11y/anchor-has-content`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/anchor-has-content.html) | `"error"` |
|
|
574
|
-
| [`jsx-a11y/anchor-is-valid`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/anchor-is-valid.html) | `"error"` |
|
|
575
|
-
| [`jsx-a11y/click-events-have-key-events`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/click-events-have-key-events.html) | `"off"` |
|
|
576
|
-
| [`jsx-a11y/control-has-associated-label`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/control-has-associated-label.html) | `"off"` |
|
|
577
|
-
| [`jsx-a11y/heading-has-content`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/heading-has-content.html) | `"error"` |
|
|
578
|
-
| [`jsx-a11y/img-redundant-alt`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/img-redundant-alt.html) | `"error"` |
|
|
579
|
-
| [`jsx-a11y/interactive-supports-focus`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/interactive-supports-focus.html) | `"error"` |
|
|
580
|
-
| [`jsx-a11y/label-has-associated-control`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/label-has-associated-control.html) | `"error"` |
|
|
581
|
-
| [`jsx-a11y/no-aria-hidden-on-focusable`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-aria-hidden-on-focusable.html) | `"error"` |
|
|
582
|
-
| [`jsx-a11y/no-autofocus`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-autofocus.html) | `["error",{"ignoreNonDOM":true}]` |
|
|
583
|
-
| [`jsx-a11y/no-noninteractive-element-interactions`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-noninteractive-element-interactions.html) | `"off"` |
|
|
584
|
-
| [`jsx-a11y/no-noninteractive-tabindex`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-noninteractive-tabindex.html) | `"error"` |
|
|
585
|
-
| [`jsx-a11y/no-redundant-roles`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-redundant-roles.html) | `"error"` |
|
|
586
|
-
| [`jsx-a11y/no-static-element-interactions`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-static-element-interactions.html) | `"error"` |
|
|
587
|
-
| [`jsx-a11y/prefer-tag-over-role`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/prefer-tag-over-role.html) | `"off"` |
|
|
588
|
-
| [`jsx-a11y/role-has-required-aria-props`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/role-has-required-aria-props.html) | `"error"` |
|
|
589
|
-
| [`jsx-a11y/role-supports-aria-props`](https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/role-supports-aria-props.html) | `"error"` |
|
|
590
|
-
| [`react-effect/no-derived-state`](https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect) | `"error"` |
|
|
591
|
-
| [`react-effect/no-chain-state-updates`](https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect) | `"error"` |
|
|
592
|
-
| [`react-effect/no-event-handler`](https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect) | `"error"` |
|
|
593
|
-
| [`react-effect/no-adjust-state-on-prop-change`](https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect) | `"error"` |
|
|
594
|
-
| [`react-effect/no-reset-all-state-on-prop-change`](https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect) | `"error"` |
|
|
595
|
-
| [`react-effect/no-pass-live-state-to-parent`](https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect) | `"error"` |
|
|
596
|
-
| [`react-effect/no-pass-data-to-parent`](https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect) | `"error"` |
|
|
597
|
-
| [`react-effect/no-external-store-subscription`](https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect) | `"error"` |
|
|
598
|
-
| [`react-effect/no-initialize-state`](https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect) | `"error"` |
|
|
599
|
-
| [`unicorn/filename-case`](https://oxc.rs/docs/guide/usage/linter/rules/unicorn/filename-case.html) | `["error",{"cases":{"kebabCase":true,"pascalCase":true}}]` |
|
|
600
|
-
|
|
601
|
-
### `@ashstack/query`
|
|
602
|
-
|
|
603
|
-
_auto-enabled by `react()` when `@tanstack/react-query` is a dependency._
|
|
604
|
-
|
|
605
|
-
#### `@ashstack/query/no-inline-keys`
|
|
606
|
-
|
|
607
|
-
Disallow a query key written as an array literal at the call site instead of coming from a keys factory.
|
|
608
|
-
|
|
609
|
-
**Fails**
|
|
610
|
-
|
|
611
|
-
```tsx
|
|
612
|
-
import { useQuery } from "@tanstack/react-query";
|
|
613
|
-
import { View } from "react-native";
|
|
614
|
-
|
|
615
|
-
import { getPosts } from "@/api/posts/posts.requests";
|
|
616
|
-
|
|
617
|
-
export const PostList = () => {
|
|
618
|
-
const { data } = useQuery({ queryKey: ["posts", "list"], queryFn: getPosts });
|
|
619
|
-
return <View accessible={data !== undefined} />;
|
|
620
|
-
};
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
**Passes**
|
|
624
|
-
|
|
625
|
-
```tsx
|
|
626
|
-
import { useQuery } from "@tanstack/react-query";
|
|
627
|
-
import { View } from "react-native";
|
|
628
|
-
|
|
629
|
-
import { postKeys } from "@/api/posts/posts.keys";
|
|
630
|
-
import { getPosts } from "@/api/posts/posts.requests";
|
|
631
|
-
|
|
632
|
-
export const PostList = () => {
|
|
633
|
-
const { data } = useQuery({ queryKey: postKeys.list(), queryFn: getPosts });
|
|
634
|
-
return <View accessible={data !== undefined} />;
|
|
635
|
-
};
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
#### `@ashstack/query/no-deprecated-filters`
|
|
639
|
-
|
|
640
|
-
Disallow the positional key argument that TanStack Query v5 removed from `invalidateQueries` and its sibling methods. The suggestion rewrites it to the filter-object form.
|
|
641
|
-
|
|
642
|
-
**Fails**
|
|
643
|
-
|
|
644
|
-
```tsx
|
|
645
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
646
|
-
import { View } from "react-native";
|
|
647
|
-
|
|
648
|
-
export const RefreshPanel = () => {
|
|
649
|
-
const queryClient = useQueryClient();
|
|
650
|
-
const refresh = () => queryClient.invalidateQueries(["posts"]);
|
|
651
|
-
return <View onLayout={refresh} />;
|
|
652
|
-
};
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
**Passes**
|
|
656
|
-
|
|
657
|
-
```tsx
|
|
658
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
659
|
-
import { View } from "react-native";
|
|
660
|
-
|
|
661
|
-
import { postKeys } from "@/api/posts/posts.keys";
|
|
662
|
-
|
|
663
|
-
export const RefreshPanel = () => {
|
|
664
|
-
const queryClient = useQueryClient();
|
|
665
|
-
const refresh = () => queryClient.invalidateQueries({ queryKey: postKeys.all() });
|
|
666
|
-
return <View onLayout={refresh} />;
|
|
667
|
-
};
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
#### `@ashstack/query/require-destructured-hooks`
|
|
671
|
-
|
|
672
|
-
Require destructuring the result of a hook imported from an `@/api/*.queries` or `@/api/*.mutations` module.
|
|
673
|
-
|
|
674
|
-
**Fails**
|
|
675
|
-
|
|
676
|
-
```tsx
|
|
677
|
-
import { View } from "react-native";
|
|
678
|
-
|
|
679
|
-
import { usePostsQuery } from "@/api/posts/posts.queries";
|
|
680
|
-
|
|
681
|
-
export const PostList = () => {
|
|
682
|
-
const posts = usePostsQuery();
|
|
683
|
-
return <View accessible={posts.isSuccess} />;
|
|
684
|
-
};
|
|
685
|
-
```
|
|
686
|
-
|
|
687
|
-
**Passes**
|
|
688
|
-
|
|
689
|
-
```tsx
|
|
690
|
-
import { View } from "react-native";
|
|
691
|
-
|
|
692
|
-
import { usePostsQuery } from "@/api/posts/posts.queries";
|
|
693
|
-
|
|
694
|
-
export const PostList = () => {
|
|
695
|
-
const { isSuccess } = usePostsQuery();
|
|
696
|
-
return <View accessible={isSuccess} />;
|
|
697
|
-
};
|
|
698
|
-
```
|
|
699
|
-
|
|
700
|
-
#### `@ashstack/query/no-fetch-in-query-fn`
|
|
701
|
-
|
|
702
|
-
Disallow a bare `fetch(` inside a `queryFn` or `mutationFn`.
|
|
703
|
-
|
|
704
|
-
**Fails**
|
|
705
|
-
|
|
706
|
-
```tsx
|
|
707
|
-
import { useQuery } from "@tanstack/react-query";
|
|
708
|
-
import { View } from "react-native";
|
|
709
|
-
|
|
710
|
-
import { postKeys } from "@/api/posts/posts.keys";
|
|
711
|
-
|
|
712
|
-
export const PostList = () => {
|
|
713
|
-
const { data } = useQuery({
|
|
714
|
-
queryKey: postKeys.list(),
|
|
715
|
-
queryFn: () => fetch("/api/posts").then(response => response.json()),
|
|
716
|
-
});
|
|
717
|
-
return <View accessible={data !== undefined} />;
|
|
718
|
-
};
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
**Passes**
|
|
722
|
-
|
|
723
|
-
```tsx
|
|
724
|
-
import { useQuery } from "@tanstack/react-query";
|
|
725
|
-
import { View } from "react-native";
|
|
726
|
-
|
|
727
|
-
import { postKeys } from "@/api/posts/posts.keys";
|
|
728
|
-
import { getPosts } from "@/api/posts/posts.requests";
|
|
729
|
-
|
|
730
|
-
export const PostList = () => {
|
|
731
|
-
const { data } = useQuery({ queryKey: postKeys.list(), queryFn: getPosts });
|
|
732
|
-
return <View accessible={data !== undefined} />;
|
|
733
|
-
};
|
|
734
|
-
```
|
|
735
|
-
|
|
736
|
-
#### `@ashstack/query/next-page-param-undefined`
|
|
737
|
-
|
|
738
|
-
Disallow `return null` in the body of a `getNextPageParam`.
|
|
739
|
-
|
|
740
|
-
**Fails**
|
|
741
|
-
|
|
742
|
-
```tsx
|
|
743
|
-
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
744
|
-
import { View } from "react-native";
|
|
745
|
-
|
|
746
|
-
import { postKeys } from "@/api/posts/posts.keys";
|
|
747
|
-
import { getPosts } from "@/api/posts/posts.requests";
|
|
748
|
-
|
|
749
|
-
export const PostFeed = () => {
|
|
750
|
-
const { data } = useInfiniteQuery({
|
|
751
|
-
queryKey: postKeys.list(),
|
|
752
|
-
queryFn: getPosts,
|
|
753
|
-
initialPageParam: 0,
|
|
754
|
-
getNextPageParam: lastPage => {
|
|
755
|
-
if (!lastPage.nextCursor) return null;
|
|
756
|
-
return lastPage.nextCursor;
|
|
757
|
-
},
|
|
758
|
-
});
|
|
759
|
-
return <View accessible={data !== undefined} />;
|
|
760
|
-
};
|
|
761
|
-
```
|
|
762
|
-
|
|
763
|
-
**Passes**
|
|
764
|
-
|
|
765
|
-
```tsx
|
|
766
|
-
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
767
|
-
import { View } from "react-native";
|
|
768
|
-
|
|
769
|
-
import { postKeys } from "@/api/posts/posts.keys";
|
|
770
|
-
import { getPosts } from "@/api/posts/posts.requests";
|
|
771
|
-
|
|
772
|
-
export const PostFeed = () => {
|
|
773
|
-
const { data } = useInfiniteQuery({
|
|
774
|
-
queryKey: postKeys.list(),
|
|
775
|
-
queryFn: getPosts,
|
|
776
|
-
initialPageParam: 0,
|
|
777
|
-
getNextPageParam: lastPage => {
|
|
778
|
-
if (!lastPage.nextCursor) return undefined;
|
|
779
|
-
return lastPage.nextCursor;
|
|
780
|
-
},
|
|
781
|
-
});
|
|
782
|
-
return <View accessible={data !== undefined} />;
|
|
783
|
-
};
|
|
784
|
-
```
|
|
785
|
-
|
|
786
|
-
### `@ashstack/zustand`
|
|
787
|
-
|
|
788
|
-
_auto-enabled by `react()` when `zustand` is a dependency._
|
|
789
|
-
|
|
790
|
-
#### `@ashstack/zustand/require-selector`
|
|
791
|
-
|
|
792
|
-
Require a selector on a store hook, rather than no arguments or `undefined` in its place.
|
|
793
|
-
|
|
794
|
-
**Fails**
|
|
795
|
-
|
|
796
|
-
```tsx
|
|
797
|
-
import { View } from "react-native";
|
|
798
|
-
|
|
799
|
-
import { useSettingsStore } from "@/stores/settings-store";
|
|
800
|
-
|
|
801
|
-
export const Panel = () => {
|
|
802
|
-
const settings = useSettingsStore();
|
|
803
|
-
return <View accessible={settings.ready} />;
|
|
804
|
-
};
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
**Passes**
|
|
808
|
-
|
|
809
|
-
```tsx
|
|
810
|
-
import { View } from "react-native";
|
|
811
|
-
|
|
812
|
-
import { useSettingsStore } from "@/stores/settings-store";
|
|
813
|
-
|
|
814
|
-
export const Panel = () => {
|
|
815
|
-
const ready = useSettingsStore(state => state.ready);
|
|
816
|
-
return <View accessible={ready} />;
|
|
817
|
-
};
|
|
818
|
-
```
|
|
819
|
-
|
|
820
|
-
### `@ashstack/i18n`
|
|
821
|
-
|
|
822
|
-
_auto-enabled by `react()` when an i18n library (i18next, lingui, react-intl, use-intl, next-intl, expo-localization) is a dependency._
|
|
823
|
-
|
|
824
|
-
#### `@ashstack/i18n/no-bare-text`
|
|
825
|
-
|
|
826
|
-
Disallow a JSX element that carries no attributes and whose single child is plain literal text.
|
|
827
|
-
|
|
828
|
-
**Fails**
|
|
829
|
-
|
|
830
|
-
```tsx
|
|
831
|
-
import { Text } from "@/components/ui/text";
|
|
832
|
-
|
|
833
|
-
export const Greeting = () => <Text>Hello there</Text>;
|
|
834
|
-
```
|
|
835
|
-
|
|
836
|
-
**Passes**
|
|
837
|
-
|
|
838
|
-
```tsx
|
|
839
|
-
import { useTranslation } from "react-i18next";
|
|
840
|
-
|
|
841
|
-
import { Text } from "@/components/ui/text";
|
|
842
|
-
|
|
843
|
-
export const Greeting = () => {
|
|
844
|
-
const { t } = useTranslation();
|
|
845
|
-
return <Text>{t("greeting")}</Text>;
|
|
846
|
-
};
|
|
847
|
-
```
|
|
848
|
-
|
|
849
|
-
#### `@ashstack/i18n/no-bare-attrs`
|
|
850
|
-
|
|
851
|
-
Disallow a plain string literal on a configurable list of user-visible JSX attributes, defaulting to placeholder, accessibilityLabel, accessibilityHint and title.
|
|
852
|
-
|
|
853
|
-
**Options**
|
|
854
|
-
|
|
855
|
-
```jsonc
|
|
856
|
-
[
|
|
857
|
-
{
|
|
858
|
-
"type": "object",
|
|
859
|
-
"properties": {
|
|
860
|
-
"attributes": {
|
|
861
|
-
"type": "array",
|
|
862
|
-
"items": {
|
|
863
|
-
"type": "string"
|
|
864
|
-
},
|
|
865
|
-
"minItems": 1
|
|
866
|
-
}
|
|
867
|
-
},
|
|
868
|
-
"additionalProperties": false
|
|
869
|
-
}
|
|
870
|
-
]
|
|
871
|
-
```
|
|
872
|
-
|
|
873
|
-
**Fails**
|
|
874
|
-
|
|
875
|
-
```tsx
|
|
876
|
-
import { TextInput } from "react-native";
|
|
877
|
-
|
|
878
|
-
export const NameField = () => <TextInput placeholder="Your name" />;
|
|
879
|
-
```
|
|
880
|
-
|
|
881
|
-
**Passes**
|
|
882
|
-
|
|
883
|
-
```tsx
|
|
884
|
-
import { useTranslation } from "react-i18next";
|
|
885
|
-
import { TextInput } from "react-native";
|
|
886
|
-
|
|
887
|
-
export const NameField = () => {
|
|
888
|
-
const { t } = useTranslation();
|
|
889
|
-
return <TextInput placeholder={t("name")} />;
|
|
890
|
-
};
|
|
891
|
-
```
|
|
892
|
-
|
|
893
|
-
#### `@ashstack/i18n/no-bare-toast`
|
|
894
|
-
|
|
895
|
-
Disallow a `toast.*` call whose only argument is a string literal.
|
|
896
|
-
|
|
897
|
-
**Fails**
|
|
898
|
-
|
|
899
|
-
```tsx
|
|
900
|
-
import { toast } from "sonner-native";
|
|
901
|
-
|
|
902
|
-
import { Button } from "@/components/ui/button";
|
|
903
|
-
|
|
904
|
-
export const SaveButton = () => <Button onPress={() => toast.success("Saved")} />;
|
|
905
|
-
```
|
|
906
|
-
|
|
907
|
-
**Passes**
|
|
908
|
-
|
|
909
|
-
```tsx
|
|
910
|
-
import { useTranslation } from "react-i18next";
|
|
911
|
-
import { toast } from "sonner-native";
|
|
912
|
-
|
|
913
|
-
import { Button } from "@/components/ui/button";
|
|
914
|
-
|
|
915
|
-
export const SaveButton = () => {
|
|
916
|
-
const { t } = useTranslation();
|
|
917
|
-
return <Button onPress={() => toast.success(t("saved"))} />;
|
|
918
|
-
};
|
|
919
|
-
```
|
|
920
|
-
|
|
921
|
-
## `react-native()`
|
|
922
|
-
|
|
923
|
-
Expo and React Native.
|
|
924
|
-
|
|
925
|
-
Plugins: `eslint`, `typescript`, `import`, `unicorn`, `promise`, `oxc`, `react`, `jsx-a11y`.
|
|
926
|
-
|
|
927
|
-
### Built-in rules
|
|
928
|
-
|
|
929
|
-
| Rule | Setting |
|
|
930
|
-
| --- | --- |
|
|
931
|
-
| [`eslint/no-empty`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-empty.html) | `"error"` |
|
|
932
|
-
| [`eslint/no-empty-function`](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-empty-function.html) | `"error"` |
|
|
933
|
-
| [`react-effect/no-event-handler`](https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect) | `"off"` |
|
|
934
|
-
|
|
935
|
-
### `@ashstack/react-native`
|
|
936
|
-
|
|
937
|
-
_always on via `reactNative()`._
|
|
938
|
-
|
|
939
|
-
**Import bans that ship with this module**
|
|
940
|
-
|
|
941
|
-
- `forwardRef` from `react`
|
|
942
|
-
- `useContext` from `react`
|
|
943
|
-
- any import of `expo-linear-gradient`, `react-native-linear-gradient`
|
|
944
|
-
|
|
945
|
-
#### `@ashstack/react-native/no-keyboard-will-events`
|
|
946
|
-
|
|
947
|
-
Disallow the `keyboardWill*` event names. They are iOS-only, so on Android the listener registers and never fires.
|
|
948
|
-
|
|
949
|
-
**Fails**
|
|
950
|
-
|
|
951
|
-
```tsx
|
|
952
|
-
import { Keyboard } from "react-native";
|
|
953
|
-
|
|
954
|
-
const onKeyboard = () => undefined;
|
|
955
|
-
|
|
956
|
-
export function subscribeBad() {
|
|
957
|
-
return Keyboard.addListener("keyboardWillShow", onKeyboard);
|
|
958
|
-
}
|
|
959
|
-
```
|
|
960
|
-
|
|
961
|
-
**Passes**
|
|
962
|
-
|
|
963
|
-
```tsx
|
|
964
|
-
import { Keyboard } from "react-native";
|
|
965
|
-
|
|
966
|
-
const onKeyboard = () => undefined;
|
|
967
|
-
|
|
968
|
-
export function subscribeGood() {
|
|
969
|
-
return Keyboard.addListener("keyboardDidShow", onKeyboard);
|
|
970
|
-
}
|
|
971
|
-
```
|
|
972
|
-
|
|
973
|
-
#### `@ashstack/react-native/no-scroll-position-state`
|
|
974
|
-
|
|
975
|
-
Disallow a React state setter inside a scroll handler prop. Scroll fires every frame, and so would the re-render.
|
|
976
|
-
|
|
977
|
-
**Fails**
|
|
978
|
-
|
|
979
|
-
```tsx
|
|
980
|
-
import { useState } from "react";
|
|
981
|
-
import { ScrollView } from "react-native";
|
|
982
|
-
|
|
983
|
-
export function BadScroll() {
|
|
984
|
-
const [, setOffset] = useState(0);
|
|
985
|
-
return <ScrollView onScroll={event => setOffset(event.nativeEvent.contentOffset.y)} />;
|
|
986
|
-
}
|
|
987
|
-
```
|
|
988
|
-
|
|
989
|
-
**Passes**
|
|
990
|
-
|
|
991
|
-
```tsx
|
|
992
|
-
import Animated, { useAnimatedScrollHandler, useSharedValue } from "react-native-reanimated";
|
|
993
|
-
|
|
994
|
-
export function GoodScroll() {
|
|
995
|
-
const offset = useSharedValue(0);
|
|
996
|
-
const onScroll = useAnimatedScrollHandler(event => {
|
|
997
|
-
offset.set(event.contentOffset.y);
|
|
998
|
-
});
|
|
999
|
-
return <Animated.ScrollView onScroll={onScroll} />;
|
|
1000
|
-
}
|
|
1001
|
-
```
|
|
1002
|
-
|
|
1003
|
-
#### `@ashstack/react-native/no-conditional-style-array`
|
|
1004
|
-
|
|
1005
|
-
Disallow conditional and logical entries inside a JSX `style` array. A falsy entry leaves a hole that breaks the Unistyles C++ proxy.
|
|
1006
|
-
|
|
1007
|
-
**Fails**
|
|
1008
|
-
|
|
1009
|
-
```tsx
|
|
1010
|
-
import { View } from "react-native";
|
|
1011
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1012
|
-
|
|
1013
|
-
export function BadStyleArray({ active }: { active: boolean }) {
|
|
1014
|
-
return <View style={[styles.card, active && styles.cardActive]} />;
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
const styles = StyleSheet.create({ card: {}, cardActive: {} });
|
|
1018
|
-
```
|
|
1019
|
-
|
|
1020
|
-
**Passes**
|
|
1021
|
-
|
|
1022
|
-
```tsx
|
|
1023
|
-
import { View } from "react-native";
|
|
1024
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1025
|
-
|
|
1026
|
-
export function GoodStyleArray({ active }: { active: boolean }) {
|
|
1027
|
-
return <View style={styles.card(active)} />;
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
const styles = StyleSheet.create({
|
|
1031
|
-
card: (active: boolean) => ({ opacity: active ? 1 : 0.5 }),
|
|
1032
|
-
});
|
|
1033
|
-
```
|
|
1034
|
-
|
|
1035
|
-
#### `@ashstack/react-native/no-leaked-render`
|
|
1036
|
-
|
|
1037
|
-
Disallow a `&&` guard in JSX on a value this rule can see is a number or a string — a `.length`, a `.size`, arithmetic, a literal. The falsy left operand leaks into the output, and a bare `0` crashes React Native with "Text strings must be rendered within a <Text> component".
|
|
1038
|
-
|
|
1039
|
-
**Fails**
|
|
1040
|
-
|
|
1041
|
-
```tsx
|
|
1042
|
-
import { Text, View } from "react-native";
|
|
1043
|
-
|
|
1044
|
-
export function BadLeakedRender({ items }: { items: string[] }) {
|
|
1045
|
-
return <View>{items.length && <Text>Has items</Text>}</View>;
|
|
1046
|
-
}
|
|
1047
|
-
```
|
|
1048
|
-
|
|
1049
|
-
**Passes**
|
|
1050
|
-
|
|
1051
|
-
```tsx
|
|
1052
|
-
import { Text, View } from "react-native";
|
|
1053
|
-
|
|
1054
|
-
export function GoodLeakedRender({ items }: { items: string[] }) {
|
|
1055
|
-
return <View>{items.length > 0 && <Text>Has items</Text>}</View>;
|
|
1056
|
-
}
|
|
1057
|
-
```
|
|
1058
|
-
|
|
1059
|
-
#### `@ashstack/react-native/no-rn-image-network-source`
|
|
1060
|
-
|
|
1061
|
-
Disallow a react-native `<Image>` with a network `{ uri }` source. It has no disk cache and no decode sizing, so the image re-downloads at full size on every cold start.
|
|
1062
|
-
|
|
1063
|
-
**Fails**
|
|
1064
|
-
|
|
1065
|
-
```tsx
|
|
1066
|
-
import { Image } from "react-native";
|
|
1067
|
-
|
|
1068
|
-
export function BadNetworkImage({ uri }: { uri: string }) {
|
|
1069
|
-
return <Image source={{ uri }} />;
|
|
1070
|
-
}
|
|
1071
|
-
```
|
|
1072
|
-
|
|
1073
|
-
**Passes**
|
|
1074
|
-
|
|
1075
|
-
```tsx
|
|
1076
|
-
import { Image } from "react-native";
|
|
1077
|
-
import { TurboImage } from "react-native-turbo-image";
|
|
1078
|
-
|
|
1079
|
-
const logo = require("./logo.png");
|
|
1080
|
-
|
|
1081
|
-
export function GoodLocalImage() {
|
|
1082
|
-
return <Image source={logo} />;
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
export function GoodNetworkImage({ uri }: { uri: string }) {
|
|
1086
|
-
return <TurboImage source={{ uri }} resize={40} cachePolicy="dataCache" />;
|
|
1087
|
-
}
|
|
1088
|
-
```
|
|
1089
|
-
|
|
1090
|
-
#### `@ashstack/react-native/no-redundant-view-nesting`
|
|
1091
|
-
|
|
1092
|
-
Disallow a View or Animated.View that wraps an identical view when neither carries anything but a style. Every extra host view is a real node in the native tree.
|
|
1093
|
-
|
|
1094
|
-
**Fails**
|
|
1095
|
-
|
|
1096
|
-
```tsx
|
|
1097
|
-
import { View } from "react-native";
|
|
1098
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1099
|
-
|
|
1100
|
-
export function BadNesting() {
|
|
1101
|
-
return (
|
|
1102
|
-
<View style={styles.outer}>
|
|
1103
|
-
<View style={styles.inner} />
|
|
1104
|
-
</View>
|
|
1105
|
-
);
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
const styles = StyleSheet.create({ outer: {}, inner: {} });
|
|
1109
|
-
```
|
|
1110
|
-
|
|
1111
|
-
**Passes**
|
|
1112
|
-
|
|
1113
|
-
```tsx
|
|
1114
|
-
import { Text, View } from "react-native";
|
|
1115
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1116
|
-
|
|
1117
|
-
export function GoodNesting() {
|
|
1118
|
-
return (
|
|
1119
|
-
<View style={styles.merged}>
|
|
1120
|
-
<Text>One host view instead of two</Text>
|
|
1121
|
-
</View>
|
|
1122
|
-
);
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
const styles = StyleSheet.create({ merged: {} });
|
|
1126
|
-
```
|
|
1127
|
-
|
|
1128
|
-
#### `@ashstack/react-native/no-rn-namespace-import`
|
|
1129
|
-
|
|
1130
|
-
Disallow a namespace import of react-native and a re-export of its `Platform`. Both defeat Metro's platform shaking, so dead `Platform.OS` branches ship in both bundles.
|
|
1131
|
-
|
|
1132
|
-
**Fails**
|
|
1133
|
-
|
|
1134
|
-
```tsx
|
|
1135
|
-
import * as RN from "react-native";
|
|
1136
|
-
|
|
1137
|
-
export const BadNamespace = () => <RN.View />;
|
|
1138
|
-
```
|
|
1139
|
-
|
|
1140
|
-
**Passes**
|
|
1141
|
-
|
|
1142
|
-
```tsx
|
|
1143
|
-
import { View } from "react-native";
|
|
1144
|
-
|
|
1145
|
-
export const GoodNamespace = () => <View />;
|
|
1146
|
-
```
|
|
1147
|
-
|
|
1148
|
-
#### `@ashstack/react-native/no-unlabeled-icon-pressable`
|
|
1149
|
-
|
|
1150
|
-
Require an accessible name on an icon-only touchable or an icon-only Expo UI `<Button>`. Without a label, hint or visible text, a screen reader cannot reach the control.
|
|
1151
|
-
|
|
1152
|
-
**Fails**
|
|
1153
|
-
|
|
1154
|
-
```tsx
|
|
1155
|
-
import { Pressable } from "react-native-gesture-handler";
|
|
1156
|
-
import { StarIcon } from "./icons";
|
|
1157
|
-
|
|
1158
|
-
export function BadIconButton({ onPress }: { onPress: () => void }) {
|
|
1159
|
-
return (
|
|
1160
|
-
<Pressable onPress={onPress}>
|
|
1161
|
-
<StarIcon />
|
|
1162
|
-
</Pressable>
|
|
1163
|
-
);
|
|
1164
|
-
}
|
|
1165
|
-
```
|
|
1166
|
-
|
|
1167
|
-
**Passes**
|
|
1168
|
-
|
|
1169
|
-
```tsx
|
|
1170
|
-
import { Pressable } from "react-native-gesture-handler";
|
|
1171
|
-
import { StarIcon } from "./icons";
|
|
1172
|
-
|
|
1173
|
-
export function GoodIconButton({ onPress }: { onPress: () => void }) {
|
|
1174
|
-
return (
|
|
1175
|
-
<Pressable accessibilityLabel="Add to favourites" onPress={onPress}>
|
|
1176
|
-
<StarIcon />
|
|
1177
|
-
</Pressable>
|
|
1178
|
-
);
|
|
1179
|
-
}
|
|
1180
|
-
```
|
|
1181
|
-
|
|
1182
|
-
#### `@ashstack/react-native/hoist-stateless-function`
|
|
1183
|
-
|
|
1184
|
-
Require module scope for a non-component function that reads nothing from the component around it. Out there it is created once, keeps a stable identity, and can be tested without rendering.
|
|
1185
|
-
|
|
1186
|
-
**Fails**
|
|
1187
|
-
|
|
1188
|
-
```tsx
|
|
1189
|
-
import { Text } from "react-native";
|
|
1190
|
-
|
|
1191
|
-
export function BadHoist() {
|
|
1192
|
-
const formatLabel = (count: number) => `${count} items`;
|
|
1193
|
-
return <Text>{formatLabel(3)}</Text>;
|
|
1194
|
-
}
|
|
1195
|
-
```
|
|
1196
|
-
|
|
1197
|
-
**Passes**
|
|
1198
|
-
|
|
1199
|
-
```tsx
|
|
1200
|
-
import { Text } from "react-native";
|
|
1201
|
-
|
|
1202
|
-
const formatLabel = (count: number) => `${count} items`;
|
|
1203
|
-
|
|
1204
|
-
export function GoodHoist({ count }: { count: number }) {
|
|
1205
|
-
const label = () => formatLabel(count);
|
|
1206
|
-
return <Text>{label()}</Text>;
|
|
1207
|
-
}
|
|
1208
|
-
```
|
|
1209
|
-
|
|
1210
|
-
#### `@ashstack/react-native/no-manual-memo`
|
|
1211
|
-
|
|
1212
|
-
Disallow `useMemo`, `useCallback` and `memo` unless a `why:` comment on the line above states the case the React Compiler cannot see: something rendered per list row, or a cost that was measured.
|
|
1213
|
-
|
|
1214
|
-
> Enabled only when one of `babel-plugin-react-compiler`, `react-compiler-runtime`, `react-compiler-marker` is a dependency.
|
|
1215
|
-
|
|
1216
|
-
**Fails**
|
|
1217
|
-
|
|
1218
|
-
```tsx
|
|
1219
|
-
import { useMemo } from "react";
|
|
1220
|
-
|
|
1221
|
-
export function useBadTotal(items: number[]) {
|
|
1222
|
-
return useMemo(() => items.reduce((sum, item) => sum + item, 0), [items]);
|
|
1223
|
-
}
|
|
1224
|
-
```
|
|
1225
|
-
|
|
1226
|
-
**Passes**
|
|
1227
|
-
|
|
1228
|
-
```tsx
|
|
1229
|
-
import { useMemo } from "react";
|
|
1230
|
-
|
|
1231
|
-
export function useGoodTotal(items: number[]) {
|
|
1232
|
-
// why: measured - this sum runs per row in a 10k-item list
|
|
1233
|
-
return useMemo(() => items.reduce((sum, item) => sum + item, 0), [items]);
|
|
1234
|
-
}
|
|
1235
|
-
```
|
|
1236
|
-
|
|
1237
|
-
#### `@ashstack/react-native/no-dynamic-import`
|
|
1238
|
-
|
|
1239
|
-
Disallow dynamic `import()` outside a `React.lazy` or `dynamic` wrapper. Metro inlines it into the same bundle, so nothing is deferred and the module is hidden from the typechecker.
|
|
1240
|
-
|
|
1241
|
-
**Fails**
|
|
1242
|
-
|
|
1243
|
-
```tsx
|
|
1244
|
-
import { View } from "react-native";
|
|
1245
|
-
|
|
1246
|
-
export const loadHelpers = () => import("./helpers");
|
|
1247
|
-
|
|
1248
|
-
export const Panel = () => <View />;
|
|
1249
|
-
```
|
|
1250
|
-
|
|
1251
|
-
**Passes**
|
|
1252
|
-
|
|
1253
|
-
```tsx
|
|
1254
|
-
import { lazy } from "react";
|
|
1255
|
-
import { View } from "react-native";
|
|
1256
|
-
|
|
1257
|
-
export const Details = lazy(() => import("./details"));
|
|
1258
|
-
|
|
1259
|
-
export const Panel = () => <View />;
|
|
1260
|
-
```
|
|
1261
|
-
|
|
1262
|
-
### `@ashstack/unistyles`
|
|
1263
|
-
|
|
1264
|
-
_auto-enabled when `react-native-unistyles` is a dependency._
|
|
1265
|
-
|
|
1266
|
-
**Import bans that ship with this module**
|
|
1267
|
-
|
|
1268
|
-
- `StyleSheet` from `react-native`
|
|
1269
|
-
- `useWindowDimensions`, `Dimensions` from `react-native`
|
|
1270
|
-
- `SafeAreaView` from `react-native`
|
|
1271
|
-
- `useSafeAreaInsets`, `useSafeAreaFrame`, `SafeAreaView` from `react-native-safe-area-context`
|
|
1272
|
-
|
|
1273
|
-
#### `@ashstack/unistyles/animated-theme`
|
|
1274
|
-
|
|
1275
|
-
A Reanimated worklet hook cannot see `useUnistyles()` theme changes on the UI thread. Read the shared value from `useAnimatedTheme()` there instead.
|
|
1276
|
-
|
|
1277
|
-
**Fails**
|
|
1278
|
-
|
|
1279
|
-
```tsx
|
|
1280
|
-
import Animated, { useAnimatedStyle } from "react-native-reanimated";
|
|
1281
|
-
import { useUnistyles } from "react-native-unistyles";
|
|
1282
|
-
|
|
1283
|
-
export function Fade() {
|
|
1284
|
-
const { theme } = useUnistyles();
|
|
1285
|
-
const animatedStyle = useAnimatedStyle(() => ({ backgroundColor: theme.colors.accent }));
|
|
1286
|
-
|
|
1287
|
-
return <Animated.View style={animatedStyle} />;
|
|
1288
|
-
}
|
|
1289
|
-
```
|
|
1290
|
-
|
|
1291
|
-
**Passes**
|
|
1292
|
-
|
|
1293
|
-
```tsx
|
|
1294
|
-
import Animated, { useAnimatedStyle } from "react-native-reanimated";
|
|
1295
|
-
import { useAnimatedTheme } from "react-native-unistyles/reanimated";
|
|
1296
|
-
|
|
1297
|
-
export function Fade() {
|
|
1298
|
-
const animatedTheme = useAnimatedTheme();
|
|
1299
|
-
const animatedStyle = useAnimatedStyle(() => ({ backgroundColor: animatedTheme.value.colors.accent }));
|
|
1300
|
-
|
|
1301
|
-
return <Animated.View style={animatedStyle} />;
|
|
1302
|
-
}
|
|
1303
|
-
```
|
|
1304
|
-
|
|
1305
|
-
#### `@ashstack/unistyles/content-container`
|
|
1306
|
-
|
|
1307
|
-
A raw component never subscribes its `contentContainerStyle` to theme or `rt` updates. Wrap the component with `withUnistyles` when the style depends on either.
|
|
1308
|
-
|
|
1309
|
-
**Fails**
|
|
1310
|
-
|
|
1311
|
-
```tsx
|
|
1312
|
-
import { ScrollView } from "react-native";
|
|
1313
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1314
|
-
|
|
1315
|
-
export function List() {
|
|
1316
|
-
return <ScrollView contentContainerStyle={styles.content} />;
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
const styles = StyleSheet.create((theme, rt) => ({
|
|
1320
|
-
content: { paddingBottom: rt.insets.bottom },
|
|
1321
|
-
}));
|
|
1322
|
-
```
|
|
1323
|
-
|
|
1324
|
-
**Passes**
|
|
1325
|
-
|
|
1326
|
-
```tsx
|
|
1327
|
-
import { ScrollView } from "react-native";
|
|
1328
|
-
import { StyleSheet, withUnistyles } from "react-native-unistyles";
|
|
1329
|
-
|
|
1330
|
-
const UnistylesScrollView = withUnistyles(ScrollView);
|
|
1331
|
-
|
|
1332
|
-
export function List() {
|
|
1333
|
-
return <UnistylesScrollView contentContainerStyle={styles.content} />;
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
const styles = StyleSheet.create((theme, rt) => ({
|
|
1337
|
-
content: { paddingBottom: rt.insets.bottom },
|
|
1338
|
-
}));
|
|
1339
|
-
```
|
|
1340
|
-
|
|
1341
|
-
#### `@ashstack/unistyles/in-sheet`
|
|
1342
|
-
|
|
1343
|
-
Inside `StyleSheet.create`, require `rt` and theme values rather than Dimensions, PixelRatio, Appearance, I18nManager, StatusBar or `UnistylesRuntime`. It also covers logical spacing, `boxShadow`, `borderCurve` and a redundant `as const`.
|
|
1344
|
-
|
|
1345
|
-
**Fails**
|
|
1346
|
-
|
|
1347
|
-
```tsx
|
|
1348
|
-
import { Dimensions, I18nManager } from "react-native";
|
|
1349
|
-
import { StyleSheet, UnistylesRuntime } from "react-native-unistyles";
|
|
1350
|
-
|
|
1351
|
-
export const styles = StyleSheet.create(theme => ({
|
|
1352
|
-
card: {
|
|
1353
|
-
position: "absolute" as const,
|
|
1354
|
-
borderRadius: theme.radius.md,
|
|
1355
|
-
shadowColor: theme.colors.shadow,
|
|
1356
|
-
paddingLeft: theme.spacing[4],
|
|
1357
|
-
width: Dimensions.get("window").width,
|
|
1358
|
-
flexDirection: I18nManager.isRTL ? "row-reverse" : "row",
|
|
1359
|
-
paddingBottom: UnistylesRuntime.insets.bottom,
|
|
1360
|
-
},
|
|
1361
|
-
}));
|
|
1362
|
-
```
|
|
1363
|
-
|
|
1364
|
-
**Passes**
|
|
1365
|
-
|
|
1366
|
-
```tsx
|
|
1367
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1368
|
-
|
|
1369
|
-
export const styles = StyleSheet.create((theme, rt) => ({
|
|
1370
|
-
card: {
|
|
1371
|
-
position: "absolute",
|
|
1372
|
-
borderRadius: theme.radius.md,
|
|
1373
|
-
borderCurve: "continuous",
|
|
1374
|
-
boxShadow: theme.shadows.card,
|
|
1375
|
-
paddingStart: theme.spacing[4],
|
|
1376
|
-
width: rt.screen.width,
|
|
1377
|
-
flexDirection: rt.rtl ? "row-reverse" : "row",
|
|
1378
|
-
paddingBottom: rt.insets.bottom,
|
|
1379
|
-
},
|
|
1380
|
-
}));
|
|
1381
|
-
```
|
|
1382
|
-
|
|
1383
|
-
#### `@ashstack/unistyles/insets`
|
|
1384
|
-
|
|
1385
|
-
Disallow passing `useSafeAreaInsets()` values into a dynamic style function or an inline JSX style object. Read `rt.insets` inside `StyleSheet.create` instead.
|
|
1386
|
-
|
|
1387
|
-
**Fails**
|
|
1388
|
-
|
|
1389
|
-
```tsx
|
|
1390
|
-
import { View } from "react-native";
|
|
1391
|
-
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
1392
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1393
|
-
|
|
1394
|
-
export function Screen() {
|
|
1395
|
-
const insets = useSafeAreaInsets();
|
|
1396
|
-
|
|
1397
|
-
return <View style={[styles.footer(insets.bottom), { paddingTop: insets.top }]} />;
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
const styles = StyleSheet.create({
|
|
1401
|
-
footer: (bottom: number) => ({ paddingBottom: bottom }),
|
|
1402
|
-
});
|
|
1403
|
-
```
|
|
1404
|
-
|
|
1405
|
-
**Passes**
|
|
1406
|
-
|
|
1407
|
-
```tsx
|
|
1408
|
-
import { View } from "react-native";
|
|
1409
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1410
|
-
|
|
1411
|
-
export function Screen() {
|
|
1412
|
-
return <View style={styles.footer} />;
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
const styles = StyleSheet.create((theme, rt) => ({
|
|
1416
|
-
footer: { paddingBottom: rt.insets.bottom },
|
|
1417
|
-
}));
|
|
1418
|
-
```
|
|
1419
|
-
|
|
1420
|
-
#### `@ashstack/unistyles/no-hardcoded-color`
|
|
1421
|
-
|
|
1422
|
-
Disallow hex and CSS-function colors inside `StyleSheet.create`. A raw color skips dark mode and never changes with the theme.
|
|
1423
|
-
|
|
1424
|
-
**Fails**
|
|
1425
|
-
|
|
1426
|
-
```tsx
|
|
1427
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1428
|
-
|
|
1429
|
-
export const styles = StyleSheet.create({
|
|
1430
|
-
raw: { color: "#FFFFFF" },
|
|
1431
|
-
translucent: { backgroundColor: "rgba(255, 255, 255, 0.24)" },
|
|
1432
|
-
});
|
|
1433
|
-
```
|
|
1434
|
-
|
|
1435
|
-
**Passes**
|
|
1436
|
-
|
|
1437
|
-
```tsx
|
|
1438
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1439
|
-
|
|
1440
|
-
export const styles = StyleSheet.create(theme => ({
|
|
1441
|
-
tinted: { color: theme.colors.text, backgroundColor: theme.colors.surface },
|
|
1442
|
-
gradient: { experimental_backgroundImage: theme.gradients.header },
|
|
1443
|
-
}));
|
|
1444
|
-
```
|
|
1445
|
-
|
|
1446
|
-
#### `@ashstack/unistyles/no-hardcoded-spacing`
|
|
1447
|
-
|
|
1448
|
-
Require `theme.spacing` or `theme.sizing.scale` for spacing, radius and type values inside `StyleSheet.create`, instead of raw numbers.
|
|
1449
|
-
|
|
1450
|
-
**Fails**
|
|
1451
|
-
|
|
1452
|
-
```tsx
|
|
1453
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1454
|
-
|
|
1455
|
-
export const styles = StyleSheet.create({
|
|
1456
|
-
card: {
|
|
1457
|
-
padding: 8,
|
|
1458
|
-
gap: 4,
|
|
1459
|
-
borderRadius: 12,
|
|
1460
|
-
borderCurve: "continuous",
|
|
1461
|
-
fontSize: 16,
|
|
1462
|
-
},
|
|
1463
|
-
});
|
|
1464
|
-
```
|
|
1465
|
-
|
|
1466
|
-
**Passes**
|
|
1467
|
-
|
|
1468
|
-
```tsx
|
|
1469
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1470
|
-
|
|
1471
|
-
const CALIBRATED_GUTTER = 14;
|
|
1472
|
-
|
|
1473
|
-
export const styles = StyleSheet.create(theme => ({
|
|
1474
|
-
card: {
|
|
1475
|
-
padding: theme.spacing[2],
|
|
1476
|
-
gap: theme.sizing.scale(4),
|
|
1477
|
-
paddingEnd: 0,
|
|
1478
|
-
paddingHorizontal: CALIBRATED_GUTTER,
|
|
1479
|
-
borderRadius: theme.radius.md,
|
|
1480
|
-
borderCurve: "continuous",
|
|
1481
|
-
fontSize: theme.typography.body.size,
|
|
1482
|
-
},
|
|
1483
|
-
}));
|
|
1484
|
-
```
|
|
1485
|
-
|
|
1486
|
-
#### `@ashstack/unistyles/no-margin`
|
|
1487
|
-
|
|
1488
|
-
Disallow non-negative `margin` inside `StyleSheet.create`; `gap` on the parent or `padding` on the element spaces children without leaving a hole when one is removed.
|
|
1489
|
-
|
|
1490
|
-
**Fails**
|
|
1491
|
-
|
|
1492
|
-
```tsx
|
|
1493
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1494
|
-
|
|
1495
|
-
export const styles = StyleSheet.create(theme => ({
|
|
1496
|
-
spaced: { marginTop: theme.spacing[2] },
|
|
1497
|
-
sided: { marginHorizontal: theme.spacing[6] },
|
|
1498
|
-
}));
|
|
1499
|
-
```
|
|
1500
|
-
|
|
1501
|
-
**Passes**
|
|
1502
|
-
|
|
1503
|
-
```tsx
|
|
1504
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1505
|
-
|
|
1506
|
-
const STACK_OVERLAP = 12;
|
|
1507
|
-
|
|
1508
|
-
export const styles = StyleSheet.create(theme => ({
|
|
1509
|
-
row: { gap: theme.spacing[2], paddingHorizontal: theme.spacing[4] },
|
|
1510
|
-
overlapped: { marginStart: -STACK_OVERLAP },
|
|
1511
|
-
}));
|
|
1512
|
-
```
|
|
1513
|
-
|
|
1514
|
-
#### `@ashstack/unistyles/no-style-spread`
|
|
1515
|
-
|
|
1516
|
-
Disallow spreading a stylesheet style into another object. The spread reads through the Unistyles C++ proxy once, so the result stops reacting to the theme.
|
|
1517
|
-
|
|
1518
|
-
**Fails**
|
|
1519
|
-
|
|
1520
|
-
```tsx
|
|
1521
|
-
import { View } from "react-native";
|
|
1522
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1523
|
-
|
|
1524
|
-
export function Card() {
|
|
1525
|
-
return <View style={{ ...styles.card, opacity: 1 }} />;
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
const styles = StyleSheet.create(theme => ({
|
|
1529
|
-
card: { backgroundColor: theme.colors.surface },
|
|
1530
|
-
}));
|
|
1531
|
-
```
|
|
1532
|
-
|
|
1533
|
-
**Passes**
|
|
1534
|
-
|
|
1535
|
-
```tsx
|
|
1536
|
-
import { View } from "react-native";
|
|
1537
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1538
|
-
|
|
1539
|
-
export function Card() {
|
|
1540
|
-
return <View style={[styles.card, styles.raised]} />;
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
const styles = StyleSheet.create(theme => ({
|
|
1544
|
-
card: { backgroundColor: theme.colors.surface },
|
|
1545
|
-
raised: { boxShadow: theme.shadows.card },
|
|
1546
|
-
}));
|
|
1547
|
-
```
|
|
1548
|
-
|
|
1549
|
-
#### `@ashstack/unistyles/no-unused-styles`
|
|
1550
|
-
|
|
1551
|
-
Disallow stylesheet keys that nothing in the file reads. A computed key, a computed read, or a sheet that leaves the module skips the whole file.
|
|
1552
|
-
|
|
1553
|
-
**Fails**
|
|
1554
|
-
|
|
1555
|
-
```tsx
|
|
1556
|
-
import { View } from "react-native";
|
|
1557
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1558
|
-
|
|
1559
|
-
export function Card() {
|
|
1560
|
-
return <View style={styles.card} />;
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
|
-
const styles = StyleSheet.create(theme => ({
|
|
1564
|
-
card: { backgroundColor: theme.colors.surface },
|
|
1565
|
-
orphan: { backgroundColor: theme.colors.accent },
|
|
1566
|
-
}));
|
|
1567
|
-
```
|
|
1568
|
-
|
|
1569
|
-
**Passes**
|
|
1570
|
-
|
|
1571
|
-
```tsx
|
|
1572
|
-
import { View } from "react-native";
|
|
1573
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1574
|
-
|
|
1575
|
-
export function Card() {
|
|
1576
|
-
return <View style={styles.card} />;
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
const styles = StyleSheet.create(theme => ({
|
|
1580
|
-
card: { backgroundColor: theme.colors.surface },
|
|
1581
|
-
}));
|
|
1582
|
-
```
|
|
1583
|
-
|
|
1584
|
-
#### `@ashstack/unistyles/rtl-style-call`
|
|
1585
|
-
|
|
1586
|
-
Disallow passing `I18nManager.isRTL` from JSX into a dynamic style function. Unistyles tracks the dependency itself once the style reads `rt.rtl`.
|
|
1587
|
-
|
|
1588
|
-
**Fails**
|
|
1589
|
-
|
|
1590
|
-
```tsx
|
|
1591
|
-
import { I18nManager, View } from "react-native";
|
|
1592
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1593
|
-
|
|
1594
|
-
export function Row() {
|
|
1595
|
-
return <View style={styles.row(I18nManager.isRTL)} />;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
const styles = StyleSheet.create({
|
|
1599
|
-
row: (rtl: boolean) => ({ transform: [{ scaleX: rtl ? -1 : 1 }] }),
|
|
1600
|
-
});
|
|
1601
|
-
```
|
|
1602
|
-
|
|
1603
|
-
**Passes**
|
|
1604
|
-
|
|
1605
|
-
```tsx
|
|
1606
|
-
import { View } from "react-native";
|
|
1607
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1608
|
-
|
|
1609
|
-
export function Row() {
|
|
1610
|
-
return <View style={styles.row} />;
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
const styles = StyleSheet.create((theme, rt) => ({
|
|
1614
|
-
row: { flexDirection: rt.rtl ? "row-reverse" : "row" },
|
|
1615
|
-
}));
|
|
1616
|
-
```
|
|
1617
|
-
|
|
1618
|
-
#### `@ashstack/unistyles/theme-screen-component`
|
|
1619
|
-
|
|
1620
|
-
`theme.screen.*` is a snapshot taken at module initialization, so a component that reads it never sees the current size. `useUnistyles().rt.screen` and `useWindowDimensions` do.
|
|
1621
|
-
|
|
1622
|
-
**Fails**
|
|
1623
|
-
|
|
1624
|
-
```tsx
|
|
1625
|
-
import { View } from "react-native";
|
|
1626
|
-
import { useUnistyles } from "react-native-unistyles";
|
|
1627
|
-
|
|
1628
|
-
export function Panel() {
|
|
1629
|
-
const { theme } = useUnistyles();
|
|
1630
|
-
|
|
1631
|
-
return <View accessibilityLabel={String(theme.screen.width)} />;
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
export const Card = () => {
|
|
1635
|
-
const { theme } = useUnistyles();
|
|
1636
|
-
|
|
1637
|
-
return <View accessibilityLabel={String(theme.screen.height)} />;
|
|
1638
|
-
};
|
|
1639
|
-
```
|
|
1640
|
-
|
|
1641
|
-
**Passes**
|
|
1642
|
-
|
|
1643
|
-
```tsx
|
|
1644
|
-
import { View } from "react-native";
|
|
1645
|
-
import { useUnistyles } from "react-native-unistyles";
|
|
1646
|
-
|
|
1647
|
-
export function Panel() {
|
|
1648
|
-
const { rt } = useUnistyles();
|
|
1649
|
-
|
|
1650
|
-
return <View accessibilityLabel={String(rt.screen.width)} />;
|
|
1651
|
-
}
|
|
1652
|
-
```
|
|
1653
|
-
|
|
1654
|
-
#### `@ashstack/unistyles/theme-style-attr`
|
|
1655
|
-
|
|
1656
|
-
Theme-dependent values belong in `StyleSheet.create`, not in a JSX `style` prop that reads a `useUnistyles()` theme.
|
|
1657
|
-
|
|
1658
|
-
**Fails**
|
|
1659
|
-
|
|
1660
|
-
```tsx
|
|
1661
|
-
import { View } from "react-native";
|
|
1662
|
-
import { useUnistyles } from "react-native-unistyles";
|
|
1663
|
-
|
|
1664
|
-
export function Badge() {
|
|
1665
|
-
const { theme } = useUnistyles();
|
|
1666
|
-
|
|
1667
|
-
return <View style={{ backgroundColor: theme.colors.accent }} />;
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
export function BadgeList({ ids }: { ids: string[] }) {
|
|
1671
|
-
const { theme } = useUnistyles();
|
|
1672
|
-
|
|
1673
|
-
return <>{ids.map(id => <View key={id} style={{ backgroundColor: theme.colors.accent }} />)}</>;
|
|
1674
|
-
}
|
|
1675
|
-
```
|
|
1676
|
-
|
|
1677
|
-
**Passes**
|
|
1678
|
-
|
|
1679
|
-
```tsx
|
|
1680
|
-
import { View } from "react-native";
|
|
1681
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1682
|
-
|
|
1683
|
-
export function Badge() {
|
|
1684
|
-
return <View style={styles.badge} />;
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
const styles = StyleSheet.create(theme => ({
|
|
1688
|
-
badge: { backgroundColor: theme.colors.accent },
|
|
1689
|
-
}));
|
|
1690
|
-
```
|
|
1691
|
-
|
|
1692
|
-
### `@ashstack/legend-list`
|
|
1693
|
-
|
|
1694
|
-
_auto-enabled when `@legendapp/list` is a dependency._
|
|
1695
|
-
|
|
1696
|
-
**Import bans that ship with this module**
|
|
1697
|
-
|
|
1698
|
-
- `FlatList`, `SectionList`, `VirtualizedList` from `react-native`
|
|
1699
|
-
- `*` from `@shopify/flash-list`
|
|
1700
|
-
- any import of `@legendapp/list`
|
|
1701
|
-
|
|
1702
|
-
#### `@ashstack/legend-list/required-props`
|
|
1703
|
-
|
|
1704
|
-
Require `keyExtractor` and an explicit `recycleItems` on a Legend List. Without a key extractor the list keys its rows by index.
|
|
1705
|
-
|
|
1706
|
-
**Fails**
|
|
1707
|
-
|
|
1708
|
-
```tsx
|
|
1709
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1710
|
-
|
|
1711
|
-
declare const items: { id: string }[];
|
|
1712
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
1713
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
1714
|
-
|
|
1715
|
-
// keyExtractor and recycleItems are both missing.
|
|
1716
|
-
export const Bad = () => <LegendList data={items} renderItem={renderRow} />;
|
|
1717
|
-
```
|
|
1718
|
-
|
|
1719
|
-
**Passes**
|
|
1720
|
-
|
|
1721
|
-
```tsx
|
|
1722
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1723
|
-
|
|
1724
|
-
declare const items: { id: string }[];
|
|
1725
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
1726
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
1727
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
1728
|
-
|
|
1729
|
-
export const Good = () => (
|
|
1730
|
-
<LegendList data={items} renderItem={renderRow} keyExtractor={keyOf} recycleItems />
|
|
1731
|
-
);
|
|
1732
|
-
|
|
1733
|
-
// An explicit opt-out is a decision, not a violation.
|
|
1734
|
-
export const NoRecycle = () => (
|
|
1735
|
-
<LegendList data={items} renderItem={renderRow} keyExtractor={keyOf} recycleItems={false} />
|
|
1736
|
-
);
|
|
1737
|
-
```
|
|
1738
|
-
|
|
1739
|
-
#### `@ashstack/legend-list/no-index-key-extractor`
|
|
1740
|
-
|
|
1741
|
-
Disallow a `keyExtractor` that uses its index parameter. Cached sizes and recycled row state hang off the key, so a prepend points every measurement at the wrong item.
|
|
1742
|
-
|
|
1743
|
-
**Fails**
|
|
1744
|
-
|
|
1745
|
-
```tsx
|
|
1746
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1747
|
-
|
|
1748
|
-
declare const items: { id: string }[];
|
|
1749
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
1750
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
1751
|
-
|
|
1752
|
-
export const Bad = () => (
|
|
1753
|
-
<LegendList
|
|
1754
|
-
data={items}
|
|
1755
|
-
renderItem={renderRow}
|
|
1756
|
-
keyExtractor={(item, index) => String(index)}
|
|
1757
|
-
recycleItems
|
|
1758
|
-
/>
|
|
1759
|
-
);
|
|
1760
|
-
```
|
|
1761
|
-
|
|
1762
|
-
**Passes**
|
|
1763
|
-
|
|
1764
|
-
```tsx
|
|
1765
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1766
|
-
|
|
1767
|
-
declare const items: { id: string }[];
|
|
1768
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
1769
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
1770
|
-
|
|
1771
|
-
export const Good = () => (
|
|
1772
|
-
<LegendList data={items} renderItem={renderRow} keyExtractor={item => item.id} recycleItems />
|
|
1773
|
-
);
|
|
1774
|
-
```
|
|
1775
|
-
|
|
1776
|
-
#### `@ashstack/legend-list/no-remount-key`
|
|
1777
|
-
|
|
1778
|
-
Disallow `key` on a Legend List, which remounts on any key change and loses its measurements and scroll position. Pass `dataKey` instead.
|
|
1779
|
-
|
|
1780
|
-
**Fails**
|
|
1781
|
-
|
|
1782
|
-
```tsx
|
|
1783
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1784
|
-
|
|
1785
|
-
declare const items: { id: string }[];
|
|
1786
|
-
declare const activeTab: string;
|
|
1787
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
1788
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
1789
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
1790
|
-
|
|
1791
|
-
export const Bad = () => (
|
|
1792
|
-
<LegendList key={activeTab} data={items} renderItem={renderRow} keyExtractor={keyOf} recycleItems />
|
|
1793
|
-
);
|
|
1794
|
-
```
|
|
1795
|
-
|
|
1796
|
-
**Passes**
|
|
1797
|
-
|
|
1798
|
-
```tsx
|
|
1799
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1800
|
-
|
|
1801
|
-
declare const items: { id: string }[];
|
|
1802
|
-
declare const activeTab: string;
|
|
1803
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
1804
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
1805
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
1806
|
-
|
|
1807
|
-
export const Good = () => (
|
|
1808
|
-
<LegendList dataKey={activeTab} data={items} renderItem={renderRow} keyExtractor={keyOf} recycleItems />
|
|
1809
|
-
);
|
|
1810
|
-
```
|
|
1811
|
-
|
|
1812
|
-
#### `@ashstack/legend-list/no-inline-data`
|
|
1813
|
-
|
|
1814
|
-
Disallow building a Legend List's `data` inline. Each render produces a new array reference, and the list re-diffs, re-keys and drops what it had cached.
|
|
1815
|
-
|
|
1816
|
-
**Fails**
|
|
1817
|
-
|
|
1818
|
-
```tsx
|
|
1819
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1820
|
-
|
|
1821
|
-
declare const items: { id: string; hidden: boolean }[];
|
|
1822
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
1823
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
1824
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
1825
|
-
|
|
1826
|
-
export const BadCall = () => (
|
|
1827
|
-
<LegendList data={items.filter(item => !item.hidden)} renderItem={renderRow} keyExtractor={keyOf} recycleItems />
|
|
1828
|
-
);
|
|
1829
|
-
|
|
1830
|
-
export const BadFallback = () => (
|
|
1831
|
-
<LegendList data={items ?? []} renderItem={renderRow} keyExtractor={keyOf} recycleItems />
|
|
1832
|
-
);
|
|
1833
|
-
```
|
|
1834
|
-
|
|
1835
|
-
**Passes**
|
|
1836
|
-
|
|
1837
|
-
```tsx
|
|
1838
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1839
|
-
import { useMemo } from "react";
|
|
1840
|
-
|
|
1841
|
-
declare const rawItems: { id: string; hidden: boolean }[];
|
|
1842
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
1843
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
1844
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
1845
|
-
|
|
1846
|
-
export const Good = () => {
|
|
1847
|
-
const visible = useMemo(() => rawItems.filter(item => !item.hidden), []);
|
|
1848
|
-
return <LegendList data={visible} renderItem={renderRow} keyExtractor={keyOf} recycleItems />;
|
|
1849
|
-
};
|
|
1850
|
-
```
|
|
1851
|
-
|
|
1852
|
-
#### `@ashstack/legend-list/no-inline-extra-data`
|
|
1853
|
-
|
|
1854
|
-
An inline object or array as `extraData` takes a new identity on every parent render, and every mounted row re-evaluates with it.
|
|
1855
|
-
|
|
1856
|
-
**Fails**
|
|
1857
|
-
|
|
1858
|
-
```tsx
|
|
1859
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1860
|
-
|
|
1861
|
-
declare const items: { id: string }[];
|
|
1862
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
1863
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
1864
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
1865
|
-
|
|
1866
|
-
export const Bad = () => (
|
|
1867
|
-
<LegendList
|
|
1868
|
-
data={items}
|
|
1869
|
-
renderItem={renderRow}
|
|
1870
|
-
keyExtractor={keyOf}
|
|
1871
|
-
recycleItems
|
|
1872
|
-
extraData={{ live: true }}
|
|
1873
|
-
/>
|
|
1874
|
-
);
|
|
1875
|
-
```
|
|
1876
|
-
|
|
1877
|
-
**Passes**
|
|
1878
|
-
|
|
1879
|
-
```tsx
|
|
1880
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1881
|
-
|
|
1882
|
-
declare const items: { id: string }[];
|
|
1883
|
-
declare const selectedId: string;
|
|
1884
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
1885
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
1886
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
1887
|
-
|
|
1888
|
-
export const Good = () => (
|
|
1889
|
-
<LegendList data={items} renderItem={renderRow} keyExtractor={keyOf} recycleItems extraData={selectedId} />
|
|
1890
|
-
);
|
|
1891
|
-
```
|
|
1892
|
-
|
|
1893
|
-
#### `@ashstack/legend-list/no-inline-render-item-props`
|
|
1894
|
-
|
|
1895
|
-
Disallow inline object and array literals on props nested inside `renderItem`. A row whose props take a new identity every render can never be skipped.
|
|
1896
|
-
|
|
1897
|
-
**Fails**
|
|
1898
|
-
|
|
1899
|
-
```tsx
|
|
1900
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1901
|
-
|
|
1902
|
-
declare const items: { id: string }[];
|
|
1903
|
-
declare const Row: (props: { item: { id: string }; style?: unknown }) => JSX.Element;
|
|
1904
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
1905
|
-
|
|
1906
|
-
export const Bad = () => (
|
|
1907
|
-
<LegendList
|
|
1908
|
-
data={items}
|
|
1909
|
-
renderItem={({ item }) => <Row item={item} style={{ paddingVertical: 8 }} />}
|
|
1910
|
-
keyExtractor={keyOf}
|
|
1911
|
-
recycleItems
|
|
1912
|
-
/>
|
|
1913
|
-
);
|
|
1914
|
-
```
|
|
1915
|
-
|
|
1916
|
-
**Passes**
|
|
1917
|
-
|
|
1918
|
-
```tsx
|
|
1919
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1920
|
-
|
|
1921
|
-
declare const items: { id: string }[];
|
|
1922
|
-
declare const Row: (props: { item: { id: string }; gap?: number }) => JSX.Element;
|
|
1923
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
1924
|
-
|
|
1925
|
-
export const Good = () => (
|
|
1926
|
-
<LegendList
|
|
1927
|
-
data={items}
|
|
1928
|
-
renderItem={({ item }) => <Row item={item} gap={8} />}
|
|
1929
|
-
keyExtractor={keyOf}
|
|
1930
|
-
recycleItems
|
|
1931
|
-
/>
|
|
1932
|
-
);
|
|
1933
|
-
```
|
|
1934
|
-
|
|
1935
|
-
#### `@ashstack/legend-list/no-mixed-children`
|
|
1936
|
-
|
|
1937
|
-
Disallow passing both `data` and real children to a Legend List. The combination is unsupported and one of the two is dropped without a warning.
|
|
1938
|
-
|
|
1939
|
-
**Fails**
|
|
1940
|
-
|
|
1941
|
-
```tsx
|
|
1942
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1943
|
-
|
|
1944
|
-
declare const items: { id: string }[];
|
|
1945
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
1946
|
-
declare const Footer: () => JSX.Element;
|
|
1947
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
1948
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
1949
|
-
|
|
1950
|
-
export const Bad = () => (
|
|
1951
|
-
<LegendList data={items} renderItem={renderRow} keyExtractor={keyOf} recycleItems>
|
|
1952
|
-
<Footer />
|
|
1953
|
-
</LegendList>
|
|
1954
|
-
);
|
|
1955
|
-
```
|
|
1956
|
-
|
|
1957
|
-
**Passes**
|
|
1958
|
-
|
|
1959
|
-
```tsx
|
|
1960
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1961
|
-
|
|
1962
|
-
declare const items: { id: string }[];
|
|
1963
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
1964
|
-
declare const Footer: () => JSX.Element;
|
|
1965
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
1966
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
1967
|
-
|
|
1968
|
-
// data mode
|
|
1969
|
-
export const DataMode = () => (
|
|
1970
|
-
<LegendList data={items} renderItem={renderRow} keyExtractor={keyOf} recycleItems />
|
|
1971
|
-
);
|
|
1972
|
-
|
|
1973
|
-
// children mode
|
|
1974
|
-
export const ChildrenMode = () => (
|
|
1975
|
-
<LegendList keyExtractor={keyOf} recycleItems>
|
|
1976
|
-
<Footer />
|
|
1977
|
-
</LegendList>
|
|
1978
|
-
);
|
|
1979
|
-
```
|
|
1980
|
-
|
|
1981
|
-
#### `@ashstack/legend-list/no-flex-in-content-container`
|
|
1982
|
-
|
|
1983
|
-
Disallow `flex` in a Legend List's `contentContainerStyle`, where it sizes the scrolled content to the viewport and the list ends up measuring zero height.
|
|
1984
|
-
|
|
1985
|
-
**Fails**
|
|
1986
|
-
|
|
1987
|
-
```tsx
|
|
1988
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
1989
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1990
|
-
|
|
1991
|
-
declare const items: { id: string }[];
|
|
1992
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
1993
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
1994
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
1995
|
-
|
|
1996
|
-
export const Bad = () => (
|
|
1997
|
-
<LegendList
|
|
1998
|
-
data={items}
|
|
1999
|
-
renderItem={renderRow}
|
|
2000
|
-
keyExtractor={keyOf}
|
|
2001
|
-
recycleItems
|
|
2002
|
-
contentContainerStyle={styles.stretched}
|
|
2003
|
-
/>
|
|
2004
|
-
);
|
|
2005
|
-
|
|
2006
|
-
const styles = StyleSheet.create(theme => {
|
|
2007
|
-
if (theme.dark) return { stretched: { flex: 1 } };
|
|
2008
|
-
return { stretched: { padding: theme.gap } };
|
|
2009
|
-
});
|
|
2010
|
-
```
|
|
2011
|
-
|
|
2012
|
-
**Passes**
|
|
2013
|
-
|
|
2014
|
-
```tsx
|
|
2015
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
2016
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
2017
|
-
|
|
2018
|
-
declare const items: { id: string }[];
|
|
2019
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
2020
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
2021
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
2022
|
-
|
|
2023
|
-
export const Good = () => (
|
|
2024
|
-
<LegendList
|
|
2025
|
-
data={items}
|
|
2026
|
-
renderItem={renderRow}
|
|
2027
|
-
keyExtractor={keyOf}
|
|
2028
|
-
recycleItems
|
|
2029
|
-
style={styles.fill}
|
|
2030
|
-
contentContainerStyle={styles.content}
|
|
2031
|
-
/>
|
|
2032
|
-
);
|
|
2033
|
-
|
|
2034
|
-
const styles = StyleSheet.create({
|
|
2035
|
-
fill: { flex: 1 },
|
|
2036
|
-
content: { paddingBottom: 24 },
|
|
2037
|
-
});
|
|
2038
|
-
```
|
|
2039
|
-
|
|
2040
|
-
#### `@ashstack/legend-list/typed-items-need-item-type`
|
|
2041
|
-
|
|
2042
|
-
Require `getItemType` when a row branches on `item.type`. Without it every layout shares one recycling pool and one size average.
|
|
2043
|
-
|
|
2044
|
-
**Fails**
|
|
2045
|
-
|
|
2046
|
-
```tsx
|
|
2047
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
2048
|
-
|
|
2049
|
-
declare const items: { id: string; type: "header" | "photo" }[];
|
|
2050
|
-
declare const Header: () => JSX.Element;
|
|
2051
|
-
declare const Photo: () => JSX.Element;
|
|
2052
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
2053
|
-
const renderMixed = ({ item }: { item: { type: string } }) =>
|
|
2054
|
-
item.type === "header" ? <Header /> : <Photo />;
|
|
2055
|
-
|
|
2056
|
-
export const Bad = () => (
|
|
2057
|
-
<LegendList data={items} renderItem={renderMixed} keyExtractor={keyOf} recycleItems />
|
|
2058
|
-
);
|
|
2059
|
-
```
|
|
2060
|
-
|
|
2061
|
-
**Passes**
|
|
2062
|
-
|
|
2063
|
-
```tsx
|
|
2064
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
2065
|
-
|
|
2066
|
-
declare const items: { id: string; type: "header" | "photo" }[];
|
|
2067
|
-
declare const Header: () => JSX.Element;
|
|
2068
|
-
declare const Photo: () => JSX.Element;
|
|
2069
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
2070
|
-
const renderMixed = ({ item }: { item: { type: string } }) =>
|
|
2071
|
-
item.type === "header" ? <Header /> : <Photo />;
|
|
2072
|
-
|
|
2073
|
-
export const Good = () => (
|
|
2074
|
-
<LegendList
|
|
2075
|
-
data={items}
|
|
2076
|
-
renderItem={renderMixed}
|
|
2077
|
-
keyExtractor={keyOf}
|
|
2078
|
-
recycleItems
|
|
2079
|
-
getItemType={item => item.type}
|
|
2080
|
-
/>
|
|
2081
|
-
);
|
|
2082
|
-
```
|
|
2083
|
-
|
|
2084
|
-
#### `@ashstack/legend-list/no-scrollview-map`
|
|
2085
|
-
|
|
2086
|
-
A `ScrollView` mounts every child up front, so a mapped collection does not belong in its children.
|
|
2087
|
-
|
|
2088
|
-
**Fails**
|
|
2089
|
-
|
|
2090
|
-
```tsx
|
|
2091
|
-
import { ScrollView } from "react-native";
|
|
2092
|
-
|
|
2093
|
-
declare const items: { id: string }[];
|
|
2094
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
2095
|
-
|
|
2096
|
-
export const Bad = () => (
|
|
2097
|
-
<ScrollView>
|
|
2098
|
-
{items.map(item => (
|
|
2099
|
-
<Row key={item.id} item={item} />
|
|
2100
|
-
))}
|
|
2101
|
-
</ScrollView>
|
|
2102
|
-
);
|
|
2103
|
-
```
|
|
2104
|
-
|
|
2105
|
-
**Passes**
|
|
2106
|
-
|
|
2107
|
-
```tsx
|
|
2108
|
-
import { ScrollView } from "react-native";
|
|
2109
|
-
|
|
2110
|
-
declare const Header: () => JSX.Element;
|
|
2111
|
-
declare const Footer: () => JSX.Element;
|
|
2112
|
-
|
|
2113
|
-
// A handful of static children is what a ScrollView is for.
|
|
2114
|
-
export const Good = () => (
|
|
2115
|
-
<ScrollView>
|
|
2116
|
-
<Header />
|
|
2117
|
-
<Footer />
|
|
2118
|
-
</ScrollView>
|
|
2119
|
-
);
|
|
2120
|
-
```
|
|
2121
|
-
|
|
2122
|
-
#### `@ashstack/legend-list/no-unsupported-props`
|
|
2123
|
-
|
|
2124
|
-
Disallow FlashList and FlatList props that Legend List v3 does not have. It ignores them rather than rejecting them, so the feature looks broken.
|
|
2125
|
-
|
|
2126
|
-
**Fails**
|
|
2127
|
-
|
|
2128
|
-
```tsx
|
|
2129
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
2130
|
-
|
|
2131
|
-
declare const items: { id: string }[];
|
|
2132
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
2133
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
2134
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
2135
|
-
|
|
2136
|
-
export const Bad = () => (
|
|
2137
|
-
<LegendList data={items} renderItem={renderRow} keyExtractor={keyOf} recycleItems inverted masonry />
|
|
2138
|
-
);
|
|
2139
|
-
```
|
|
2140
|
-
|
|
2141
|
-
**Passes**
|
|
2142
|
-
|
|
2143
|
-
```tsx
|
|
2144
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
2145
|
-
|
|
2146
|
-
declare const items: { id: string }[];
|
|
2147
|
-
declare const Row: (props: { item: { id: string } }) => JSX.Element;
|
|
2148
|
-
const keyOf = (item: { id: string }) => item.id;
|
|
2149
|
-
const renderRow = ({ item }: { item: { id: string } }) => <Row item={item} />;
|
|
2150
|
-
|
|
2151
|
-
export const Good = () => (
|
|
2152
|
-
<LegendList
|
|
2153
|
-
data={items}
|
|
2154
|
-
renderItem={renderRow}
|
|
2155
|
-
keyExtractor={keyOf}
|
|
2156
|
-
recycleItems
|
|
2157
|
-
maintainScrollAtEnd
|
|
2158
|
-
initialScrollAtEnd
|
|
2159
|
-
/>
|
|
2160
|
-
);
|
|
2161
|
-
```
|
|
2162
|
-
|
|
2163
|
-
### `@ashstack/legend-state`
|
|
2164
|
-
|
|
2165
|
-
_auto-enabled when `@legendapp/state` is a dependency._
|
|
2166
|
-
|
|
2167
|
-
**Import bans that ship with this module**
|
|
2168
|
-
|
|
2169
|
-
- `use$`, `useSelector` from `@legendapp/state/react`
|
|
2170
|
-
|
|
2171
|
-
#### `@ashstack/legend-state/no-assignment`
|
|
2172
|
-
|
|
2173
|
-
Write an observable with `.set(...)` or `.assign({...})`. Assigning or incrementing it with an operator is a silent no-op.
|
|
2174
|
-
|
|
2175
|
-
**Fails**
|
|
2176
|
-
|
|
2177
|
-
```tsx
|
|
2178
|
-
import { observable } from "@legendapp/state";
|
|
2179
|
-
|
|
2180
|
-
const settings$ = observable({ theme: "light", count: 0 });
|
|
2181
|
-
|
|
2182
|
-
export const applyDark = () => {
|
|
2183
|
-
settings$.theme = "dark";
|
|
2184
|
-
};
|
|
2185
|
-
|
|
2186
|
-
export const bump = () => {
|
|
2187
|
-
settings$.count++;
|
|
2188
|
-
};
|
|
2189
|
-
```
|
|
2190
|
-
|
|
2191
|
-
**Passes**
|
|
2192
|
-
|
|
2193
|
-
```tsx
|
|
2194
|
-
import { observable } from "@legendapp/state";
|
|
2195
|
-
|
|
2196
|
-
const settings$ = observable({ theme: "light", count: 0 });
|
|
2197
|
-
|
|
2198
|
-
export const applyDark = () => {
|
|
2199
|
-
settings$.theme.set("dark");
|
|
2200
|
-
};
|
|
2201
|
-
|
|
2202
|
-
export const bump = () => {
|
|
2203
|
-
settings$.count.set(value => value + 1);
|
|
2204
|
-
};
|
|
2205
|
-
```
|
|
2206
|
-
|
|
2207
|
-
#### `@ashstack/legend-state/naming`
|
|
2208
|
-
|
|
2209
|
-
A variable initialized from `observable()` or `useObservable()` needs a trailing `$`. The other rules in this module key off that suffix.
|
|
2210
|
-
|
|
2211
|
-
**Fails**
|
|
2212
|
-
|
|
2213
|
-
```tsx
|
|
2214
|
-
import { observable } from "@legendapp/state";
|
|
2215
|
-
|
|
2216
|
-
export const counter = observable(0);
|
|
2217
|
-
```
|
|
2218
|
-
|
|
2219
|
-
**Passes**
|
|
2220
|
-
|
|
2221
|
-
```tsx
|
|
2222
|
-
import { observable } from "@legendapp/state";
|
|
2223
|
-
|
|
2224
|
-
export const counter$ = observable(0);
|
|
2225
|
-
```
|
|
2226
|
-
|
|
2227
|
-
#### `@ashstack/legend-state/no-nested-observable`
|
|
2228
|
-
|
|
2229
|
-
Never pass an observable to `observable()` or `useObservable()`. The wrapper is a second node, and reads and writes on it never reach the original.
|
|
2230
|
-
|
|
2231
|
-
**Fails**
|
|
2232
|
-
|
|
2233
|
-
```tsx
|
|
2234
|
-
import { observable } from "@legendapp/state";
|
|
2235
|
-
|
|
2236
|
-
export const count$ = observable(0);
|
|
2237
|
-
|
|
2238
|
-
export const wrapped$ = observable(count$);
|
|
2239
|
-
```
|
|
2240
|
-
|
|
2241
|
-
**Passes**
|
|
2242
|
-
|
|
2243
|
-
```tsx
|
|
2244
|
-
import { observable } from "@legendapp/state";
|
|
2245
|
-
|
|
2246
|
-
export const count$ = observable(0);
|
|
2247
|
-
|
|
2248
|
-
export const doubled$ = observable(() => count$.get() * 2);
|
|
2249
|
-
```
|
|
2250
|
-
|
|
2251
|
-
#### `@ashstack/legend-state/no-react-mirror`
|
|
2252
|
-
|
|
2253
|
-
Seeding `useState` from an observable's `get()` or `peek()` gives the value two owners. Read it with `useValue(...)` where it renders.
|
|
2254
|
-
|
|
2255
|
-
**Fails**
|
|
2256
|
-
|
|
2257
|
-
```tsx
|
|
2258
|
-
import { observable } from "@legendapp/state";
|
|
2259
|
-
import { useState } from "react";
|
|
2260
|
-
|
|
2261
|
-
const count$ = observable(0);
|
|
2262
|
-
|
|
2263
|
-
export const useCounter = () => {
|
|
2264
|
-
const [count] = useState(count$.get());
|
|
2265
|
-
return count;
|
|
2266
|
-
};
|
|
2267
|
-
```
|
|
2268
|
-
|
|
2269
|
-
**Passes**
|
|
2270
|
-
|
|
2271
|
-
```tsx
|
|
2272
|
-
import { observable } from "@legendapp/state";
|
|
2273
|
-
import { useValue } from "@legendapp/state/react";
|
|
2274
|
-
|
|
2275
|
-
const count$ = observable(0);
|
|
2276
|
-
|
|
2277
|
-
export const useCounter = () => useValue(count$);
|
|
2278
|
-
```
|
|
2279
|
-
|
|
2280
|
-
#### `@ashstack/legend-state/no-untracked-get-in-jsx`
|
|
2281
|
-
|
|
2282
|
-
A `get()` placed directly in a JSX expression container is a plain read. The value renders once and never updates.
|
|
2283
|
-
|
|
2284
|
-
**Fails**
|
|
2285
|
-
|
|
2286
|
-
```tsx
|
|
2287
|
-
import { observable } from "@legendapp/state";
|
|
2288
|
-
import { View } from "react-native";
|
|
2289
|
-
|
|
2290
|
-
const count$ = observable(0);
|
|
2291
|
-
|
|
2292
|
-
export const Counter = () => <View accessibilityValue={{ now: count$.get() }} />;
|
|
2293
|
-
```
|
|
2294
|
-
|
|
2295
|
-
**Passes**
|
|
2296
|
-
|
|
2297
|
-
```tsx
|
|
2298
|
-
import { observable } from "@legendapp/state";
|
|
2299
|
-
import { Memo } from "@legendapp/state/react";
|
|
2300
|
-
import { View } from "react-native";
|
|
2301
|
-
|
|
2302
|
-
const count$ = observable(0);
|
|
2303
|
-
|
|
2304
|
-
export const Counter = () => (
|
|
2305
|
-
<View>
|
|
2306
|
-
<Memo>{() => count$.get()}</Memo>
|
|
2307
|
-
</View>
|
|
2308
|
-
);
|
|
2309
|
-
```
|
|
2310
|
-
|
|
2311
|
-
#### `@ashstack/legend-state/no-peek-in-selector`
|
|
2312
|
-
|
|
2313
|
-
`peek()` never subscribes, so a selector or tracking callback that uses it never re-runs. Call `get()` there and keep `peek()` for handlers.
|
|
2314
|
-
|
|
2315
|
-
**Fails**
|
|
2316
|
-
|
|
2317
|
-
```tsx
|
|
2318
|
-
import { observable } from "@legendapp/state";
|
|
2319
|
-
import { useValue } from "@legendapp/state/react";
|
|
2320
|
-
|
|
2321
|
-
const settings$ = observable({ theme: "light" });
|
|
2322
|
-
|
|
2323
|
-
export const useTheme = () => useValue(() => settings$.theme.peek());
|
|
2324
|
-
```
|
|
2325
|
-
|
|
2326
|
-
**Passes**
|
|
2327
|
-
|
|
2328
|
-
```tsx
|
|
2329
|
-
import { observable } from "@legendapp/state";
|
|
2330
|
-
import { useValue } from "@legendapp/state/react";
|
|
2331
|
-
|
|
2332
|
-
const settings$ = observable({ theme: "light" });
|
|
2333
|
-
|
|
2334
|
-
export const useTheme = () => useValue(() => settings$.theme.get());
|
|
2335
|
-
|
|
2336
|
-
export const readThemeOnce = () => settings$.theme.peek();
|
|
2337
|
-
```
|
|
2338
|
-
|
|
2339
|
-
#### `@ashstack/legend-state/no-object-selector`
|
|
2340
|
-
|
|
2341
|
-
A `useValue` selector that builds a new object or array returns a fresh identity every run. The component then re-renders on every store change.
|
|
2342
|
-
|
|
2343
|
-
**Fails**
|
|
2344
|
-
|
|
2345
|
-
```tsx
|
|
2346
|
-
import { observable } from "@legendapp/state";
|
|
2347
|
-
import { useValue } from "@legendapp/state/react";
|
|
2348
|
-
|
|
2349
|
-
const settings$ = observable({ theme: "light", locale: "en" });
|
|
2350
|
-
|
|
2351
|
-
export const useSettings = () =>
|
|
2352
|
-
useValue(() => ({ theme: settings$.theme.get(), locale: settings$.locale.get() }));
|
|
2353
|
-
```
|
|
2354
|
-
|
|
2355
|
-
**Passes**
|
|
2356
|
-
|
|
2357
|
-
```tsx
|
|
2358
|
-
import { observable } from "@legendapp/state";
|
|
2359
|
-
import { useValue } from "@legendapp/state/react";
|
|
2360
|
-
|
|
2361
|
-
const settings$ = observable({ theme: "light", locale: "en" });
|
|
2362
|
-
|
|
2363
|
-
export const useTheme = () => useValue(() => settings$.theme.get());
|
|
2364
|
-
|
|
2365
|
-
export const useLocale = () => useValue(() => settings$.locale.get());
|
|
2366
|
-
```
|
|
2367
|
-
|
|
2368
|
-
#### `@ashstack/legend-state/no-observable-in-component`
|
|
2369
|
-
|
|
2370
|
-
Calling `observable()` inside a component or hook makes a new observable on every render. Use `useObservable()` or a module-level store instead.
|
|
2371
|
-
|
|
2372
|
-
**Fails**
|
|
2373
|
-
|
|
2374
|
-
```tsx
|
|
2375
|
-
import { observable } from "@legendapp/state";
|
|
2376
|
-
import { View } from "react-native";
|
|
2377
|
-
|
|
2378
|
-
export const Counter = () => {
|
|
2379
|
-
const count$ = observable(0);
|
|
2380
|
-
return <View accessible={count$ !== undefined} />;
|
|
2381
|
-
};
|
|
2382
|
-
```
|
|
2383
|
-
|
|
2384
|
-
**Passes**
|
|
2385
|
-
|
|
2386
|
-
```tsx
|
|
2387
|
-
import { useObservable } from "@legendapp/state/react";
|
|
2388
|
-
import { View } from "react-native";
|
|
2389
|
-
|
|
2390
|
-
export const Counter = () => {
|
|
2391
|
-
const count$ = useObservable(0);
|
|
2392
|
-
return <View accessible={count$ !== undefined} />;
|
|
2393
|
-
};
|
|
2394
|
-
```
|
|
2395
|
-
|
|
2396
|
-
### `@ashstack/reanimated`
|
|
2397
|
-
|
|
2398
|
-
_auto-enabled when `react-native-reanimated` is a dependency._
|
|
2399
|
-
|
|
2400
|
-
**Import bans that ship with this module**
|
|
2401
|
-
|
|
2402
|
-
- `Animated` from `react-native`
|
|
2403
|
-
- `runOnJS`, `runOnUI` from `react-native-reanimated`
|
|
2404
|
-
- `runOnJS`, `runOnUI` from `react-native-worklets`
|
|
2405
|
-
|
|
2406
|
-
#### `@ashstack/reanimated/animated-reaction-safety`
|
|
2407
|
-
|
|
2408
|
-
A `useAnimatedReaction` result callback loops forever if it writes a shared value the prepare callback reads. Guard `scheduleOnRN` there on the current result differing from the previous one.
|
|
2409
|
-
|
|
2410
|
-
**Fails**
|
|
2411
|
-
|
|
2412
|
-
```tsx
|
|
2413
|
-
import { useAnimatedReaction, useSharedValue } from "react-native-reanimated";
|
|
2414
|
-
import { scheduleOnRN } from "react-native-worklets";
|
|
2415
|
-
|
|
2416
|
-
declare function report(value: number): void;
|
|
2417
|
-
|
|
2418
|
-
export function BadReaction() {
|
|
2419
|
-
const progress = useSharedValue(0);
|
|
2420
|
-
|
|
2421
|
-
useAnimatedReaction(
|
|
2422
|
-
() => progress.get(),
|
|
2423
|
-
current => {
|
|
2424
|
-
progress.set(current + 1);
|
|
2425
|
-
scheduleOnRN(report, current);
|
|
2426
|
-
}
|
|
2427
|
-
);
|
|
2428
|
-
|
|
2429
|
-
return null;
|
|
2430
|
-
}
|
|
2431
|
-
```
|
|
2432
|
-
|
|
2433
|
-
**Passes**
|
|
2434
|
-
|
|
2435
|
-
```tsx
|
|
2436
|
-
import { useAnimatedReaction, useSharedValue } from "react-native-reanimated";
|
|
2437
|
-
import { scheduleOnRN } from "react-native-worklets";
|
|
2438
|
-
|
|
2439
|
-
declare function report(value: number): void;
|
|
2440
|
-
|
|
2441
|
-
export function GoodReaction() {
|
|
2442
|
-
const progress = useSharedValue(0);
|
|
2443
|
-
const mirror = useSharedValue(0);
|
|
2444
|
-
|
|
2445
|
-
useAnimatedReaction(
|
|
2446
|
-
() => progress.get() > 0.5,
|
|
2447
|
-
(current, previous) => {
|
|
2448
|
-
if (current !== previous) {
|
|
2449
|
-
mirror.set(current ? 1 : 0);
|
|
2450
|
-
scheduleOnRN(report, current ? 1 : 0);
|
|
2451
|
-
}
|
|
2452
|
-
}
|
|
2453
|
-
);
|
|
2454
|
-
|
|
2455
|
-
return null;
|
|
2456
|
-
}
|
|
2457
|
-
```
|
|
2458
|
-
|
|
2459
|
-
#### `@ashstack/reanimated/animated-style-needs-animated-component`
|
|
2460
|
-
|
|
2461
|
-
An animated style only takes effect on an `Animated.*` component. A plain element applies it once at mount and then never updates.
|
|
2462
|
-
|
|
2463
|
-
**Fails**
|
|
2464
|
-
|
|
2465
|
-
```tsx
|
|
2466
|
-
import { View } from "react-native";
|
|
2467
|
-
import { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
2468
|
-
|
|
2469
|
-
export function BadAnimatedStyleHost() {
|
|
2470
|
-
const progress = useSharedValue(0);
|
|
2471
|
-
const fade = useAnimatedStyle(() => ({ opacity: progress.get() }));
|
|
2472
|
-
return <View style={fade} />;
|
|
2473
|
-
}
|
|
2474
|
-
```
|
|
2475
|
-
|
|
2476
|
-
**Passes**
|
|
2477
|
-
|
|
2478
|
-
```tsx
|
|
2479
|
-
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
2480
|
-
|
|
2481
|
-
export function GoodAnimatedStyleHost() {
|
|
2482
|
-
const progress = useSharedValue(0);
|
|
2483
|
-
const fade = useAnimatedStyle(() => ({ opacity: progress.get() }));
|
|
2484
|
-
return <Animated.View style={fade} />;
|
|
2485
|
-
}
|
|
2486
|
-
```
|
|
2487
|
-
|
|
2488
|
-
#### `@ashstack/reanimated/animated-updater-purity`
|
|
2489
|
-
|
|
2490
|
-
An updater passed to `useAnimatedStyle` or `useAnimatedProps` must stay pure, so it may not write a shared value or call `scheduleOnRN`.
|
|
2491
|
-
|
|
2492
|
-
**Fails**
|
|
2493
|
-
|
|
2494
|
-
```tsx
|
|
2495
|
-
import { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
2496
|
-
import { scheduleOnRN } from "react-native-worklets";
|
|
2497
|
-
|
|
2498
|
-
declare function report(value: number): void;
|
|
2499
|
-
|
|
2500
|
-
export function useBadUpdater() {
|
|
2501
|
-
const progress = useSharedValue(0);
|
|
2502
|
-
|
|
2503
|
-
return useAnimatedStyle(() => {
|
|
2504
|
-
progress.set(progress.get() + 1);
|
|
2505
|
-
scheduleOnRN(report, progress.get());
|
|
2506
|
-
return { opacity: progress.get() };
|
|
2507
|
-
});
|
|
2508
|
-
}
|
|
2509
|
-
```
|
|
2510
|
-
|
|
2511
|
-
**Passes**
|
|
2512
|
-
|
|
2513
|
-
```tsx
|
|
2514
|
-
import { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
2515
|
-
|
|
2516
|
-
export function useGoodUpdater() {
|
|
2517
|
-
const progress = useSharedValue(0);
|
|
2518
|
-
return useAnimatedStyle(() => ({ opacity: progress.get() }));
|
|
2519
|
-
}
|
|
2520
|
-
```
|
|
2521
|
-
|
|
2522
|
-
#### `@ashstack/reanimated/gpu-properties-only`
|
|
2523
|
-
|
|
2524
|
-
Animate `transform` and `opacity` in `useAnimatedStyle` and `useAnimatedProps`. Layout properties such as `width` or `margin` recalculate layout every frame.
|
|
2525
|
-
|
|
2526
|
-
**Fails**
|
|
2527
|
-
|
|
2528
|
-
```tsx
|
|
2529
|
-
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
2530
|
-
|
|
2531
|
-
export function BadGpu() {
|
|
2532
|
-
const progress = useSharedValue(0);
|
|
2533
|
-
const grow = useAnimatedStyle(() => ({ height: progress.get() * 100 }));
|
|
2534
|
-
return <Animated.View style={grow} />;
|
|
2535
|
-
}
|
|
2536
|
-
```
|
|
2537
|
-
|
|
2538
|
-
**Passes**
|
|
2539
|
-
|
|
2540
|
-
```tsx
|
|
2541
|
-
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
2542
|
-
|
|
2543
|
-
export function GoodGpu() {
|
|
2544
|
-
const progress = useSharedValue(0);
|
|
2545
|
-
const slide = useAnimatedStyle(() => ({
|
|
2546
|
-
opacity: progress.get(),
|
|
2547
|
-
transform: [{ translateY: progress.get() * 10 }],
|
|
2548
|
-
}));
|
|
2549
|
-
return <Animated.View style={slide} />;
|
|
2550
|
-
}
|
|
2551
|
-
```
|
|
2552
|
-
|
|
2553
|
-
#### `@ashstack/reanimated/hoist-layout-animation-builder`
|
|
2554
|
-
|
|
2555
|
-
A layout animation belongs at module scope, or inside a memo when it depends on component values. The `entering`/`exiting`/`layout` props otherwise rebuild it on every render.
|
|
2556
|
-
|
|
2557
|
-
**Fails**
|
|
2558
|
-
|
|
2559
|
-
```tsx
|
|
2560
|
-
import Animated, { FadeIn } from "react-native-reanimated";
|
|
2561
|
-
|
|
2562
|
-
export function BadLayoutBuilder() {
|
|
2563
|
-
return <Animated.View entering={FadeIn.duration(250)} />;
|
|
2564
|
-
}
|
|
2565
|
-
```
|
|
2566
|
-
|
|
2567
|
-
**Passes**
|
|
2568
|
-
|
|
2569
|
-
```tsx
|
|
2570
|
-
import Animated, { FadeIn } from "react-native-reanimated";
|
|
2571
|
-
|
|
2572
|
-
const FADE_IN = FadeIn.duration(250);
|
|
2573
|
-
|
|
2574
|
-
export function GoodLayoutBuilder() {
|
|
2575
|
-
return <Animated.View entering={FADE_IN} />;
|
|
2576
|
-
}
|
|
2577
|
-
```
|
|
2578
|
-
|
|
2579
|
-
#### `@ashstack/reanimated/interpolate-needs-clamp`
|
|
2580
|
-
|
|
2581
|
-
Give `interpolate()` a fourth argument that clamps. Without one, or with `EXTEND` or `IDENTITY`, the output keeps going past the ends of the input range.
|
|
2582
|
-
|
|
2583
|
-
**Fails**
|
|
2584
|
-
|
|
2585
|
-
```tsx
|
|
2586
|
-
import Animated, { interpolate, useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
2587
|
-
|
|
2588
|
-
export function BadInterpolate() {
|
|
2589
|
-
const offset = useSharedValue(0);
|
|
2590
|
-
const fade = useAnimatedStyle(() => ({ opacity: interpolate(offset.get(), [0, 100], [1, 0]) }));
|
|
2591
|
-
return <Animated.View style={fade} />;
|
|
2592
|
-
}
|
|
2593
|
-
```
|
|
2594
|
-
|
|
2595
|
-
**Passes**
|
|
2596
|
-
|
|
2597
|
-
```tsx
|
|
2598
|
-
import Animated, { Extrapolation, interpolate, useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
2599
|
-
|
|
2600
|
-
export function GoodInterpolate() {
|
|
2601
|
-
const offset = useSharedValue(0);
|
|
2602
|
-
const fade = useAnimatedStyle(() => ({
|
|
2603
|
-
opacity: interpolate(offset.get(), [0, 100], [1, 0], Extrapolation.CLAMP),
|
|
2604
|
-
}));
|
|
2605
|
-
return <Animated.View style={fade} />;
|
|
2606
|
-
}
|
|
2607
|
-
```
|
|
2608
|
-
|
|
2609
|
-
#### `@ashstack/reanimated/no-shared-value-dot-value`
|
|
2610
|
-
|
|
2611
|
-
Shared values are read with `.get()` and written with `.set(...)`, never through `.value`. React Compiler cannot track a `.value` access.
|
|
2612
|
-
|
|
2613
|
-
**Fails**
|
|
2614
|
-
|
|
2615
|
-
```tsx
|
|
2616
|
-
import { useSharedValue } from "react-native-reanimated";
|
|
2617
|
-
|
|
2618
|
-
export function useBadDotValue() {
|
|
2619
|
-
const progress = useSharedValue(0);
|
|
2620
|
-
progress.value = 1;
|
|
2621
|
-
return progress.value;
|
|
2622
|
-
}
|
|
2623
|
-
```
|
|
2624
|
-
|
|
2625
|
-
**Passes**
|
|
2626
|
-
|
|
2627
|
-
```tsx
|
|
2628
|
-
import { useSharedValue } from "react-native-reanimated";
|
|
2629
|
-
|
|
2630
|
-
export function useGoodDotValue() {
|
|
2631
|
-
const progress = useSharedValue(0);
|
|
2632
|
-
progress.set(1);
|
|
2633
|
-
return progress.get();
|
|
2634
|
-
}
|
|
2635
|
-
```
|
|
2636
|
-
|
|
2637
|
-
#### `@ashstack/reanimated/no-react-state-from-continuous-worklet`
|
|
2638
|
-
|
|
2639
|
-
A worklet that runs every frame must not send a React state setter through `scheduleOnRN`. That puts a Fabric commit on an animation frame.
|
|
2640
|
-
|
|
2641
|
-
**Fails**
|
|
2642
|
-
|
|
2643
|
-
```tsx
|
|
2644
|
-
import { useState } from "react";
|
|
2645
|
-
import { useFrameCallback } from "react-native-reanimated";
|
|
2646
|
-
import { scheduleOnRN } from "react-native-worklets";
|
|
2647
|
-
|
|
2648
|
-
export function BadWorkletState() {
|
|
2649
|
-
const [, setVisible] = useState(false);
|
|
2650
|
-
|
|
2651
|
-
useFrameCallback(() => {
|
|
2652
|
-
scheduleOnRN(setVisible, true);
|
|
2653
|
-
});
|
|
2654
|
-
|
|
2655
|
-
return null;
|
|
2656
|
-
}
|
|
2657
|
-
```
|
|
2658
|
-
|
|
2659
|
-
**Passes**
|
|
2660
|
-
|
|
2661
|
-
```tsx
|
|
2662
|
-
import { useFrameCallback, useSharedValue } from "react-native-reanimated";
|
|
2663
|
-
|
|
2664
|
-
export function GoodWorkletState() {
|
|
2665
|
-
const visible = useSharedValue(false);
|
|
2666
|
-
|
|
2667
|
-
useFrameCallback(() => {
|
|
2668
|
-
visible.set(true);
|
|
2669
|
-
});
|
|
2670
|
-
|
|
2671
|
-
return null;
|
|
2672
|
-
}
|
|
2673
|
-
```
|
|
2674
|
-
|
|
2675
|
-
#### `@ashstack/reanimated/prefer-lazy-shared-value-initializer`
|
|
2676
|
-
|
|
2677
|
-
Pass a computed `useSharedValue` initial value as a function. An eager call runs on every render while only the first result is kept.
|
|
2678
|
-
|
|
2679
|
-
**Fails**
|
|
2680
|
-
|
|
2681
|
-
```tsx
|
|
2682
|
-
import { useSharedValue } from "react-native-reanimated";
|
|
2683
|
-
|
|
2684
|
-
declare function computeInitialProgress(): number;
|
|
2685
|
-
|
|
2686
|
-
export function useBadInitializer() {
|
|
2687
|
-
return useSharedValue(computeInitialProgress());
|
|
2688
|
-
}
|
|
2689
|
-
```
|
|
2690
|
-
|
|
2691
|
-
**Passes**
|
|
2692
|
-
|
|
2693
|
-
```tsx
|
|
2694
|
-
import { useSharedValue } from "react-native-reanimated";
|
|
2695
|
-
|
|
2696
|
-
declare function computeInitialProgress(): number;
|
|
2697
|
-
|
|
2698
|
-
export function useGoodInitializer() {
|
|
2699
|
-
return useSharedValue(() => computeInitialProgress());
|
|
2700
|
-
}
|
|
2701
|
-
```
|
|
2702
|
-
|
|
2703
|
-
#### `@ashstack/reanimated/schedule-on-rn-scope`
|
|
2704
|
-
|
|
2705
|
-
`scheduleOnRN` takes a function declared in RN Runtime scope. An inline callback can end up created on the wrong runtime.
|
|
2706
|
-
|
|
2707
|
-
**Fails**
|
|
2708
|
-
|
|
2709
|
-
```tsx
|
|
2710
|
-
import { useFrameCallback } from "react-native-reanimated";
|
|
2711
|
-
import { scheduleOnRN } from "react-native-worklets";
|
|
2712
|
-
|
|
2713
|
-
declare function report(value: number): void;
|
|
2714
|
-
|
|
2715
|
-
export function useBadSchedule() {
|
|
2716
|
-
useFrameCallback(() => {
|
|
2717
|
-
scheduleOnRN(() => report(1));
|
|
2718
|
-
});
|
|
2719
|
-
}
|
|
2720
|
-
```
|
|
2721
|
-
|
|
2722
|
-
**Passes**
|
|
2723
|
-
|
|
2724
|
-
```tsx
|
|
2725
|
-
import { useFrameCallback } from "react-native-reanimated";
|
|
2726
|
-
import { scheduleOnRN } from "react-native-worklets";
|
|
2727
|
-
|
|
2728
|
-
declare function report(value: number): void;
|
|
2729
|
-
|
|
2730
|
-
export function useGoodSchedule() {
|
|
2731
|
-
useFrameCallback(() => {
|
|
2732
|
-
scheduleOnRN(report, 1);
|
|
2733
|
-
});
|
|
2734
|
-
}
|
|
2735
|
-
```
|
|
2736
|
-
|
|
2737
|
-
#### `@ashstack/reanimated/shared-value-usage`
|
|
2738
|
-
|
|
2739
|
-
Destructuring a shared value or mutating what its `get()` returned detaches it from Reanimated reactivity. Reading or writing one while JSX evaluates also makes render impure.
|
|
2740
|
-
|
|
2741
|
-
**Fails**
|
|
2742
|
-
|
|
2743
|
-
```tsx
|
|
2744
|
-
import { Text } from "react-native";
|
|
2745
|
-
import { useSharedValue } from "react-native-reanimated";
|
|
2746
|
-
|
|
2747
|
-
export function BadSharedValueUsage() {
|
|
2748
|
-
const progress = useSharedValue(0);
|
|
2749
|
-
return <Text>{progress.get()}</Text>;
|
|
2750
|
-
}
|
|
2751
|
-
```
|
|
2752
|
-
|
|
2753
|
-
**Passes**
|
|
2754
|
-
|
|
2755
|
-
```tsx
|
|
2756
|
-
import { Text } from "react-native";
|
|
2757
|
-
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
2758
|
-
|
|
2759
|
-
export function GoodSharedValueUsage() {
|
|
2760
|
-
const progress = useSharedValue(0);
|
|
2761
|
-
const fade = useAnimatedStyle(() => ({ opacity: progress.get() }));
|
|
2762
|
-
return (
|
|
2763
|
-
<Animated.View style={fade}>
|
|
2764
|
-
<Text>Loading</Text>
|
|
2765
|
-
</Animated.View>
|
|
2766
|
-
);
|
|
2767
|
-
}
|
|
2768
|
-
```
|
|
2769
|
-
|
|
2770
|
-
### `@ashstack/turbo-image`
|
|
2771
|
-
|
|
2772
|
-
_auto-enabled when `react-native-turbo-image` is a dependency._
|
|
2773
|
-
|
|
2774
|
-
#### `@ashstack/turbo-image/require-resize`
|
|
2775
|
-
|
|
2776
|
-
Require `resize` on a TurboImage so the native decoder downsamples before the bitmap reaches memory. A full-resolution decode wastes tens of megabytes and stalls the first frame.
|
|
2777
|
-
|
|
2778
|
-
**Fails**
|
|
2779
|
-
|
|
2780
|
-
```tsx
|
|
2781
|
-
import { TurboImage } from "react-native-turbo-image";
|
|
2782
|
-
|
|
2783
|
-
export function BadTurboResize({ uri }: { uri: string }) {
|
|
2784
|
-
return <TurboImage source={{ uri }} cachePolicy="dataCache" />;
|
|
2785
|
-
}
|
|
2786
|
-
```
|
|
2787
|
-
|
|
2788
|
-
**Passes**
|
|
2789
|
-
|
|
2790
|
-
```tsx
|
|
2791
|
-
import { TurboImage } from "react-native-turbo-image";
|
|
2792
|
-
|
|
2793
|
-
export function GoodTurboResize({ uri }: { uri: string }) {
|
|
2794
|
-
return <TurboImage source={{ uri }} resize={40} cachePolicy="dataCache" />;
|
|
2795
|
-
}
|
|
2796
|
-
```
|
|
2797
|
-
|
|
2798
|
-
#### `@ashstack/turbo-image/require-cache-policy`
|
|
2799
|
-
|
|
2800
|
-
Require `cachePolicy` on a TurboImage. Without one the image is re-fetched over the network on every cold start, so an already-scrolled feed costs its bandwidth again.
|
|
2801
|
-
|
|
2802
|
-
**Fails**
|
|
2803
|
-
|
|
2804
|
-
```tsx
|
|
2805
|
-
import { TurboImage } from "react-native-turbo-image";
|
|
2806
|
-
|
|
2807
|
-
export function BadTurboCache({ uri }: { uri: string }) {
|
|
2808
|
-
return <TurboImage source={{ uri }} resize={40} />;
|
|
2809
|
-
}
|
|
2810
|
-
```
|
|
2811
|
-
|
|
2812
|
-
**Passes**
|
|
2813
|
-
|
|
2814
|
-
```tsx
|
|
2815
|
-
import { TurboImage } from "react-native-turbo-image";
|
|
2816
|
-
|
|
2817
|
-
export function GoodTurboCache({ uri }: { uri: string }) {
|
|
2818
|
-
return <TurboImage source={{ uri }} resize={40} cachePolicy="dataCache" />;
|
|
2819
|
-
}
|
|
2820
|
-
```
|
|
2821
|
-
|
|
2822
|
-
### `@ashstack/skia`
|
|
2823
|
-
|
|
2824
|
-
_auto-enabled when `@shopify/react-native-skia` is a dependency._
|
|
2825
|
-
|
|
2826
|
-
**Import bans that ship with this module**
|
|
2827
|
-
|
|
2828
|
-
- `usePathValue`, `usePathInterpolation` from `@shopify/react-native-skia`
|
|
2829
|
-
|
|
2830
|
-
#### `@ashstack/skia/canvas-opaque`
|
|
2831
|
-
|
|
2832
|
-
Require an explicit `opaque` prop on a Skia `<Canvas>`. A fullscreen animated canvas wants it on; anything that needs transparency or a view transform wants it off.
|
|
2833
|
-
|
|
2834
|
-
**Fails**
|
|
2835
|
-
|
|
2836
|
-
```tsx
|
|
2837
|
-
import { Canvas } from "@shopify/react-native-skia";
|
|
2838
|
-
|
|
2839
|
-
export function BadCanvas() {
|
|
2840
|
-
return <Canvas />;
|
|
2841
|
-
}
|
|
2842
|
-
```
|
|
2843
|
-
|
|
2844
|
-
**Passes**
|
|
2845
|
-
|
|
2846
|
-
```tsx
|
|
2847
|
-
import { Canvas } from "@shopify/react-native-skia";
|
|
2848
|
-
import { Platform } from "react-native";
|
|
2849
|
-
|
|
2850
|
-
export function GoodCanvas() {
|
|
2851
|
-
return <Canvas opaque={Platform.OS === "android"} />;
|
|
2852
|
-
}
|
|
2853
|
-
```
|
|
2854
|
-
|
|
2855
|
-
### `@ashstack/keyboard`
|
|
2856
|
-
|
|
2857
|
-
_auto-enabled when `react-native-keyboard-controller` is a dependency._
|
|
2858
|
-
|
|
2859
|
-
**Import bans that ship with this module**
|
|
2860
|
-
|
|
2861
|
-
- `KeyboardAvoidingView` from `react-native`
|
|
2862
|
-
|
|
2863
|
-
### Import bans without a module
|
|
2864
|
-
|
|
2865
|
-
_Auto-detected from your dependencies; these carry no rules of their own._
|
|
2866
|
-
|
|
2867
|
-
**`react-native-gesture-handler` installed**
|
|
2868
|
-
|
|
2869
|
-
- `Pressable`, `TouchableOpacity`, `TouchableHighlight`, `TouchableWithoutFeedback`, `TouchableNativeFeedback` from `react-native`
|
|
2870
|
-
|
|
2871
|
-
**`expo-router` installed**
|
|
2872
|
-
|
|
2873
|
-
- any import of `@react-navigation/stack`, `@react-navigation/bottom-tabs`
|
|
2874
|
-
|
|
2875
|
-
**`expo-font` installed**
|
|
2876
|
-
|
|
2877
|
-
- `useFonts`, `loadAsync` from `expo-font`
|
|
2878
|
-
|
|
2879
|
-
**`react-native-quick-crypto` installed**
|
|
2880
|
-
|
|
2881
|
-
- `*` from `crypto-js`
|