@blumintinc/eslint-plugin-blumint 1.8.0 → 1.8.2
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 +84 -39
- package/lib/index.js +50 -2
- package/lib/rules/enforce-assertSafe-object-key.js +1 -1
- package/lib/rules/enforce-css-media-queries.d.ts +5 -0
- package/lib/rules/enforce-css-media-queries.js +87 -0
- package/lib/rules/enforce-dynamic-imports.d.ts +9 -0
- package/lib/rules/enforce-dynamic-imports.js +84 -0
- package/lib/rules/enforce-id-capitalization.d.ts +6 -0
- package/lib/rules/enforce-id-capitalization.js +118 -0
- package/lib/rules/enforce-mui-rounded-icons.d.ts +1 -0
- package/lib/rules/enforce-mui-rounded-icons.js +54 -0
- package/lib/rules/enforce-object-literal-as-const.js +37 -0
- package/lib/rules/enforce-positive-naming.js +116 -168
- package/lib/rules/enforce-react-type-naming.d.ts +3 -0
- package/lib/rules/enforce-react-type-naming.js +191 -0
- package/lib/rules/enforce-singular-type-names.d.ts +2 -0
- package/lib/rules/enforce-singular-type-names.js +112 -0
- package/lib/rules/enforce-verb-noun-naming.js +5 -2
- package/lib/rules/ensure-pointer-events-none.d.ts +1 -0
- package/lib/rules/ensure-pointer-events-none.js +211 -0
- package/lib/rules/key-only-outermost-element.d.ts +3 -0
- package/lib/rules/key-only-outermost-element.js +152 -0
- package/lib/rules/no-always-true-false-conditions.js +19 -0
- package/lib/rules/no-circular-references.d.ts +1 -0
- package/lib/rules/no-circular-references.js +523 -0
- package/lib/rules/no-hungarian.d.ts +5 -0
- package/lib/rules/no-hungarian.js +223 -0
- package/lib/rules/no-object-values-on-strings.d.ts +2 -0
- package/lib/rules/no-object-values-on-strings.js +294 -0
- package/lib/rules/no-type-assertion-returns.js +143 -118
- package/lib/rules/no-unnecessary-destructuring.d.ts +5 -0
- package/lib/rules/no-unnecessary-destructuring.js +69 -0
- package/lib/rules/no-unused-props.js +109 -11
- package/lib/rules/no-unused-usestate.d.ts +8 -0
- package/lib/rules/no-unused-usestate.js +84 -0
- package/lib/rules/omit-index-html.d.ts +7 -0
- package/lib/rules/omit-index-html.js +91 -0
- package/lib/rules/prefer-usememo-over-useeffect-usestate.d.ts +5 -0
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +129 -0
- package/lib/rules/react-usememo-should-be-component.js +369 -2
- package/package.json +7 -4
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforceReactTypeNaming = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
// Types that should have lowercase variable names
|
|
7
|
+
const LOWERCASE_TYPES = ['ReactNode', 'JSX.Element'];
|
|
8
|
+
// Types that should have uppercase variable names
|
|
9
|
+
const UPPERCASE_TYPES = ['ComponentType', 'FC', 'FunctionComponent'];
|
|
10
|
+
exports.enforceReactTypeNaming = (0, createRule_1.createRule)({
|
|
11
|
+
name: 'enforce-react-type-naming',
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'suggestion',
|
|
14
|
+
docs: {
|
|
15
|
+
description: 'Enforce naming conventions for React types',
|
|
16
|
+
recommended: 'error',
|
|
17
|
+
},
|
|
18
|
+
fixable: 'code',
|
|
19
|
+
schema: [],
|
|
20
|
+
messages: {
|
|
21
|
+
reactNodeShouldBeLowercase: 'Variables or parameters of type "{{type}}" should use lowercase naming (e.g., "{{suggestion}}").',
|
|
22
|
+
componentTypeShouldBeUppercase: 'Variables or parameters of type "{{type}}" should use uppercase naming (e.g., "{{suggestion}}").',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultOptions: [],
|
|
26
|
+
create(context) {
|
|
27
|
+
/**
|
|
28
|
+
* Checks if a string starts with an uppercase letter
|
|
29
|
+
*/
|
|
30
|
+
function isUppercase(str) {
|
|
31
|
+
return /^[A-Z]/.test(str);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Converts a string to start with lowercase
|
|
35
|
+
*/
|
|
36
|
+
function toLowercase(str) {
|
|
37
|
+
if (!str)
|
|
38
|
+
return str;
|
|
39
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Converts a string to start with uppercase
|
|
43
|
+
*/
|
|
44
|
+
function toUppercase(str) {
|
|
45
|
+
if (!str)
|
|
46
|
+
return str;
|
|
47
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Extracts the type name from a type annotation
|
|
51
|
+
*/
|
|
52
|
+
function getTypeName(typeAnnotation) {
|
|
53
|
+
if (!typeAnnotation)
|
|
54
|
+
return null;
|
|
55
|
+
// Handle TSTypeReference (e.g., ReactNode, ComponentType)
|
|
56
|
+
if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
57
|
+
if (typeAnnotation.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
58
|
+
return typeAnnotation.typeName.name;
|
|
59
|
+
}
|
|
60
|
+
// Handle qualified names like JSX.Element
|
|
61
|
+
if (typeAnnotation.typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
|
|
62
|
+
const left = typeAnnotation.typeName.left.type === utils_1.AST_NODE_TYPES.Identifier
|
|
63
|
+
? typeAnnotation.typeName.left.name
|
|
64
|
+
: '';
|
|
65
|
+
const right = typeAnnotation.typeName.right.name;
|
|
66
|
+
return `${left}.${right}`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Checks if a node is a destructured variable
|
|
73
|
+
*/
|
|
74
|
+
function isDestructured(node) {
|
|
75
|
+
return (node.parent?.type === utils_1.AST_NODE_TYPES.Property ||
|
|
76
|
+
node.parent?.type === utils_1.AST_NODE_TYPES.ArrayPattern ||
|
|
77
|
+
(node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
78
|
+
(node.parent.id.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
|
|
79
|
+
node.parent.id.type === utils_1.AST_NODE_TYPES.ArrayPattern)));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Checks if a node is a default import
|
|
83
|
+
*/
|
|
84
|
+
function isDefaultImport(node) {
|
|
85
|
+
return (node.parent?.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier ||
|
|
86
|
+
(node.parent?.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
87
|
+
node.parent.local.name !== node.parent.imported?.name));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check variable declarations for React type naming conventions
|
|
91
|
+
*/
|
|
92
|
+
function checkVariableDeclaration(node) {
|
|
93
|
+
if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
94
|
+
return;
|
|
95
|
+
// Skip destructured variables
|
|
96
|
+
if (isDestructured(node.id))
|
|
97
|
+
return;
|
|
98
|
+
const variableName = node.id.name;
|
|
99
|
+
// Get the type annotation
|
|
100
|
+
const typeAnnotation = node.id.typeAnnotation?.typeAnnotation;
|
|
101
|
+
const typeName = getTypeName(typeAnnotation);
|
|
102
|
+
if (!typeName)
|
|
103
|
+
return;
|
|
104
|
+
// Check if it's a ReactNode or JSX.Element (should be lowercase)
|
|
105
|
+
if (LOWERCASE_TYPES.includes(typeName) && isUppercase(variableName)) {
|
|
106
|
+
const suggestion = toLowercase(variableName);
|
|
107
|
+
context.report({
|
|
108
|
+
node: node.id,
|
|
109
|
+
messageId: 'reactNodeShouldBeLowercase',
|
|
110
|
+
data: {
|
|
111
|
+
type: typeName,
|
|
112
|
+
suggestion,
|
|
113
|
+
},
|
|
114
|
+
fix: (fixer) => fixer.replaceText(node.id, suggestion),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
// Check if it's a ComponentType or FC (should be uppercase)
|
|
118
|
+
if (UPPERCASE_TYPES.includes(typeName) && !isUppercase(variableName)) {
|
|
119
|
+
const suggestion = toUppercase(variableName);
|
|
120
|
+
context.report({
|
|
121
|
+
node: node.id,
|
|
122
|
+
messageId: 'componentTypeShouldBeUppercase',
|
|
123
|
+
data: {
|
|
124
|
+
type: typeName,
|
|
125
|
+
suggestion,
|
|
126
|
+
},
|
|
127
|
+
fix: (fixer) => fixer.replaceText(node.id, suggestion),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Check function parameters for React type naming conventions
|
|
133
|
+
*/
|
|
134
|
+
function checkParameter(node) {
|
|
135
|
+
if (node.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
136
|
+
return;
|
|
137
|
+
// Skip destructured parameters
|
|
138
|
+
if (isDestructured(node))
|
|
139
|
+
return;
|
|
140
|
+
// Skip default imports
|
|
141
|
+
if (isDefaultImport(node))
|
|
142
|
+
return;
|
|
143
|
+
const paramName = node.name;
|
|
144
|
+
// Get the type annotation
|
|
145
|
+
const typeAnnotation = node.typeAnnotation?.typeAnnotation;
|
|
146
|
+
const typeName = getTypeName(typeAnnotation);
|
|
147
|
+
if (!typeName)
|
|
148
|
+
return;
|
|
149
|
+
// Check if it's a ReactNode or JSX.Element (should be lowercase)
|
|
150
|
+
if (LOWERCASE_TYPES.includes(typeName) && isUppercase(paramName)) {
|
|
151
|
+
const suggestion = toLowercase(paramName);
|
|
152
|
+
context.report({
|
|
153
|
+
node,
|
|
154
|
+
messageId: 'reactNodeShouldBeLowercase',
|
|
155
|
+
data: {
|
|
156
|
+
type: typeName,
|
|
157
|
+
suggestion,
|
|
158
|
+
},
|
|
159
|
+
fix: (fixer) => fixer.replaceText(node, suggestion),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// Check if it's a ComponentType or FC (should be uppercase)
|
|
163
|
+
if (UPPERCASE_TYPES.includes(typeName) && !isUppercase(paramName)) {
|
|
164
|
+
const suggestion = toUppercase(paramName);
|
|
165
|
+
context.report({
|
|
166
|
+
node,
|
|
167
|
+
messageId: 'componentTypeShouldBeUppercase',
|
|
168
|
+
data: {
|
|
169
|
+
type: typeName,
|
|
170
|
+
suggestion,
|
|
171
|
+
},
|
|
172
|
+
fix: (fixer) => fixer.replaceText(node, suggestion),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
VariableDeclarator: checkVariableDeclaration,
|
|
178
|
+
Identifier(node) {
|
|
179
|
+
// Check parameter names in function declarations
|
|
180
|
+
if (node.parent &&
|
|
181
|
+
(node.parent.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
182
|
+
node.parent.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
183
|
+
node.parent.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) &&
|
|
184
|
+
node.parent.params.includes(node)) {
|
|
185
|
+
checkParameter(node);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
//# sourceMappingURL=enforce-react-type-naming.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.enforceSingularTypeNames = void 0;
|
|
27
|
+
const createRule_1 = require("../utils/createRule");
|
|
28
|
+
const pluralize = __importStar(require("pluralize"));
|
|
29
|
+
exports.enforceSingularTypeNames = (0, createRule_1.createRule)({
|
|
30
|
+
create(context) {
|
|
31
|
+
/**
|
|
32
|
+
* Check if a name is plural
|
|
33
|
+
* @param name The name to check
|
|
34
|
+
* @returns true if the name is plural, false otherwise
|
|
35
|
+
*/
|
|
36
|
+
function isPlural(name) {
|
|
37
|
+
// Skip checking if name is too short (less than 3 characters)
|
|
38
|
+
if (name.length < 3)
|
|
39
|
+
return false;
|
|
40
|
+
// Skip checking if name ends with 'Props' or 'Params'
|
|
41
|
+
if (name.endsWith('Props') || name.endsWith('Params') || name.endsWith('Options') || name.endsWith('Settings'))
|
|
42
|
+
return false;
|
|
43
|
+
// Skip checking if name is already singular according to pluralize
|
|
44
|
+
if (pluralize.isSingular(name))
|
|
45
|
+
return false;
|
|
46
|
+
// Check if the singular form is different from the name
|
|
47
|
+
const singular = pluralize.singular(name);
|
|
48
|
+
return singular !== name;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the singular form of a name
|
|
52
|
+
* @param name The name to get the singular form of
|
|
53
|
+
* @returns The singular form of the name
|
|
54
|
+
*/
|
|
55
|
+
function getSingularForm(name) {
|
|
56
|
+
return pluralize.singular(name);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Report a plural type name
|
|
60
|
+
* @param node The node to report
|
|
61
|
+
* @param name The plural name
|
|
62
|
+
* @param suggestedName The suggested singular name
|
|
63
|
+
*/
|
|
64
|
+
function reportPluralName(node, name, suggestedName) {
|
|
65
|
+
context.report({
|
|
66
|
+
node,
|
|
67
|
+
messageId: 'typeShouldBeSingular',
|
|
68
|
+
data: {
|
|
69
|
+
name,
|
|
70
|
+
suggestedName,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
// Check type aliases
|
|
76
|
+
TSTypeAliasDeclaration(node) {
|
|
77
|
+
const name = node.id.name;
|
|
78
|
+
if (isPlural(name)) {
|
|
79
|
+
reportPluralName(node.id, name, getSingularForm(name));
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
// Check interfaces
|
|
83
|
+
TSInterfaceDeclaration(node) {
|
|
84
|
+
const name = node.id.name;
|
|
85
|
+
if (isPlural(name)) {
|
|
86
|
+
reportPluralName(node.id, name, getSingularForm(name));
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
// Check enums
|
|
90
|
+
TSEnumDeclaration(node) {
|
|
91
|
+
const name = node.id.name;
|
|
92
|
+
if (isPlural(name)) {
|
|
93
|
+
reportPluralName(node.id, name, getSingularForm(name));
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
name: 'enforce-singular-type-names',
|
|
99
|
+
meta: {
|
|
100
|
+
type: 'suggestion',
|
|
101
|
+
docs: {
|
|
102
|
+
description: 'Enforce TypeScript type names to be singular',
|
|
103
|
+
recommended: 'error',
|
|
104
|
+
},
|
|
105
|
+
schema: [],
|
|
106
|
+
messages: {
|
|
107
|
+
typeShouldBeSingular: "Type name '{{name}}' should be singular. Consider using '{{suggestedName}}' instead.",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
defaultOptions: [],
|
|
111
|
+
});
|
|
112
|
+
//# sourceMappingURL=enforce-singular-type-names.js.map
|
|
@@ -4651,10 +4651,13 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
|
|
|
4651
4651
|
}
|
|
4652
4652
|
}
|
|
4653
4653
|
return false;
|
|
4654
|
-
})()
|
|
4654
|
+
})(),
|
|
4655
|
+
// 5. Component name ends with "Unmemoized" (common React pattern)
|
|
4656
|
+
functionName && functionName.endsWith('Unmemoized')
|
|
4655
4657
|
];
|
|
4656
4658
|
// Consider it a React component if it matches at least 2 indicators
|
|
4657
|
-
|
|
4659
|
+
// OR if it has the Unmemoized suffix (strong indicator of a React component)
|
|
4660
|
+
return indicators.filter(Boolean).length >= 2 || (!!functionName && functionName.endsWith('Unmemoized'));
|
|
4658
4661
|
}
|
|
4659
4662
|
return {
|
|
4660
4663
|
FunctionDeclaration(node) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ensurePointerEventsNone: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"missingPointerEventsNone", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensurePointerEventsNone = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
/**
|
|
7
|
+
* Checks if a string contains a pseudo-element selector (::before or ::after)
|
|
8
|
+
*/
|
|
9
|
+
function hasPseudoElementSelector(selector) {
|
|
10
|
+
return /::?(before|after)\b/i.test(selector);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Checks if a property name is position with absolute or fixed value
|
|
14
|
+
*/
|
|
15
|
+
function isAbsoluteOrFixedPosition(propertyName, propertyValue) {
|
|
16
|
+
if (propertyName !== 'position')
|
|
17
|
+
return false;
|
|
18
|
+
return propertyValue === 'absolute' || propertyValue === 'fixed';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Checks if a property is pointer-events with a value
|
|
22
|
+
*/
|
|
23
|
+
function isPointerEventsProperty(propertyName) {
|
|
24
|
+
return propertyName === 'pointerEvents' || propertyName === 'pointer-events';
|
|
25
|
+
}
|
|
26
|
+
exports.ensurePointerEventsNone = (0, createRule_1.createRule)({
|
|
27
|
+
name: 'ensure-pointer-events-none',
|
|
28
|
+
meta: {
|
|
29
|
+
type: 'suggestion',
|
|
30
|
+
docs: {
|
|
31
|
+
description: 'Ensure pointer-events: none is added to non-interactive pseudo-elements',
|
|
32
|
+
recommended: 'error',
|
|
33
|
+
},
|
|
34
|
+
fixable: 'code',
|
|
35
|
+
schema: [],
|
|
36
|
+
messages: {
|
|
37
|
+
missingPointerEventsNone: 'Pseudo-elements (::before, ::after) with position: absolute or fixed should have pointer-events: none to prevent blocking interactions with underlying elements',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
defaultOptions: [],
|
|
41
|
+
create(context) {
|
|
42
|
+
// Track style objects that have position: absolute or fixed
|
|
43
|
+
const absolutePositionedStyles = new Map();
|
|
44
|
+
// Track style objects that already have pointer-events defined
|
|
45
|
+
const stylesWithPointerEvents = new Map();
|
|
46
|
+
/**
|
|
47
|
+
* Process a CSS-in-JS style object to check for position: absolute/fixed and pointer-events
|
|
48
|
+
*/
|
|
49
|
+
function processStyleObject(node) {
|
|
50
|
+
let hasAbsolutePosition = false;
|
|
51
|
+
let pointerEventsValue;
|
|
52
|
+
// Check each property in the style object
|
|
53
|
+
for (const property of node.properties) {
|
|
54
|
+
if (property.type !== utils_1.AST_NODE_TYPES.Property)
|
|
55
|
+
continue;
|
|
56
|
+
let propertyName = '';
|
|
57
|
+
let propertyValue;
|
|
58
|
+
// Get property name
|
|
59
|
+
if (property.key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
60
|
+
propertyName = property.key.name;
|
|
61
|
+
}
|
|
62
|
+
else if (property.key.type === utils_1.AST_NODE_TYPES.Literal && typeof property.key.value === 'string') {
|
|
63
|
+
propertyName = property.key.value;
|
|
64
|
+
}
|
|
65
|
+
// Get property value if it's a string literal
|
|
66
|
+
if (property.value.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
67
|
+
propertyValue = String(property.value.value);
|
|
68
|
+
}
|
|
69
|
+
else if (property.value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
70
|
+
propertyValue = property.value.name;
|
|
71
|
+
}
|
|
72
|
+
// Check if this is position: absolute/fixed
|
|
73
|
+
if (isAbsoluteOrFixedPosition(propertyName, propertyValue)) {
|
|
74
|
+
hasAbsolutePosition = true;
|
|
75
|
+
}
|
|
76
|
+
// Check if this is pointer-events property
|
|
77
|
+
if (isPointerEventsProperty(propertyName)) {
|
|
78
|
+
if (property.value.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
79
|
+
pointerEventsValue = String(property.value.value);
|
|
80
|
+
}
|
|
81
|
+
else if (property.value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
82
|
+
pointerEventsValue = property.value.name;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Store the results for this style object
|
|
87
|
+
absolutePositionedStyles.set(node, hasAbsolutePosition);
|
|
88
|
+
if (pointerEventsValue !== undefined) {
|
|
89
|
+
stylesWithPointerEvents.set(node, pointerEventsValue);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check if a style object needs pointer-events: none
|
|
94
|
+
*/
|
|
95
|
+
function checkStyleObject(node, selector) {
|
|
96
|
+
const isPseudoElement = selector && hasPseudoElementSelector(selector);
|
|
97
|
+
const isAbsolutePositioned = absolutePositionedStyles.get(node) || false;
|
|
98
|
+
const pointerEventsValue = stylesWithPointerEvents.get(node);
|
|
99
|
+
// If this is a pseudo-element with absolute positioning but no pointer-events
|
|
100
|
+
if (isPseudoElement && isAbsolutePositioned && pointerEventsValue === undefined) {
|
|
101
|
+
context.report({
|
|
102
|
+
node,
|
|
103
|
+
messageId: 'missingPointerEventsNone',
|
|
104
|
+
fix(fixer) {
|
|
105
|
+
// Find the last property in the object
|
|
106
|
+
const sourceCode = context.getSourceCode();
|
|
107
|
+
const properties = node.properties;
|
|
108
|
+
if (properties.length === 0)
|
|
109
|
+
return null;
|
|
110
|
+
const lastProperty = properties[properties.length - 1];
|
|
111
|
+
const lastPropertyToken = sourceCode.getLastToken(lastProperty);
|
|
112
|
+
if (lastPropertyToken) {
|
|
113
|
+
return fixer.insertTextAfter(lastPropertyToken, `, pointerEvents: 'none'`);
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// If this is a pseudo-element with absolute positioning and pointer-events: auto
|
|
120
|
+
if (isPseudoElement && isAbsolutePositioned && pointerEventsValue === 'auto') {
|
|
121
|
+
// Don't report an error if pointer-events is explicitly set to 'auto'
|
|
122
|
+
// This is an intentional choice by the developer
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
// Check for pseudo-element selectors in styled-components and similar libraries
|
|
127
|
+
TaggedTemplateExpression(node) {
|
|
128
|
+
// Check if this is a styled-components template
|
|
129
|
+
const tag = node.tag;
|
|
130
|
+
let isStyledComponent = false;
|
|
131
|
+
if (tag.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
132
|
+
tag.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
133
|
+
tag.object.name === 'styled') {
|
|
134
|
+
isStyledComponent = true;
|
|
135
|
+
}
|
|
136
|
+
else if (tag.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
137
|
+
(tag.name === 'styled' || tag.name === 'css')) {
|
|
138
|
+
isStyledComponent = true;
|
|
139
|
+
}
|
|
140
|
+
if (isStyledComponent) {
|
|
141
|
+
// For styled-components, we need to check the template content
|
|
142
|
+
const template = node.quasi.quasis.map(q => q.value.raw).join('');
|
|
143
|
+
// Check if it contains a pseudo-element with position: absolute/fixed
|
|
144
|
+
if (hasPseudoElementSelector(template) &&
|
|
145
|
+
(template.includes('position: absolute') || template.includes('position: fixed'))) {
|
|
146
|
+
// Check if it's missing pointer-events: none
|
|
147
|
+
if (!template.includes('pointer-events: none') && !template.includes('pointer-events:none')) {
|
|
148
|
+
context.report({
|
|
149
|
+
node,
|
|
150
|
+
messageId: 'missingPointerEventsNone'
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
// Process style objects in JSX
|
|
157
|
+
JSXAttribute(node) {
|
|
158
|
+
if (node.name.type !== utils_1.AST_NODE_TYPES.JSXIdentifier || node.name.name !== 'style')
|
|
159
|
+
return;
|
|
160
|
+
if (node.value?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer &&
|
|
161
|
+
node.value.expression.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
162
|
+
processStyleObject(node.value.expression);
|
|
163
|
+
checkStyleObject(node.value.expression);
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
// Process style objects in regular JavaScript/TypeScript
|
|
167
|
+
ObjectExpression(node) {
|
|
168
|
+
// Skip if parent is not a variable declaration or assignment
|
|
169
|
+
const parent = node.parent;
|
|
170
|
+
if (!parent)
|
|
171
|
+
return;
|
|
172
|
+
// Check if this might be a style object
|
|
173
|
+
let isStyleObject = false;
|
|
174
|
+
let selector;
|
|
175
|
+
if (parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
176
|
+
parent.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
177
|
+
/style/i.test(parent.id.name)) {
|
|
178
|
+
isStyleObject = true;
|
|
179
|
+
}
|
|
180
|
+
else if (parent.type === utils_1.AST_NODE_TYPES.Property &&
|
|
181
|
+
parent.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
182
|
+
/style/i.test(parent.key.name)) {
|
|
183
|
+
isStyleObject = true;
|
|
184
|
+
}
|
|
185
|
+
else if (parent.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
186
|
+
// Check for CSS-in-JS libraries like emotion's css() function
|
|
187
|
+
const callee = parent.callee;
|
|
188
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier && callee.name === 'css') {
|
|
189
|
+
isStyleObject = true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (isStyleObject) {
|
|
193
|
+
processStyleObject(node);
|
|
194
|
+
checkStyleObject(node, selector);
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
// Process CSS-in-JS libraries that use objects with selectors
|
|
198
|
+
Property(node) {
|
|
199
|
+
// Check for patterns like { '&::before': { ... } }
|
|
200
|
+
if (node.key.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
201
|
+
typeof node.key.value === 'string' &&
|
|
202
|
+
hasPseudoElementSelector(node.key.value) &&
|
|
203
|
+
node.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
204
|
+
processStyleObject(node.value);
|
|
205
|
+
checkStyleObject(node.value, node.key.value);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
//# sourceMappingURL=ensure-pointer-events-none.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
type MessageIds = 'keyOnlyOutermostElement' | 'fragmentShouldHaveKey';
|
|
2
|
+
export declare const keyOnlyOutermostElement: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
3
|
+
export {};
|