@hypersolid-dev/eslint-config 5.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +17 -1
- package/dist/base.js +14 -1
- package/dist/jest.d.ts +3 -0
- package/dist/jest.js +56 -0
- package/dist/plugins/props-destructuring.js +12 -12
- package/dist/plugins/react-native-a11y/rules/accessibility-actions-need-handler.js +2 -2
- package/dist/plugins/react-native-a11y/rules/accessibility-hint.js +2 -2
- package/dist/plugins/react-native-a11y/rules/accessibility-label.js +2 -2
- package/dist/plugins/react-native-a11y/rules/accessibility-role.js +2 -2
- package/dist/plugins/react-native-a11y/rules/accessibility-value-needs-range.js +25 -27
- package/dist/plugins/react-native-a11y/rules/adjustable-needs-value.js +16 -18
- package/dist/plugins/react-native-a11y/rules/gate-announce-for-accessibility.js +4 -4
- package/dist/plugins/react-native-a11y/rules/input-purpose.js +2 -2
- package/dist/plugins/react-native-a11y/rules/no-aria-legacy-mix.js +20 -22
- package/dist/plugins/react-native-a11y/rules/no-deprecated-accessibility-focus.js +12 -14
- package/dist/plugins/react-native-a11y/rules/no-dropped-aria-on-text.js +2 -2
- package/dist/plugins/react-native-a11y/rules/no-font-scaling-disabled.js +24 -26
- package/dist/plugins/react-native-a11y/rules/no-ineffective-accessibility-props.js +34 -36
- package/dist/plugins/react-native-a11y/rules/no-invalid-accessibility-state.js +31 -33
- package/dist/plugins/react-native-a11y/rules/no-nested-focus-stops.js +2 -2
- package/dist/plugins/react-native-a11y/rules/no-press-in-activation.js +2 -2
- package/dist/plugins/react-native-a11y/rules/no-unnamed-interactive.js +2 -2
- package/dist/plugins/react-native-a11y/rules/require-invert-colors-opt-out.js +2 -2
- package/dist/plugins/react-native-a11y/rules/require-modal-a11y-on-sheets.js +2 -2
- package/dist/plugins/react-native-a11y/utils/get-amount-of-text-children.js +15 -13
- package/dist/plugins/rules/boolean-guard.js +3 -3
- package/dist/plugins/rules/comment-style.js +43 -34
- package/dist/plugins/rules/effect-takes-callback.js +1 -1
- package/dist/plugins/rules/jsdoc-for-props.js +1 -1
- package/dist/plugins/rules/no-callback-payload.js +18 -15
- package/dist/plugins/rules/no-markup-variable.js +41 -35
- package/dist/plugins/rules/no-unnecessary-use-callback.js +2 -2
- package/dist/plugins/rules/regex-pattern-const.js +1 -1
- package/dist/plugins/rules/test-id-default.js +23 -27
- package/dist/plugins/rules/test-id-format.js +14 -18
- package/dist/plugins/rules/test-id-from-props.js +23 -27
- package/dist/plugins/rules/undefined-check.js +2 -2
- package/dist/plugins/rules/webview-allow-list.js +22 -26
- package/dist/plugins/utils/expressions.d.ts +3 -3
- package/dist/plugins/utils/expressions.js +94 -78
- package/dist/plugins/utils/types.js +14 -12
- package/dist/react-native.js +28 -0
- package/dist/shared.d.ts +3 -3
- package/dist/shared.js +168 -112
- package/package.json +12 -2
|
@@ -8,14 +8,14 @@ const DEFAULT_SELECT_OBJECTS = new Set(['Platform']);
|
|
|
8
8
|
const isWrapper = (node) => TS_WRAPPERS.has(node.type);
|
|
9
9
|
const isFunction = (node) => FUNCTIONS.has(node.type);
|
|
10
10
|
/** Through `as`, `satisfies`, `!` and `?.`. */
|
|
11
|
-
export
|
|
11
|
+
export const unwrap = (node) => {
|
|
12
12
|
let current = node ?? undefined;
|
|
13
13
|
while (current && isWrapper(current))
|
|
14
14
|
current = current.expression;
|
|
15
15
|
return current;
|
|
16
|
-
}
|
|
16
|
+
};
|
|
17
17
|
/** The name of the memo hook a callee calls (`useMemo`, `React.useMemo`), if it is one. */
|
|
18
|
-
|
|
18
|
+
const memoHookName = (callee) => {
|
|
19
19
|
if (callee.type === AST_NODE_TYPES.Identifier)
|
|
20
20
|
return MEMO_HOOKS.has(callee.name) ? callee.name : undefined;
|
|
21
21
|
if (callee.type === AST_NODE_TYPES.MemberExpression &&
|
|
@@ -26,29 +26,27 @@ function memoHookName(callee) {
|
|
|
26
26
|
return callee.property.name;
|
|
27
27
|
}
|
|
28
28
|
return undefined;
|
|
29
|
-
}
|
|
30
|
-
|
|
29
|
+
};
|
|
30
|
+
const findVariable = (scope, name) => {
|
|
31
31
|
for (let current = scope; current; current = current.upper) {
|
|
32
32
|
const variable = current.set.get(name);
|
|
33
33
|
if (variable)
|
|
34
34
|
return variable;
|
|
35
35
|
}
|
|
36
36
|
return undefined;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
40
|
-
}
|
|
37
|
+
};
|
|
38
|
+
const bindingOf = (context, identifier) => findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
41
39
|
/** The declarator a name resolves to, when it is a local with an initialiser; `.init` need not be the name's own value. */
|
|
42
|
-
|
|
40
|
+
const localDeclarator = (context, identifier) => {
|
|
43
41
|
const definition = bindingOf(context, identifier)?.defs[0];
|
|
44
42
|
return definition?.type === DefinitionType.Variable && definition.node.init ? definition.node : undefined;
|
|
45
|
-
}
|
|
43
|
+
};
|
|
46
44
|
/**
|
|
47
45
|
* A property's key when it is written literally (`a`, `"a"`, `1`, `["a"]`,
|
|
48
46
|
* `[\`a\`]`), else null: a computed key that is a variable, a call or a
|
|
49
47
|
* template with expressions, a spread and a pattern have no key to read.
|
|
50
48
|
*/
|
|
51
|
-
export
|
|
49
|
+
export const keyOf = (property) => {
|
|
52
50
|
if (property.type !== AST_NODE_TYPES.Property)
|
|
53
51
|
return null;
|
|
54
52
|
const { key } = property;
|
|
@@ -57,13 +55,13 @@ export function keyOf(property) {
|
|
|
57
55
|
if (key.type === AST_NODE_TYPES.Literal)
|
|
58
56
|
return String(key.value);
|
|
59
57
|
return key.type === AST_NODE_TYPES.TemplateLiteral && key.expressions.length === 0 ? (key.quasis[0]?.value.cooked ?? null) : null;
|
|
60
|
-
}
|
|
58
|
+
};
|
|
61
59
|
/**
|
|
62
60
|
* The expression a destructuring pattern hands to `name`, read off a literal
|
|
63
61
|
* initialiser. A non-literal value, a rest element, a computed key and a spread
|
|
64
62
|
* before the wanted element all give up, since the value is not in this file.
|
|
65
63
|
*/
|
|
66
|
-
|
|
64
|
+
const picked = (pattern, init, name) => {
|
|
67
65
|
if (pattern.type === AST_NODE_TYPES.Identifier)
|
|
68
66
|
return pattern.name === name ? (init ?? undefined) : undefined;
|
|
69
67
|
// The default only applies when the source has nothing there.
|
|
@@ -71,37 +69,44 @@ function picked(pattern, init, name) {
|
|
|
71
69
|
return picked(pattern.left, init ?? pattern.right, name);
|
|
72
70
|
const source = unwrap(init);
|
|
73
71
|
if (pattern.type === AST_NODE_TYPES.ObjectPattern && source?.type === AST_NODE_TYPES.ObjectExpression) {
|
|
74
|
-
|
|
75
|
-
const wanted = keyOf(property);
|
|
76
|
-
if (wanted === null || property.type !== AST_NODE_TYPES.Property)
|
|
77
|
-
continue;
|
|
78
|
-
const entry = source.properties.find((candidate) => keyOf(candidate) === wanted);
|
|
79
|
-
const found = picked(property.value, entry?.type === AST_NODE_TYPES.Property ? entry.value : undefined, name);
|
|
80
|
-
if (found)
|
|
81
|
-
return found;
|
|
82
|
-
}
|
|
83
|
-
return undefined;
|
|
72
|
+
return pickedFromObject(pattern, source, name);
|
|
84
73
|
}
|
|
85
74
|
if (pattern.type === AST_NODE_TYPES.ArrayPattern && source?.type === AST_NODE_TYPES.ArrayExpression) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
75
|
+
return pickedFromArray(pattern, source, name);
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
};
|
|
79
|
+
const pickedFromObject = (pattern, source, name) => {
|
|
80
|
+
for (const property of pattern.properties) {
|
|
81
|
+
const wanted = keyOf(property);
|
|
82
|
+
if (wanted === null || property.type !== AST_NODE_TYPES.Property)
|
|
83
|
+
continue;
|
|
84
|
+
const entry = source.properties.find((candidate) => keyOf(candidate) === wanted);
|
|
85
|
+
const found = picked(property.value, entry?.type === AST_NODE_TYPES.Property ? entry.value : undefined, name);
|
|
86
|
+
if (found)
|
|
87
|
+
return found;
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
};
|
|
91
|
+
const pickedFromArray = (pattern, source, name) => {
|
|
92
|
+
for (const [index, element] of pattern.elements.entries()) {
|
|
93
|
+
if (!element)
|
|
94
|
+
continue;
|
|
95
|
+
const before = source.elements.slice(0, index + 1);
|
|
96
|
+
if (before.some((entry) => entry?.type === AST_NODE_TYPES.SpreadElement))
|
|
97
|
+
continue;
|
|
98
|
+
const found = picked(element, source.elements[index], name);
|
|
99
|
+
if (found)
|
|
100
|
+
return found;
|
|
96
101
|
}
|
|
97
102
|
return undefined;
|
|
98
|
-
}
|
|
103
|
+
};
|
|
99
104
|
/**
|
|
100
105
|
* What a name stands for: a `const`'s own value, or a declared function itself.
|
|
101
106
|
* A `let` or `var` is not followed, and a destructured `const` resolves only
|
|
102
107
|
* when its initialiser is a literal holding the matching entry.
|
|
103
108
|
*/
|
|
104
|
-
|
|
109
|
+
const boundTo = (context, identifier) => {
|
|
105
110
|
const definition = bindingOf(context, identifier)?.defs[0];
|
|
106
111
|
if (definition?.type === DefinitionType.FunctionName)
|
|
107
112
|
return definition.node;
|
|
@@ -111,17 +116,17 @@ function boundTo(context, identifier) {
|
|
|
111
116
|
return undefined;
|
|
112
117
|
const { id, init } = definition.node;
|
|
113
118
|
return id.type === AST_NODE_TYPES.Identifier ? init : picked(id, init, identifier.name);
|
|
114
|
-
}
|
|
115
|
-
|
|
119
|
+
};
|
|
120
|
+
const selectBranches = (node, selectObjects) => {
|
|
116
121
|
const { callee } = node;
|
|
117
122
|
if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed)
|
|
118
123
|
return undefined;
|
|
119
124
|
if (callee.property.name !== 'select' || callee.object.type !== AST_NODE_TYPES.Identifier)
|
|
120
125
|
return undefined;
|
|
121
126
|
return selectObjects.has(callee.object.name) ? node.arguments[0] : undefined;
|
|
122
|
-
}
|
|
127
|
+
};
|
|
123
128
|
/** Every `return` with an argument in a body, including nested blocks but not nested functions. */
|
|
124
|
-
|
|
129
|
+
const returnsIn = (body) => {
|
|
125
130
|
const found = [];
|
|
126
131
|
const visit = (value) => {
|
|
127
132
|
if (Array.isArray(value)) {
|
|
@@ -146,7 +151,7 @@ function returnsIn(body) {
|
|
|
146
151
|
};
|
|
147
152
|
visit(body);
|
|
148
153
|
return found;
|
|
149
|
-
}
|
|
154
|
+
};
|
|
150
155
|
/**
|
|
151
156
|
* Every leaf an expression can produce.
|
|
152
157
|
*
|
|
@@ -160,7 +165,7 @@ function returnsIn(body) {
|
|
|
160
165
|
* itself: a name from another file, a `let`, a method call, a computed member,
|
|
161
166
|
* a spread. A cycle yields nothing rather than looping.
|
|
162
167
|
*/
|
|
163
|
-
export
|
|
168
|
+
export const leavesOf = (context, node, options = {}) => {
|
|
164
169
|
const { names = true, members = true, returns = false, selectObjects = DEFAULT_SELECT_OBJECTS, calls, seen = new Set(), } = options;
|
|
165
170
|
const out = [];
|
|
166
171
|
const walkFunction = (fn) => {
|
|
@@ -171,31 +176,38 @@ export function leavesOf(context, node, options = {}) {
|
|
|
171
176
|
for (const argument of returnsIn(fn.body))
|
|
172
177
|
walk(argument);
|
|
173
178
|
};
|
|
179
|
+
/** Each branch is a value for whatever slot the call sits in. */
|
|
180
|
+
const walkSelect = (branches) => {
|
|
181
|
+
for (const branch of leavesOf(context, branches, { ...options, seen })) {
|
|
182
|
+
if (branch.type !== AST_NODE_TYPES.ObjectExpression)
|
|
183
|
+
continue;
|
|
184
|
+
for (const property of branch.properties) {
|
|
185
|
+
if (property.type === AST_NODE_TYPES.Property)
|
|
186
|
+
walk(property.value);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
/** A call to a local function is what it returns (a factory); a method call is never followed. */
|
|
191
|
+
const factoryOf = (call) => {
|
|
192
|
+
if (!returns || call.callee.type !== AST_NODE_TYPES.Identifier)
|
|
193
|
+
return undefined;
|
|
194
|
+
const bound = boundTo(context, call.callee);
|
|
195
|
+
return bound && isFunction(bound) ? bound : undefined;
|
|
196
|
+
};
|
|
174
197
|
const didWalkCall = (call) => {
|
|
175
198
|
const branches = selectBranches(call, selectObjects);
|
|
176
199
|
if (branches) {
|
|
177
|
-
|
|
178
|
-
for (const branch of leavesOf(context, branches, { ...options, seen })) {
|
|
179
|
-
if (branch.type !== AST_NODE_TYPES.ObjectExpression)
|
|
180
|
-
continue;
|
|
181
|
-
for (const property of branch.properties) {
|
|
182
|
-
if (property.type === AST_NODE_TYPES.Property)
|
|
183
|
-
walk(property.value);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
200
|
+
walkSelect(branches);
|
|
186
201
|
return true;
|
|
187
202
|
}
|
|
188
203
|
if (calls?.(call)) {
|
|
189
204
|
walk(call.arguments[0]);
|
|
190
205
|
return true;
|
|
191
206
|
}
|
|
192
|
-
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
walk(bound);
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
207
|
+
const factory = factoryOf(call);
|
|
208
|
+
if (factory) {
|
|
209
|
+
walk(factory);
|
|
210
|
+
return true;
|
|
199
211
|
}
|
|
200
212
|
const hook = memoHookName(call.callee);
|
|
201
213
|
const callback = call.arguments.at(0);
|
|
@@ -208,6 +220,25 @@ export function leavesOf(context, node, options = {}) {
|
|
|
208
220
|
walkFunction(callback);
|
|
209
221
|
return true;
|
|
210
222
|
};
|
|
223
|
+
/** `OBJ.key`'s value when `OBJ` is a local `const` object literal. */
|
|
224
|
+
const memberEntry = (member) => {
|
|
225
|
+
if (member.computed || member.object.type !== AST_NODE_TYPES.Identifier)
|
|
226
|
+
return undefined;
|
|
227
|
+
const declarator = localDeclarator(context, member.object);
|
|
228
|
+
// A destructured name is not what the whole initialiser holds.
|
|
229
|
+
const held = declarator?.id.type === AST_NODE_TYPES.Identifier ? unwrap(declarator.init) : undefined;
|
|
230
|
+
if (held?.type !== AST_NODE_TYPES.ObjectExpression)
|
|
231
|
+
return undefined;
|
|
232
|
+
const key = member.property.name;
|
|
233
|
+
const entry = held.properties.find((property) => keyOf(property) === key);
|
|
234
|
+
return entry?.type === AST_NODE_TYPES.Property ? entry.value : undefined;
|
|
235
|
+
};
|
|
236
|
+
/** What a name or `OBJ.key` stands for, when the options let it be followed. */
|
|
237
|
+
const referenced = (node) => {
|
|
238
|
+
if (node.type === AST_NODE_TYPES.Identifier)
|
|
239
|
+
return names ? boundTo(context, node) : undefined;
|
|
240
|
+
return members ? memberEntry(node) : undefined;
|
|
241
|
+
};
|
|
211
242
|
const walk = (current) => {
|
|
212
243
|
if (!current || seen.has(current))
|
|
213
244
|
return;
|
|
@@ -229,26 +260,11 @@ export function leavesOf(context, node, options = {}) {
|
|
|
229
260
|
walk(current.left);
|
|
230
261
|
walk(current.right);
|
|
231
262
|
return;
|
|
232
|
-
case AST_NODE_TYPES.Identifier:
|
|
233
|
-
const bound = names ? boundTo(context, current) : undefined;
|
|
234
|
-
if (bound) {
|
|
235
|
-
walk(bound);
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
break;
|
|
239
|
-
}
|
|
263
|
+
case AST_NODE_TYPES.Identifier:
|
|
240
264
|
case AST_NODE_TYPES.MemberExpression: {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
// A destructured name is not what the whole initialiser holds.
|
|
245
|
-
const held = declarator?.id.type === AST_NODE_TYPES.Identifier ? unwrap(declarator.init) : undefined;
|
|
246
|
-
if (held?.type !== AST_NODE_TYPES.ObjectExpression)
|
|
247
|
-
break;
|
|
248
|
-
const key = current.property.name;
|
|
249
|
-
const entry = held.properties.find((property) => keyOf(property) === key);
|
|
250
|
-
if (entry?.type === AST_NODE_TYPES.Property) {
|
|
251
|
-
walk(entry.value);
|
|
265
|
+
const target = referenced(current);
|
|
266
|
+
if (target) {
|
|
267
|
+
walk(target);
|
|
252
268
|
return;
|
|
253
269
|
}
|
|
254
270
|
break;
|
|
@@ -271,4 +287,4 @@ export function leavesOf(context, node, options = {}) {
|
|
|
271
287
|
};
|
|
272
288
|
walk(node);
|
|
273
289
|
return out;
|
|
274
|
-
}
|
|
290
|
+
};
|
|
@@ -21,6 +21,18 @@ const UNRESOLVED = TypeFlags.TypeParameter |
|
|
|
21
21
|
TypeFlags.Conditional |
|
|
22
22
|
TypeFlags.Index |
|
|
23
23
|
TypeFlags.Substitution;
|
|
24
|
+
/**
|
|
25
|
+
* A callable or constructable type is truthy. Any other object type is only
|
|
26
|
+
* as good as what the falsy primitives do against it: `{}`, `Object` and
|
|
27
|
+
* `{ length: number }` all accept `""`, `0` or `false`.
|
|
28
|
+
*/
|
|
29
|
+
const isTruthyObject = (type, checker) => {
|
|
30
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0)
|
|
31
|
+
return true;
|
|
32
|
+
if (!checker)
|
|
33
|
+
return false;
|
|
34
|
+
return [checker.getStringLiteralType(""), checker.getNumberLiteralType(0), checker.getFalseType()].every((falsy) => !checker.isTypeAssignableTo(falsy, type));
|
|
35
|
+
};
|
|
24
36
|
/**
|
|
25
37
|
* Whether every value of this type is truthy, `undefined` aside.
|
|
26
38
|
*
|
|
@@ -50,18 +62,8 @@ export const isSameAsBangBang = (type, checker) => {
|
|
|
50
62
|
if (type.flags & (TypeFlags.ESSymbolLike | TypeFlags.NonPrimitive)) {
|
|
51
63
|
return true;
|
|
52
64
|
}
|
|
53
|
-
if (type.flags & TypeFlags.Object)
|
|
54
|
-
|
|
55
|
-
* A callable or constructable type is truthy. Any other object type is only
|
|
56
|
-
* as good as what the falsy primitives do against it: `{}`, `Object` and
|
|
57
|
-
* `{ length: number }` all accept `""`, `0` or `false`.
|
|
58
|
-
*/
|
|
59
|
-
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0)
|
|
60
|
-
return true;
|
|
61
|
-
if (!checker)
|
|
62
|
-
return false;
|
|
63
|
-
return [checker.getStringLiteralType(""), checker.getNumberLiteralType(0), checker.getFalseType()].every((falsy) => !checker.isTypeAssignableTo(falsy, type));
|
|
64
|
-
}
|
|
65
|
+
if (type.flags & TypeFlags.Object)
|
|
66
|
+
return isTruthyObject(type, checker);
|
|
65
67
|
// A literal is as truthy as its value: only `""`, `0` and `0n` are falsy.
|
|
66
68
|
if (type.isStringLiteral())
|
|
67
69
|
return type.value !== "";
|
package/dist/react-native.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import expo from 'eslint-plugin-expo';
|
|
2
|
+
import reactDoctor from 'eslint-plugin-react-doctor';
|
|
2
3
|
import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript';
|
|
3
4
|
import { createNodeResolver } from 'eslint-plugin-import-x';
|
|
4
5
|
import globals from 'globals';
|
|
@@ -152,6 +153,33 @@ export default function reactNative() {
|
|
|
152
153
|
'@hypersolid-dev/test-id-default': 'error',
|
|
153
154
|
},
|
|
154
155
|
},
|
|
156
|
+
{
|
|
157
|
+
// React Doctor's React Native rules, as used in the ESPN Goal pilot.
|
|
158
|
+
name: '@hypersolid-dev/react-native/react-doctor',
|
|
159
|
+
files: CODE_FILES,
|
|
160
|
+
ignores: TEST_FILES,
|
|
161
|
+
plugins: { 'react-doctor': reactDoctor },
|
|
162
|
+
rules: {
|
|
163
|
+
// A state update per scroll event re-renders mid-gesture; track position in a shared value. Bad: `onScroll={(event) => setOffset(event.nativeEvent.contentOffset.y)}`.
|
|
164
|
+
'react-doctor/rn-no-scroll-state': 'error',
|
|
165
|
+
// A reaction that only copies one value into another is a `useDerivedValue`. Bad: `useAnimatedReaction(() => x.value, (value) => { y.value = value; })`.
|
|
166
|
+
'react-doctor/rn-animation-reaction-as-derived': 'error',
|
|
167
|
+
// A string outside `<Text>` crashes on native. Bad: `<View>Hello</View>`.
|
|
168
|
+
'react-doctor/rn-no-raw-text': 'error',
|
|
169
|
+
// A list keys rows from `keyExtractor`; a `key` on the row is ignored. Bad: `renderItem={({ item }) => <Row key={item.id} />}`.
|
|
170
|
+
'react-doctor/rn-no-renderitem-key': 'error',
|
|
171
|
+
// A press animation set from `onPressIn` runs on the JS thread. Bad: `onPressIn={() => { scale.value = 0.9; }}`.
|
|
172
|
+
'react-doctor/rn-pressable-shared-value-mutation': 'error',
|
|
173
|
+
// `PanResponder` runs on the JS thread; `Gesture.Pan()` runs on the UI thread. Bad: `PanResponder.create({ … })`.
|
|
174
|
+
'react-doctor/rn-no-panresponder': 'error',
|
|
175
|
+
// `Dimensions.get()` reads the size once and misses rotation and split screen. Bad: `const { width } = Dimensions.get('window')` (use `useWindowDimensions`).
|
|
176
|
+
'react-doctor/rn-no-dimensions-get': 'error',
|
|
177
|
+
// `react-native/Libraries/…` paths are private and move on upgrade. Bad: `import … from 'react-native/Libraries/Image/resolveAssetSource'`.
|
|
178
|
+
'react-doctor/rn-no-deep-imports': 'error',
|
|
179
|
+
// Unmaintained Expo-era packages each have a successor. Bad: `import { Audio } from 'expo-av'` (use `expo-audio`).
|
|
180
|
+
'react-doctor/rn-no-legacy-expo-packages': 'error',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
155
183
|
{
|
|
156
184
|
name: '@hypersolid-dev/react-native/json',
|
|
157
185
|
files: ['**/*.json'],
|
package/dist/shared.d.ts
CHANGED
|
@@ -31,6 +31,6 @@ export declare const NAMING_CONVENTION: ({
|
|
|
31
31
|
format: string[];
|
|
32
32
|
modifiers?: undefined;
|
|
33
33
|
})[];
|
|
34
|
-
export declare
|
|
35
|
-
export declare
|
|
36
|
-
export declare
|
|
34
|
+
export declare const raiseWarnings: (configs: Linter.Config[]) => Linter.Config[];
|
|
35
|
+
export declare const scopeToCode: (configs: readonly Linter.Config[]) => Linter.Config[];
|
|
36
|
+
export declare const reactConfigs: () => Linter.Config[];
|