@blumintinc/eslint-plugin-blumint 1.20.123 → 1.20.125
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/lib/index.js +1 -1
- package/lib/rules/enforce-console-error.js +43 -4
- package/lib/rules/enforce-mock-firestore.js +93 -35
- package/lib/rules/enforce-querykey-ts.js +53 -9
- package/lib/rules/ensure-pointer-events-none.js +58 -26
- package/lib/rules/logical-top-to-bottom-grouping.js +119 -41
- package/lib/rules/no-complex-cloud-params.js +44 -11
- package/lib/rules/no-conditional-literals-in-jsx.js +20 -9
- package/lib/rules/no-margin-properties.js +116 -62
- package/lib/rules/prefer-global-router-state-key.js +53 -6
- package/package.json +1 -1
- package/release-manifest.json +108 -0
package/lib/index.js
CHANGED
|
@@ -41,6 +41,45 @@ exports.enforceConsoleError = (0, createRule_1.createRule)({
|
|
|
41
41
|
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
42
42
|
node.callee.property.name === method);
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* A type assertion carries no runtime behaviour, so `'error' as const`,
|
|
46
|
+
* `'error' satisfies Severity`, `<const>'error'` and `'error'!` all evaluate
|
|
47
|
+
* to exactly the string their inner expression evaluates to. Reading through
|
|
48
|
+
* the wrapper keeps a severity that is pinned at compile time out of the
|
|
49
|
+
* `dynamic` bucket, whose report demands both console methods and asserts
|
|
50
|
+
* the severity may render either dialog. The wrappers nest, so unwrap until
|
|
51
|
+
* a non-assertion node is reached.
|
|
52
|
+
*/
|
|
53
|
+
function unwrapTypeAssertions(node) {
|
|
54
|
+
let current = node;
|
|
55
|
+
while (current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
56
|
+
current.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
|
|
57
|
+
current.type === utils_1.AST_NODE_TYPES.TSNonNullExpression ||
|
|
58
|
+
current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion) {
|
|
59
|
+
current = current.expression;
|
|
60
|
+
}
|
|
61
|
+
return current;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Reads a severity that is fixed at compile time, or null when the value can
|
|
65
|
+
* only be known at runtime. A template with no substitutions denotes a
|
|
66
|
+
* single constant string exactly as a quoted literal does; only an
|
|
67
|
+
* INTERPOLATED template is genuinely dynamic.
|
|
68
|
+
*/
|
|
69
|
+
function getStaticSeverityValue(node) {
|
|
70
|
+
const value = unwrapTypeAssertions(node);
|
|
71
|
+
if (value.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
72
|
+
typeof value.value === 'string') {
|
|
73
|
+
return value.value;
|
|
74
|
+
}
|
|
75
|
+
if (value.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
|
|
76
|
+
value.expressions.length === 0 &&
|
|
77
|
+
value.quasis.length === 1 &&
|
|
78
|
+
typeof value.quasis[0].value.cooked === 'string') {
|
|
79
|
+
return value.quasis[0].value.cooked;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
44
83
|
function getSeverityFromObjectExpression(node) {
|
|
45
84
|
for (const prop of node.properties) {
|
|
46
85
|
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
@@ -57,11 +96,11 @@ exports.enforceConsoleError = (0, createRule_1.createRule)({
|
|
|
57
96
|
isSeverityProperty = true;
|
|
58
97
|
}
|
|
59
98
|
if (isSeverityProperty) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return
|
|
99
|
+
const staticSeverity = getStaticSeverityValue(prop.value);
|
|
100
|
+
if (staticSeverity !== null) {
|
|
101
|
+
return staticSeverity;
|
|
63
102
|
}
|
|
64
|
-
//
|
|
103
|
+
// A severity only known at runtime forces the stricter dynamic path
|
|
65
104
|
return 'dynamic';
|
|
66
105
|
}
|
|
67
106
|
}
|
|
@@ -8,6 +8,38 @@ const FIRESTORE_PATHS = [
|
|
|
8
8
|
'firebase-admin',
|
|
9
9
|
'firebase-admin/firestore',
|
|
10
10
|
];
|
|
11
|
+
/**
|
|
12
|
+
* `x as T`, `<T>x`, `x satisfies T` and `x!` assert a type without contributing
|
|
13
|
+
* a value of their own, so a check that classifies the *shape* of an expression
|
|
14
|
+
* must look through all four alike.
|
|
15
|
+
*
|
|
16
|
+
* This matters beyond hand-written code: sibling rules' autofixes append
|
|
17
|
+
* ` as const` to the very objects this rule inspects
|
|
18
|
+
* (`enforce-object-literal-as-const` rewrites
|
|
19
|
+
* `jest.mock(path, () => { return ({ db }); })` into
|
|
20
|
+
* `jest.mock(path, () => { return ({ db } as const); })`). A bare
|
|
21
|
+
* `node.type === ObjectExpression` test taken on the wrapper therefore goes
|
|
22
|
+
* silent on code `eslint --fix` had just reported (Issue #1806).
|
|
23
|
+
*/
|
|
24
|
+
const ASSERTION_EXPRESSION_TYPES = new Set([
|
|
25
|
+
utils_1.AST_NODE_TYPES.TSAsExpression,
|
|
26
|
+
utils_1.AST_NODE_TYPES.TSSatisfiesExpression,
|
|
27
|
+
utils_1.AST_NODE_TYPES.TSNonNullExpression,
|
|
28
|
+
utils_1.AST_NODE_TYPES.TSTypeAssertion,
|
|
29
|
+
]);
|
|
30
|
+
const isAssertionExpression = (node) => ASSERTION_EXPRESSION_TYPES.has(node.type);
|
|
31
|
+
/**
|
|
32
|
+
* Peels every assertion wrapper off an expression, so `{ db } as const`,
|
|
33
|
+
* `<const>{ db }` and chains such as `{ db } as const satisfies Module` all
|
|
34
|
+
* classify as the object literal they wrap.
|
|
35
|
+
*/
|
|
36
|
+
const unwrapAssertions = (node) => {
|
|
37
|
+
let target = node;
|
|
38
|
+
while (isAssertionExpression(target)) {
|
|
39
|
+
target = target.expression;
|
|
40
|
+
}
|
|
41
|
+
return target;
|
|
42
|
+
};
|
|
11
43
|
/**
|
|
12
44
|
* A `jest.mock` factory produces the same module shape whether it is written as
|
|
13
45
|
* a concise arrow, a block-bodied arrow, or a `function` expression. Matching
|
|
@@ -15,28 +47,37 @@ const FIRESTORE_PATHS = [
|
|
|
15
47
|
* identical mock evade the rule on a body-form choice alone. Resolving to the
|
|
16
48
|
* produced object keeps every spelling on one matching path.
|
|
17
49
|
*
|
|
50
|
+
* Assertions are peeled at both ends — off the factory and off the expression
|
|
51
|
+
* it produces — so every body form is covered by one unwrap rather than only
|
|
52
|
+
* the spelling a bug report happened to quote.
|
|
53
|
+
*
|
|
18
54
|
* A body with more than a lone `return` is deliberately unresolved: the object
|
|
19
55
|
* reaching the caller can no longer be read off a single expression.
|
|
20
56
|
*/
|
|
21
57
|
const resolveFactoryReturn = (factory) => {
|
|
22
|
-
if (!factory
|
|
23
|
-
|
|
24
|
-
|
|
58
|
+
if (!factory) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
const callable = unwrapAssertions(factory);
|
|
62
|
+
if (callable.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
63
|
+
callable.type !== utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
25
64
|
return undefined;
|
|
26
65
|
}
|
|
27
66
|
// A concise arrow body is the produced expression itself. Parentheses around
|
|
28
67
|
// an object body are not part of the AST, so the node is matched directly.
|
|
29
|
-
if (
|
|
30
|
-
return
|
|
68
|
+
if (callable.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
69
|
+
return unwrapAssertions(callable.body);
|
|
31
70
|
}
|
|
32
|
-
const statements =
|
|
71
|
+
const statements = callable.body.body;
|
|
33
72
|
if (statements.length !== 1) {
|
|
34
73
|
return undefined;
|
|
35
74
|
}
|
|
36
75
|
const [statement] = statements;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
76
|
+
if (statement.type !== utils_1.AST_NODE_TYPES.ReturnStatement ||
|
|
77
|
+
!statement.argument) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
return unwrapAssertions(statement.argument);
|
|
40
81
|
};
|
|
41
82
|
exports.enforceFirestoreMock = (0, createRule_1.createRule)({
|
|
42
83
|
name: 'enforce-mock-firestore',
|
|
@@ -57,34 +98,51 @@ exports.enforceFirestoreMock = (0, createRule_1.createRule)({
|
|
|
57
98
|
return {
|
|
58
99
|
// Detect jest.mock() calls for firebaseAdmin
|
|
59
100
|
CallExpression(node) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
101
|
+
// `(jest.mock as any)(...)` and `jest!.mock(...)` call the same
|
|
102
|
+
// function, so the callee and its object are read through assertions
|
|
103
|
+
// too. The property name cannot carry one: an assertion there requires
|
|
104
|
+
// computed access, a spelling this rule does not resolve at all.
|
|
105
|
+
const callee = unwrapAssertions(node.callee);
|
|
106
|
+
if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const calleeObject = unwrapAssertions(callee.object);
|
|
110
|
+
if (calleeObject.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
111
|
+
calleeObject.name !== 'jest' ||
|
|
112
|
+
callee.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
113
|
+
callee.property.name !== 'mock' ||
|
|
114
|
+
node.arguments.length === 0) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const modulePath = unwrapAssertions(node.arguments[0]);
|
|
118
|
+
if (modulePath.type !== utils_1.AST_NODE_TYPES.Literal) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const mockedPath = modulePath.value;
|
|
122
|
+
if (typeof mockedPath !== 'string' ||
|
|
123
|
+
!FIRESTORE_PATHS.some((path) => mockedPath.includes(path))) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Check if the mock includes Firestore-related properties
|
|
127
|
+
const mockedModule = resolveFactoryReturn(node.arguments[1]);
|
|
128
|
+
if (mockedModule &&
|
|
129
|
+
mockedModule.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
130
|
+
mockedModule.properties.some((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
131
|
+
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
132
|
+
(prop.key.name === 'db' ||
|
|
133
|
+
prop.key.name === 'firestore' ||
|
|
134
|
+
prop.key.name === 'getFirestore'))) {
|
|
135
|
+
context.report({
|
|
136
|
+
node,
|
|
137
|
+
messageId: 'noManualFirestoreMock',
|
|
138
|
+
});
|
|
85
139
|
}
|
|
86
140
|
},
|
|
87
|
-
// Detect imports of mockFirebase
|
|
141
|
+
// Detect imports of mockFirebase.
|
|
142
|
+
//
|
|
143
|
+
// No unwrapping applies here: the grammar admits only a bare string
|
|
144
|
+
// literal as an import source and only a bare identifier as an imported
|
|
145
|
+
// name, so neither position can carry an assertion for a fixer to add.
|
|
88
146
|
ImportDeclaration(node) {
|
|
89
147
|
if (node.source.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
90
148
|
node.source.value === 'firestore-jest-mock' &&
|
|
@@ -478,15 +478,60 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
478
478
|
return false;
|
|
479
479
|
}
|
|
480
480
|
/**
|
|
481
|
-
*
|
|
481
|
+
* The key's value when it is knowable without running the program, paired
|
|
482
|
+
* with the node that spells it.
|
|
483
|
+
*
|
|
484
|
+
* The substituted `QUERY_KEY_*` name is derived from that value, so what a
|
|
485
|
+
* fix needs is the value — not the notation carrying it. Gating on the node
|
|
486
|
+
* type instead left a static template reported exactly like the quoted
|
|
487
|
+
* string it renders to but with no fix behind the report (#1803). Every
|
|
488
|
+
* genuinely underivable shape — concatenation, a ternary, a template WITH
|
|
489
|
+
* expressions — holds no single value and falls out here on its own, so the
|
|
490
|
+
* conservative carve-out survives without being keyed to notation.
|
|
491
|
+
*
|
|
492
|
+
* Read through `cooked` rather than `raw` so an escape names the character
|
|
493
|
+
* it renders to, and the two spellings of one key derive one constant.
|
|
494
|
+
*/
|
|
495
|
+
function staticKeyOf(node) {
|
|
496
|
+
if (node.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
497
|
+
return typeof node.value === 'string'
|
|
498
|
+
? { node, text: node.value }
|
|
499
|
+
: null;
|
|
500
|
+
}
|
|
501
|
+
if (node.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
|
|
502
|
+
node.expressions.length === 0) {
|
|
503
|
+
const cooked = node.quasis[0]?.value.cooked;
|
|
504
|
+
// A cooked value is absent only for an invalid escape sequence, which
|
|
505
|
+
// names no character and so cannot name a constant either.
|
|
506
|
+
return typeof cooked === 'string' ? { node, text: cooked } : null;
|
|
507
|
+
}
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* The `QUERY_KEY_*` constant a key value names, or null when it names none.
|
|
512
|
+
*
|
|
513
|
+
* A key that is empty, or built only from the characters normalization
|
|
514
|
+
* folds into separators and then strips, leaves nothing after the prefix:
|
|
515
|
+
* the bare `QUERY_KEY_` that emitted is a name `queryKeys.ts` neither
|
|
516
|
+
* exports nor plausibly would, so applying it traded a report for a file
|
|
517
|
+
* that no longer compiles. Declining here leaves the report standing with
|
|
518
|
+
* no fix, which is the honest outcome — the author has to choose a real
|
|
519
|
+
* key, and no rewrite can choose one for them.
|
|
520
|
+
*
|
|
521
|
+
* The test is on the derived text alone, so it answers the same way for
|
|
522
|
+
* every notation the same value can be written in; putting it in
|
|
523
|
+
* `staticKeyOf` instead would gate the fix on content at the point that
|
|
524
|
+
* exists to keep notation out of the gate (#1803, #1813).
|
|
482
525
|
*/
|
|
483
526
|
function generateAutoFix(keyValue) {
|
|
484
|
-
// Simple heuristic to suggest query key constant names
|
|
485
527
|
const normalizedKey = keyValue
|
|
486
528
|
.toUpperCase()
|
|
487
529
|
.replace(/[^A-Z0-9]/g, '_')
|
|
488
530
|
.replace(/_+/g, '_')
|
|
489
531
|
.replace(/^_|_$/g, '');
|
|
532
|
+
if (normalizedKey === '') {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
490
535
|
return `QUERY_KEY_${normalizedKey}`;
|
|
491
536
|
}
|
|
492
537
|
return {
|
|
@@ -549,18 +594,17 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
549
594
|
if (!isValidQueryKeyUsage(keyValue)) {
|
|
550
595
|
// Check if it contains invalid string literals
|
|
551
596
|
if (containsInvalidStringLiteral(keyValue)) {
|
|
552
|
-
// Only
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
? generateAutoFix(
|
|
597
|
+
// Only a statically known key value can be auto-fixed.
|
|
598
|
+
const staticKey = staticKeyOf(keyValue);
|
|
599
|
+
const suggestedConstant = staticKey
|
|
600
|
+
? generateAutoFix(staticKey.text)
|
|
556
601
|
: null;
|
|
557
602
|
pendingReports.push({
|
|
558
603
|
node: keyValue,
|
|
559
604
|
messageId: 'enforceQueryKeyImport',
|
|
560
|
-
substitution:
|
|
561
|
-
keyValue.type === utils_1.AST_NODE_TYPES.Literal
|
|
605
|
+
substitution: staticKey && suggestedConstant
|
|
562
606
|
? {
|
|
563
|
-
keyNode:
|
|
607
|
+
keyNode: staticKey.node,
|
|
564
608
|
constant: suggestedConstant,
|
|
565
609
|
scope: scopeOf(keyValue),
|
|
566
610
|
}
|
|
@@ -9,6 +9,34 @@ const createRule_1 = require("../utils/createRule");
|
|
|
9
9
|
function hasPseudoElementSelector(selector) {
|
|
10
10
|
return /::?(before|after)\b/i.test(selector);
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Reads the static string a node denotes, so a property name or value carries
|
|
14
|
+
* the same meaning however it is spelled. A no-substitution template literal is
|
|
15
|
+
* a notation-only rewrite of a quoted string, and CSS-in-JS code writes both.
|
|
16
|
+
*
|
|
17
|
+
* Reading every name and value through one accessor keeps detection and the
|
|
18
|
+
* `pointerEvents` exemption on the same footing. Widening only the detection
|
|
19
|
+
* side would make the rule report objects that already set `pointerEvents` in a
|
|
20
|
+
* spelling it cannot read, and its fixer would append a second key — an object
|
|
21
|
+
* literal with duplicate keys does not compile.
|
|
22
|
+
*
|
|
23
|
+
* An interpolated template stays opaque: its text is not known statically, so
|
|
24
|
+
* the rule keeps its conservative silence there.
|
|
25
|
+
*/
|
|
26
|
+
function staticStringOf(node) {
|
|
27
|
+
if (node.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
28
|
+
return String(node.value);
|
|
29
|
+
}
|
|
30
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
31
|
+
return node.name;
|
|
32
|
+
}
|
|
33
|
+
if (node.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
|
|
34
|
+
node.expressions.length === 0 &&
|
|
35
|
+
node.quasis.length === 1) {
|
|
36
|
+
return node.quasis[0].value.cooked ?? node.quasis[0].value.raw;
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
12
40
|
/**
|
|
13
41
|
* Checks if a property name is position with absolute or fixed value
|
|
14
42
|
*/
|
|
@@ -151,6 +179,13 @@ exports.ensurePointerEventsNone = (0, createRule_1.createRule)({
|
|
|
151
179
|
const absolutePositionedStyles = new Map();
|
|
152
180
|
// Track style objects that already have pointer-events defined
|
|
153
181
|
const stylesWithPointerEvents = new Map();
|
|
182
|
+
// Track style objects that declare a `pointerEvents` key whose value cannot
|
|
183
|
+
// be read statically (a member expression, a call, a ternary, an
|
|
184
|
+
// interpolated template). Such a value earns no exemption — it might be
|
|
185
|
+
// 'auto', so the report stands — but it does veto the fix: the rule's only
|
|
186
|
+
// remedy is to append a `pointerEvents` key, and an object literal with two
|
|
187
|
+
// identical keys does not compile (TS1117).
|
|
188
|
+
const stylesWithUnreadablePointerEvents = new Map();
|
|
154
189
|
// Track style objects that are hit-slop touch-target extensions: an
|
|
155
190
|
// absolute/fixed overlay whose inset offsets only extend beyond the origin
|
|
156
191
|
// box (>=1 negative, none positive). A browser attributes pointer events on
|
|
@@ -164,40 +199,29 @@ exports.ensurePointerEventsNone = (0, createRule_1.createRule)({
|
|
|
164
199
|
function processStyleObject(node) {
|
|
165
200
|
let hasAbsolutePosition = false;
|
|
166
201
|
let pointerEventsValue;
|
|
202
|
+
let hasUnreadablePointerEvents = false;
|
|
167
203
|
let hasNegativeOffset = false;
|
|
168
204
|
let hasPositiveOffset = false;
|
|
169
205
|
// Check each property in the style object
|
|
170
206
|
for (const property of node.properties) {
|
|
171
207
|
if (property.type !== utils_1.AST_NODE_TYPES.Property)
|
|
172
208
|
continue;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
// Get property name
|
|
176
|
-
if (property.key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
177
|
-
propertyName = property.key.name;
|
|
178
|
-
}
|
|
179
|
-
else if (property.key.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
180
|
-
typeof property.key.value === 'string') {
|
|
181
|
-
propertyName = property.key.value;
|
|
182
|
-
}
|
|
183
|
-
// Get property value if it's a string literal
|
|
184
|
-
if (property.value.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
185
|
-
propertyValue = String(property.value.value);
|
|
186
|
-
}
|
|
187
|
-
else if (property.value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
188
|
-
propertyValue = property.value.name;
|
|
189
|
-
}
|
|
209
|
+
const propertyName = staticStringOf(property.key) ?? '';
|
|
210
|
+
const propertyValue = staticStringOf(property.value);
|
|
190
211
|
// Check if this is position: absolute/fixed
|
|
191
212
|
if (isAbsoluteOrFixedPosition(propertyName, propertyValue)) {
|
|
192
213
|
hasAbsolutePosition = true;
|
|
193
214
|
}
|
|
194
|
-
// Check if this is pointer-events property
|
|
215
|
+
// Check if this is pointer-events property. A value that can be read
|
|
216
|
+
// decides the exemption; one that cannot is recorded separately, because
|
|
217
|
+
// the key's presence vetoes the fix even where it cannot prove the
|
|
218
|
+
// overlay is inert. An unreadable value never clears one already read.
|
|
195
219
|
if (isPointerEventsProperty(propertyName)) {
|
|
196
|
-
if (
|
|
197
|
-
pointerEventsValue =
|
|
220
|
+
if (propertyValue !== undefined) {
|
|
221
|
+
pointerEventsValue = propertyValue;
|
|
198
222
|
}
|
|
199
|
-
else
|
|
200
|
-
|
|
223
|
+
else {
|
|
224
|
+
hasUnreadablePointerEvents = true;
|
|
201
225
|
}
|
|
202
226
|
}
|
|
203
227
|
// Track inset offsets to detect hit-slop touch-target extensions
|
|
@@ -216,6 +240,7 @@ exports.ensurePointerEventsNone = (0, createRule_1.createRule)({
|
|
|
216
240
|
if (pointerEventsValue !== undefined) {
|
|
217
241
|
stylesWithPointerEvents.set(node, pointerEventsValue);
|
|
218
242
|
}
|
|
243
|
+
stylesWithUnreadablePointerEvents.set(node, hasUnreadablePointerEvents);
|
|
219
244
|
// A hit-slop extension only enlarges the tappable area: it is
|
|
220
245
|
// absolute/fixed and its inset offsets extend outward (>=1 negative, none
|
|
221
246
|
// positive). Such overlays cannot occlude the control they belong to.
|
|
@@ -246,6 +271,13 @@ exports.ensurePointerEventsNone = (0, createRule_1.createRule)({
|
|
|
246
271
|
selector: formatSelector(selector),
|
|
247
272
|
},
|
|
248
273
|
fix(fixer) {
|
|
274
|
+
// The object already declares `pointerEvents`, but in a spelling
|
|
275
|
+
// whose value cannot be read. Appending the key is the rule's only
|
|
276
|
+
// remedy, and here it would emit a duplicate key that does not
|
|
277
|
+
// compile. A report with no fix is the correct outcome: the reader
|
|
278
|
+
// decides what the opaque value resolves to.
|
|
279
|
+
if (stylesWithUnreadablePointerEvents.get(node))
|
|
280
|
+
return null;
|
|
249
281
|
// Find the last property in the object
|
|
250
282
|
const sourceCode = context.sourceCode;
|
|
251
283
|
const properties = node.properties;
|
|
@@ -350,12 +382,12 @@ exports.ensurePointerEventsNone = (0, createRule_1.createRule)({
|
|
|
350
382
|
// Process CSS-in-JS libraries that use objects with selectors
|
|
351
383
|
Property(node) {
|
|
352
384
|
// Check for patterns like { '&::before': { ... } }
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
hasPseudoElementSelector(
|
|
385
|
+
const selector = staticStringOf(node.key);
|
|
386
|
+
if (selector !== undefined &&
|
|
387
|
+
hasPseudoElementSelector(selector) &&
|
|
356
388
|
node.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
357
389
|
processStyleObject(node.value);
|
|
358
|
-
checkStyleObject(node.value,
|
|
390
|
+
checkStyleObject(node.value, selector);
|
|
359
391
|
}
|
|
360
392
|
},
|
|
361
393
|
};
|