eslint-plugin-absolute 0.1.6 → 0.2.1
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/.absolutejs/eslint.cache.json +49 -0
- package/.absolutejs/prettier.cache.json +49 -0
- package/.absolutejs/tsconfig.tsbuildinfo +1 -0
- package/.claude/settings.local.json +10 -0
- package/dist/index.js +1787 -1457
- package/eslint.config.mjs +107 -0
- package/package.json +15 -12
- package/src/index.ts +45 -0
- package/src/rules/explicit-object-types.ts +75 -0
- package/src/rules/inline-style-limit.ts +88 -0
- package/src/rules/localize-react-props.ts +454 -0
- package/src/rules/max-depth-extended.ts +153 -0
- package/src/rules/{max-jsx-nesting.js → max-jsx-nesting.ts} +37 -38
- package/src/rules/min-var-length.ts +360 -0
- package/src/rules/no-button-navigation.ts +270 -0
- package/src/rules/no-explicit-return-types.ts +83 -0
- package/src/rules/no-inline-prop-types.ts +68 -0
- package/src/rules/no-multi-style-objects.ts +80 -0
- package/src/rules/no-nested-jsx-return.ts +205 -0
- package/src/rules/no-or-none-component.ts +63 -0
- package/src/rules/no-transition-cssproperties.ts +131 -0
- package/src/rules/no-unnecessary-div.ts +65 -0
- package/src/rules/no-unnecessary-key.ts +111 -0
- package/src/rules/no-useless-function.ts +56 -0
- package/src/rules/seperate-style-files.ts +79 -0
- package/src/rules/sort-exports.ts +424 -0
- package/src/rules/sort-keys-fixable.ts +647 -0
- package/src/rules/spring-naming-convention.ts +160 -0
- package/tsconfig.json +4 -1
- package/src/index.js +0 -45
- package/src/rules/explicit-object-types.js +0 -54
- package/src/rules/inline-style-limit.js +0 -77
- package/src/rules/localize-react-props.js +0 -418
- package/src/rules/max-depth-extended.js +0 -124
- package/src/rules/min-var-length.js +0 -300
- package/src/rules/no-button-navigation.js +0 -232
- package/src/rules/no-explicit-return-types.js +0 -64
- package/src/rules/no-inline-prop-types.js +0 -55
- package/src/rules/no-multi-style-objects.js +0 -70
- package/src/rules/no-nested-jsx-return.js +0 -154
- package/src/rules/no-or-none-component.js +0 -50
- package/src/rules/no-transition-cssproperties.js +0 -102
- package/src/rules/no-unnecessary-div.js +0 -40
- package/src/rules/no-unnecessary-key.js +0 -128
- package/src/rules/no-useless-function.js +0 -43
- package/src/rules/seperate-style-files.js +0 -62
- package/src/rules/sort-exports.js +0 -397
- package/src/rules/sort-keys-fixable.js +0 -459
- package/src/rules/spring-naming-convention.js +0 -111
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Disallow variable names shorter than a specified minimum length unless an outer variable
|
|
3
|
-
* with a corresponding longer name exists, with an option to exempt specific variable names.
|
|
4
|
-
*
|
|
5
|
-
* Options:
|
|
6
|
-
* - minLength: a number specifying the minimum allowed length for variable names. Defaults to 1.
|
|
7
|
-
* - allowedVars: an array of variable names that are exempt from the minimum length rule.
|
|
8
|
-
* Each allowed variable name should have a length of at least 1 and at most minLength.
|
|
9
|
-
*
|
|
10
|
-
* This rule checks variable declarations, function parameters, catch clauses,
|
|
11
|
-
* and destructuring patterns. It uses the ESLint v9 APIs (via the scopeManager on the
|
|
12
|
-
* SourceCode object) and a custom helper to get ancestors.
|
|
13
|
-
*/
|
|
14
|
-
export default {
|
|
15
|
-
meta: {
|
|
16
|
-
type: "problem",
|
|
17
|
-
docs: {
|
|
18
|
-
description:
|
|
19
|
-
"Disallow variable names shorter than the configured minimum length unless an outer variable with a longer name starting with the same characters exists. You can exempt specific variable names using the allowedVars option.",
|
|
20
|
-
recommended: false
|
|
21
|
-
},
|
|
22
|
-
schema: [
|
|
23
|
-
{
|
|
24
|
-
type: "object",
|
|
25
|
-
properties: {
|
|
26
|
-
minLength: {
|
|
27
|
-
type: "number",
|
|
28
|
-
default: 1
|
|
29
|
-
},
|
|
30
|
-
allowedVars: {
|
|
31
|
-
type: "array",
|
|
32
|
-
items: {
|
|
33
|
-
type: "string",
|
|
34
|
-
minLength: 1
|
|
35
|
-
// Note: The maxLength for each string should be at most the configured minLength.
|
|
36
|
-
},
|
|
37
|
-
default: []
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
additionalProperties: false
|
|
41
|
-
}
|
|
42
|
-
],
|
|
43
|
-
messages: {
|
|
44
|
-
variableNameTooShort:
|
|
45
|
-
"Variable '{{name}}' is too short. Minimum allowed length is {{minLength}} characters unless an outer variable with a longer name starting with '{{name}}' exists."
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
create(context) {
|
|
50
|
-
const sourceCode = context.getSourceCode();
|
|
51
|
-
const options = context.options[0] || {};
|
|
52
|
-
const minLength =
|
|
53
|
-
typeof options.minLength === "number" ? options.minLength : 1;
|
|
54
|
-
const allowedVars = options.allowedVars || [];
|
|
55
|
-
|
|
56
|
-
// Helper: walk up the node.parent chain to get ancestors.
|
|
57
|
-
function getAncestors(node) {
|
|
58
|
-
const ancestors = [];
|
|
59
|
-
let current = node.parent;
|
|
60
|
-
while (current) {
|
|
61
|
-
ancestors.push(current);
|
|
62
|
-
current = current.parent;
|
|
63
|
-
}
|
|
64
|
-
return ancestors;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Helper: retrieve the scope for a given node using the scopeManager.
|
|
68
|
-
function getScope(node) {
|
|
69
|
-
return (
|
|
70
|
-
sourceCode.scopeManager.acquire(node) ||
|
|
71
|
-
sourceCode.scopeManager.globalScope
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Fallback: get declared variable names in the nearest BlockStatement.
|
|
76
|
-
function getVariablesInNearestBlock(node) {
|
|
77
|
-
let current = node.parent;
|
|
78
|
-
while (current && current.type !== "BlockStatement") {
|
|
79
|
-
current = current.parent;
|
|
80
|
-
}
|
|
81
|
-
const names = [];
|
|
82
|
-
if (current && Array.isArray(current.body)) {
|
|
83
|
-
for (const stmt of current.body) {
|
|
84
|
-
if (stmt.type === "VariableDeclaration") {
|
|
85
|
-
for (const decl of stmt.declarations) {
|
|
86
|
-
if (decl.id && decl.id.type === "Identifier") {
|
|
87
|
-
names.push(decl.id.name);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return names;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Recursively extract identifier names from a pattern (for destructuring)
|
|
98
|
-
* @param {ASTNode} pattern The pattern node.
|
|
99
|
-
* @param {string[]} identifiers Array to accumulate names.
|
|
100
|
-
* @returns {string[]} Array of identifier names.
|
|
101
|
-
*/
|
|
102
|
-
function extractIdentifiersFromPattern(pattern, identifiers = []) {
|
|
103
|
-
if (!pattern) return identifiers;
|
|
104
|
-
switch (pattern.type) {
|
|
105
|
-
case "Identifier":
|
|
106
|
-
identifiers.push(pattern.name);
|
|
107
|
-
break;
|
|
108
|
-
case "ObjectPattern":
|
|
109
|
-
for (const prop of pattern.properties) {
|
|
110
|
-
if (prop.type === "Property") {
|
|
111
|
-
extractIdentifiersFromPattern(
|
|
112
|
-
prop.value,
|
|
113
|
-
identifiers
|
|
114
|
-
);
|
|
115
|
-
} else if (prop.type === "RestElement") {
|
|
116
|
-
extractIdentifiersFromPattern(
|
|
117
|
-
prop.argument,
|
|
118
|
-
identifiers
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
break;
|
|
123
|
-
case "ArrayPattern":
|
|
124
|
-
for (const element of pattern.elements) {
|
|
125
|
-
if (element)
|
|
126
|
-
extractIdentifiersFromPattern(element, identifiers);
|
|
127
|
-
}
|
|
128
|
-
break;
|
|
129
|
-
case "AssignmentPattern":
|
|
130
|
-
extractIdentifiersFromPattern(pattern.left, identifiers);
|
|
131
|
-
break;
|
|
132
|
-
default:
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
return identifiers;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Checks if there is an outer variable whose name is longer than the current short name
|
|
140
|
-
* and starts with the same characters.
|
|
141
|
-
* It first uses the scope manager; if that doesn’t yield a match,
|
|
142
|
-
* it falls back on scanning the nearest block and ancestors.
|
|
143
|
-
* @param {string} shortName The variable name that is shorter than minLength.
|
|
144
|
-
* @param {ASTNode} node The current identifier node.
|
|
145
|
-
* @returns {boolean} True if an outer variable is found.
|
|
146
|
-
*/
|
|
147
|
-
function hasOuterCorrespondingIdentifier(shortName, node) {
|
|
148
|
-
// First, try using the scope manager.
|
|
149
|
-
let currentScope = getScope(node);
|
|
150
|
-
let outer = currentScope.upper;
|
|
151
|
-
while (outer) {
|
|
152
|
-
for (const variable of outer.variables) {
|
|
153
|
-
if (
|
|
154
|
-
variable.name.length >= minLength &&
|
|
155
|
-
variable.name.length > shortName.length &&
|
|
156
|
-
variable.name.startsWith(shortName)
|
|
157
|
-
) {
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
outer = outer.upper;
|
|
162
|
-
}
|
|
163
|
-
// Fallback: scan the nearest BlockStatement.
|
|
164
|
-
const blockVars = getVariablesInNearestBlock(node);
|
|
165
|
-
for (const name of blockVars) {
|
|
166
|
-
if (
|
|
167
|
-
name.length >= minLength &&
|
|
168
|
-
name.length > shortName.length &&
|
|
169
|
-
name.startsWith(shortName)
|
|
170
|
-
) {
|
|
171
|
-
return true;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
// Fallback: scan ancestors.
|
|
175
|
-
const ancestors = getAncestors(node);
|
|
176
|
-
for (const anc of ancestors) {
|
|
177
|
-
if (
|
|
178
|
-
anc.type === "VariableDeclarator" &&
|
|
179
|
-
anc.id &&
|
|
180
|
-
anc.id.type === "Identifier"
|
|
181
|
-
) {
|
|
182
|
-
const outerName = anc.id.name;
|
|
183
|
-
if (
|
|
184
|
-
outerName.length >= minLength &&
|
|
185
|
-
outerName.length > shortName.length &&
|
|
186
|
-
outerName.startsWith(shortName)
|
|
187
|
-
) {
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if (
|
|
192
|
-
(anc.type === "FunctionDeclaration" ||
|
|
193
|
-
anc.type === "FunctionExpression" ||
|
|
194
|
-
anc.type === "ArrowFunctionExpression") &&
|
|
195
|
-
Array.isArray(anc.params)
|
|
196
|
-
) {
|
|
197
|
-
for (const param of anc.params) {
|
|
198
|
-
const names = extractIdentifiersFromPattern(param, []);
|
|
199
|
-
for (const n of names) {
|
|
200
|
-
if (
|
|
201
|
-
n.length >= minLength &&
|
|
202
|
-
n.length > shortName.length &&
|
|
203
|
-
n.startsWith(shortName)
|
|
204
|
-
) {
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
if (anc.type === "CatchClause" && anc.param) {
|
|
211
|
-
const names = extractIdentifiersFromPattern(anc.param, []);
|
|
212
|
-
for (const n of names) {
|
|
213
|
-
if (
|
|
214
|
-
n.length >= minLength &&
|
|
215
|
-
n.length > shortName.length &&
|
|
216
|
-
n.startsWith(shortName)
|
|
217
|
-
) {
|
|
218
|
-
return true;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Checks an Identifier node. If its name is shorter than minLength (and not in the allowed list)
|
|
228
|
-
* and no outer variable with a longer name starting with the short name is found, it reports an error.
|
|
229
|
-
* @param {ASTNode} node The Identifier node.
|
|
230
|
-
*/
|
|
231
|
-
function checkIdentifier(node) {
|
|
232
|
-
const name = node.name;
|
|
233
|
-
if (typeof name === "string" && name.length < minLength) {
|
|
234
|
-
// If the name is in the allowed list, skip.
|
|
235
|
-
if (allowedVars.includes(name)) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
if (!hasOuterCorrespondingIdentifier(name, node)) {
|
|
239
|
-
context.report({
|
|
240
|
-
node,
|
|
241
|
-
messageId: "variableNameTooShort",
|
|
242
|
-
data: { name, minLength }
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Recursively checks a pattern node for identifiers.
|
|
250
|
-
* @param {ASTNode} pattern The pattern node.
|
|
251
|
-
*/
|
|
252
|
-
function checkPattern(pattern) {
|
|
253
|
-
if (!pattern) return;
|
|
254
|
-
switch (pattern.type) {
|
|
255
|
-
case "Identifier":
|
|
256
|
-
checkIdentifier(pattern);
|
|
257
|
-
break;
|
|
258
|
-
case "ObjectPattern":
|
|
259
|
-
for (const prop of pattern.properties) {
|
|
260
|
-
if (prop.type === "Property") {
|
|
261
|
-
checkPattern(prop.value);
|
|
262
|
-
} else if (prop.type === "RestElement") {
|
|
263
|
-
checkPattern(prop.argument);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
break;
|
|
267
|
-
case "ArrayPattern":
|
|
268
|
-
for (const element of pattern.elements) {
|
|
269
|
-
if (element) checkPattern(element);
|
|
270
|
-
}
|
|
271
|
-
break;
|
|
272
|
-
case "AssignmentPattern":
|
|
273
|
-
checkPattern(pattern.left);
|
|
274
|
-
break;
|
|
275
|
-
default:
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return {
|
|
281
|
-
VariableDeclarator(node) {
|
|
282
|
-
if (node.id) {
|
|
283
|
-
checkPattern(node.id);
|
|
284
|
-
}
|
|
285
|
-
},
|
|
286
|
-
"FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(
|
|
287
|
-
node
|
|
288
|
-
) {
|
|
289
|
-
for (const param of node.params) {
|
|
290
|
-
checkPattern(param);
|
|
291
|
-
}
|
|
292
|
-
},
|
|
293
|
-
CatchClause(node) {
|
|
294
|
-
if (node.param) {
|
|
295
|
-
checkPattern(node.param);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
};
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
meta: {
|
|
3
|
-
type: "suggestion",
|
|
4
|
-
docs: {
|
|
5
|
-
description:
|
|
6
|
-
"Enforce using anchor tags for navigation instead of buttons whose onClick handlers change the path. Allow only query/hash updates via window.location.search or history.replaceState(window.location.pathname + …).",
|
|
7
|
-
category: "Best Practices",
|
|
8
|
-
recommended: false
|
|
9
|
-
},
|
|
10
|
-
schema: []
|
|
11
|
-
},
|
|
12
|
-
create(context) {
|
|
13
|
-
/**
|
|
14
|
-
* Returns true if the given AST subtree contains a MemberExpression of
|
|
15
|
-
* the form `window.location.pathname`, `window.location.search`, or `window.location.hash`.
|
|
16
|
-
*/
|
|
17
|
-
function urlUsesAllowedLocation(argNode) {
|
|
18
|
-
let allowed = false;
|
|
19
|
-
const visited = new WeakSet();
|
|
20
|
-
|
|
21
|
-
function check(n) {
|
|
22
|
-
if (allowed || !n || typeof n !== "object" || visited.has(n))
|
|
23
|
-
return;
|
|
24
|
-
visited.add(n);
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
-
n.type === "MemberExpression" &&
|
|
28
|
-
n.object.type === "MemberExpression" &&
|
|
29
|
-
n.object.object.type === "Identifier" &&
|
|
30
|
-
n.object.object.name === "window" &&
|
|
31
|
-
n.object.property.type === "Identifier" &&
|
|
32
|
-
n.object.property.name === "location" &&
|
|
33
|
-
n.property.type === "Identifier" &&
|
|
34
|
-
(n.property.name === "pathname" ||
|
|
35
|
-
n.property.name === "search" ||
|
|
36
|
-
n.property.name === "hash")
|
|
37
|
-
) {
|
|
38
|
-
allowed = true;
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
for (const key of Object.keys(n)) {
|
|
43
|
-
if (key === "parent") continue;
|
|
44
|
-
const child = n[key];
|
|
45
|
-
if (Array.isArray(child)) {
|
|
46
|
-
child.forEach((c) => check(c));
|
|
47
|
-
} else {
|
|
48
|
-
check(child);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
check(argNode);
|
|
54
|
-
return allowed;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Returns an object { shouldReport, reason } after inspecting the
|
|
59
|
-
* function body for forbidden patterns. - shouldReport is true if
|
|
60
|
-
* we must flag. - reason explains which pattern was found.
|
|
61
|
-
*/
|
|
62
|
-
function containsWindowNavigation(node) {
|
|
63
|
-
let reason = null;
|
|
64
|
-
const visited = new WeakSet();
|
|
65
|
-
let sawReplaceCall = false;
|
|
66
|
-
let sawAllowedLocationRead = false;
|
|
67
|
-
|
|
68
|
-
function inspect(n, parent) {
|
|
69
|
-
if (reason || !n || typeof n !== "object" || visited.has(n))
|
|
70
|
-
return;
|
|
71
|
-
visited.add(n);
|
|
72
|
-
|
|
73
|
-
// 1) window.open(...)
|
|
74
|
-
if (
|
|
75
|
-
n.type === "MemberExpression" &&
|
|
76
|
-
n.object.type === "Identifier" &&
|
|
77
|
-
n.object.name === "window" &&
|
|
78
|
-
n.property.type === "Identifier" &&
|
|
79
|
-
n.property.name === "open"
|
|
80
|
-
) {
|
|
81
|
-
reason = "window.open";
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 2) Assignment to window.location or window.location.*
|
|
86
|
-
if (
|
|
87
|
-
n.type === "AssignmentExpression" &&
|
|
88
|
-
n.left.type === "MemberExpression"
|
|
89
|
-
) {
|
|
90
|
-
const left = n.left;
|
|
91
|
-
|
|
92
|
-
// window.location = ...
|
|
93
|
-
if (
|
|
94
|
-
left.object.type === "Identifier" &&
|
|
95
|
-
left.object.name === "window" &&
|
|
96
|
-
left.property.type === "Identifier" &&
|
|
97
|
-
left.property.name === "location"
|
|
98
|
-
) {
|
|
99
|
-
reason = "assignment to window.location";
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// window.location.href = ... OR window.location.pathname =
|
|
104
|
-
if (
|
|
105
|
-
left.object.type === "MemberExpression" &&
|
|
106
|
-
left.object.object.type === "Identifier" &&
|
|
107
|
-
left.object.object.name === "window" &&
|
|
108
|
-
left.object.property.type === "Identifier" &&
|
|
109
|
-
left.object.property.name === "location"
|
|
110
|
-
) {
|
|
111
|
-
reason = "assignment to window.location sub-property";
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// 3) window.location.replace(...) (or any call on window.location besides .search/.hash)
|
|
117
|
-
if (
|
|
118
|
-
n.type === "MemberExpression" &&
|
|
119
|
-
n.object.type === "MemberExpression" &&
|
|
120
|
-
n.object.object.type === "Identifier" &&
|
|
121
|
-
n.object.object.name === "window" &&
|
|
122
|
-
n.object.property.type === "Identifier" &&
|
|
123
|
-
n.object.property.name === "location" &&
|
|
124
|
-
n.property.type === "Identifier" &&
|
|
125
|
-
n.property.name === "replace"
|
|
126
|
-
) {
|
|
127
|
-
if (parent && parent.type === "CallExpression") {
|
|
128
|
-
reason = "window.location.replace";
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 4) window.history.pushState(...) or replaceState(...)
|
|
134
|
-
if (
|
|
135
|
-
n.type === "MemberExpression" &&
|
|
136
|
-
n.object.type === "MemberExpression" &&
|
|
137
|
-
n.object.object.type === "Identifier" &&
|
|
138
|
-
n.object.object.name === "window" &&
|
|
139
|
-
n.object.property.type === "Identifier" &&
|
|
140
|
-
n.object.property.name === "history" &&
|
|
141
|
-
n.property.type === "Identifier" &&
|
|
142
|
-
(n.property.name === "pushState" ||
|
|
143
|
-
n.property.name === "replaceState")
|
|
144
|
-
) {
|
|
145
|
-
sawReplaceCall = true;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// 5) Reading window.location.search, .pathname, or .hash
|
|
149
|
-
if (
|
|
150
|
-
n.type === "MemberExpression" &&
|
|
151
|
-
n.object.type === "MemberExpression" &&
|
|
152
|
-
n.object.object.type === "Identifier" &&
|
|
153
|
-
n.object.object.name === "window" &&
|
|
154
|
-
n.object.property.type === "Identifier" &&
|
|
155
|
-
n.object.property.name === "location" &&
|
|
156
|
-
n.property.type === "Identifier" &&
|
|
157
|
-
(n.property.name === "search" ||
|
|
158
|
-
n.property.name === "pathname" ||
|
|
159
|
-
n.property.name === "hash")
|
|
160
|
-
) {
|
|
161
|
-
sawAllowedLocationRead = true;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Recurse into children
|
|
165
|
-
for (const key of Object.keys(n)) {
|
|
166
|
-
if (key === "parent" || reason) continue;
|
|
167
|
-
const child = n[key];
|
|
168
|
-
if (Array.isArray(child)) {
|
|
169
|
-
child.forEach((c) => inspect(c, n));
|
|
170
|
-
} else {
|
|
171
|
-
inspect(child, n);
|
|
172
|
-
}
|
|
173
|
-
if (reason) return;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
inspect(
|
|
178
|
-
node.type === "ArrowFunctionExpression" ||
|
|
179
|
-
node.type === "FunctionExpression"
|
|
180
|
-
? node.body
|
|
181
|
-
: node,
|
|
182
|
-
null
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
if (reason) {
|
|
186
|
-
return { shouldReport: true, reason };
|
|
187
|
-
}
|
|
188
|
-
if (sawReplaceCall && !sawAllowedLocationRead) {
|
|
189
|
-
return {
|
|
190
|
-
shouldReport: true,
|
|
191
|
-
reason: "history.replace/pushState without reading window.location"
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
return { shouldReport: false, reason: null };
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
JSXElement(node) {
|
|
199
|
-
const { openingElement } = node;
|
|
200
|
-
if (
|
|
201
|
-
openingElement.name.type === "JSXIdentifier" &&
|
|
202
|
-
openingElement.name.name === "button"
|
|
203
|
-
) {
|
|
204
|
-
for (const attr of openingElement.attributes) {
|
|
205
|
-
if (
|
|
206
|
-
attr.type === "JSXAttribute" &&
|
|
207
|
-
attr.name.name === "onClick" &&
|
|
208
|
-
attr.value?.type === "JSXExpressionContainer"
|
|
209
|
-
) {
|
|
210
|
-
const expr = attr.value.expression;
|
|
211
|
-
if (
|
|
212
|
-
expr.type === "ArrowFunctionExpression" ||
|
|
213
|
-
expr.type === "FunctionExpression"
|
|
214
|
-
) {
|
|
215
|
-
const { shouldReport, reason } =
|
|
216
|
-
containsWindowNavigation(expr);
|
|
217
|
-
if (shouldReport) {
|
|
218
|
-
context.report({
|
|
219
|
-
node: attr,
|
|
220
|
-
message:
|
|
221
|
-
`Use an anchor tag for navigation instead of a button whose onClick handler changes the path. ` +
|
|
222
|
-
`Detected: ${reason}. Only query/hash updates (reading window.location.search, .pathname, or .hash) are allowed.`
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
};
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
meta: {
|
|
3
|
-
type: "suggestion",
|
|
4
|
-
docs: {
|
|
5
|
-
description:
|
|
6
|
-
"Disallow explicit return type annotations on functions, except when using type predicates for type guards or inline object literal returns (e.g., style objects).",
|
|
7
|
-
recommended: false
|
|
8
|
-
},
|
|
9
|
-
schema: [],
|
|
10
|
-
messages: {
|
|
11
|
-
noExplicitReturnType:
|
|
12
|
-
"Explicit return types are disallowed; rely on TypeScript's inference instead."
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
|
|
16
|
-
create(context) {
|
|
17
|
-
return {
|
|
18
|
-
"FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(
|
|
19
|
-
node
|
|
20
|
-
) {
|
|
21
|
-
if (node.returnType) {
|
|
22
|
-
// Allow type predicate annotations for type guards.
|
|
23
|
-
const typeAnnotation = node.returnType.typeAnnotation;
|
|
24
|
-
if (
|
|
25
|
-
typeAnnotation &&
|
|
26
|
-
typeAnnotation.type === "TSTypePredicate"
|
|
27
|
-
) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Allow if it's an arrow function that directly returns an object literal.
|
|
32
|
-
if (
|
|
33
|
-
node.type === "ArrowFunctionExpression" &&
|
|
34
|
-
node.expression &&
|
|
35
|
-
node.body &&
|
|
36
|
-
node.body.type === "ObjectExpression"
|
|
37
|
-
) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Allow if the function has a block body with a single return statement that returns an object literal.
|
|
42
|
-
if (node.body && node.body.type === "BlockStatement") {
|
|
43
|
-
const returns = node.body.body.filter(
|
|
44
|
-
(stmt) => stmt.type === "ReturnStatement"
|
|
45
|
-
);
|
|
46
|
-
if (
|
|
47
|
-
returns.length === 1 &&
|
|
48
|
-
returns[0].argument &&
|
|
49
|
-
returns[0].argument.type === "ObjectExpression"
|
|
50
|
-
) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Otherwise, report an error.
|
|
56
|
-
context.report({
|
|
57
|
-
node: node.returnType,
|
|
58
|
-
messageId: "noExplicitReturnType"
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
meta: {
|
|
3
|
-
type: "suggestion",
|
|
4
|
-
docs: {
|
|
5
|
-
description:
|
|
6
|
-
"Enforce that component prop types are not defined inline (using an object literal) but rather use a named type or interface.",
|
|
7
|
-
category: "Best Practices",
|
|
8
|
-
recommended: false
|
|
9
|
-
},
|
|
10
|
-
schema: [],
|
|
11
|
-
messages: {
|
|
12
|
-
noInlinePropTypes:
|
|
13
|
-
"Inline prop type definitions are not allowed. Use a named type alias or interface instead of an inline object type."
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
create(context) {
|
|
18
|
-
/**
|
|
19
|
-
* Checks the node representing a parameter to determine if it is an ObjectPattern with an inline type literal.
|
|
20
|
-
* @param {ASTNode} param The parameter node from the function declaration/expression.
|
|
21
|
-
*/
|
|
22
|
-
function checkParameter(param) {
|
|
23
|
-
// Ensure we are dealing with a destructured object pattern with a type annotation.
|
|
24
|
-
if (
|
|
25
|
-
param &&
|
|
26
|
-
param.type === "ObjectPattern" &&
|
|
27
|
-
param.typeAnnotation &&
|
|
28
|
-
param.typeAnnotation.type === "TSTypeAnnotation"
|
|
29
|
-
) {
|
|
30
|
-
// The actual type annotation node (for example, { mode: string } yields a TSTypeLiteral).
|
|
31
|
-
const annotation = param.typeAnnotation.typeAnnotation;
|
|
32
|
-
// If the type is an inline object (TSTypeLiteral), we want to report it.
|
|
33
|
-
if (annotation.type === "TSTypeLiteral") {
|
|
34
|
-
context.report({
|
|
35
|
-
node: param,
|
|
36
|
-
messageId: "noInlinePropTypes"
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
// Applies to FunctionDeclaration, ArrowFunctionExpression, and FunctionExpression nodes.
|
|
44
|
-
"FunctionDeclaration, ArrowFunctionExpression, FunctionExpression"(
|
|
45
|
-
node
|
|
46
|
-
) {
|
|
47
|
-
// It is common to define props as the first parameter.
|
|
48
|
-
const firstParam = node.params[0];
|
|
49
|
-
if (firstParam) {
|
|
50
|
-
checkParameter(firstParam);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
};
|