eslint-plugin-absolute 0.0.2 → 0.0.3
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/dist/index.js +18 -34
- package/package.json +1 -1
- package/src/rules/no-button-navigation.js +38 -65
package/dist/index.js
CHANGED
|
@@ -1203,51 +1203,35 @@ var no_button_navigation_default = {
|
|
|
1203
1203
|
create(context) {
|
|
1204
1204
|
function containsWindowNavigation(node) {
|
|
1205
1205
|
let found = false;
|
|
1206
|
-
const visited = new WeakSet;
|
|
1207
1206
|
function inspect(n) {
|
|
1208
|
-
if (!n ||
|
|
1207
|
+
if (found || !n || typeof n !== "object")
|
|
1209
1208
|
return;
|
|
1210
|
-
if (
|
|
1209
|
+
if (n.type === "MemberExpression" && n.object.type === "Identifier" && n.object.name === "window" && n.property.type === "Identifier" && (n.property.name === "open" || n.property.name === "location")) {
|
|
1210
|
+
found = true;
|
|
1211
1211
|
return;
|
|
1212
|
-
if (visited.has(n))
|
|
1213
|
-
return;
|
|
1214
|
-
visited.add(n);
|
|
1215
|
-
if (n.type === "MemberExpression") {
|
|
1216
|
-
if (n.object && n.object.type === "Identifier" && n.object.name === "window" && n.property && (n.property.type === "Identifier" && (n.property.name === "location" || n.property.name === "open") || n.property.type === "Literal" && (n.property.value === "location" || n.property.value === "open"))) {
|
|
1217
|
-
found = true;
|
|
1218
|
-
return;
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
if (n.type === "CallExpression" && n.callee && n.callee.type === "MemberExpression") {
|
|
1222
|
-
const callee = n.callee;
|
|
1223
|
-
if (callee.object && callee.object.type === "Identifier" && callee.object.name === "window" && callee.property && callee.property.type === "Identifier" && callee.property.name === "open") {
|
|
1224
|
-
found = true;
|
|
1225
|
-
return;
|
|
1226
|
-
}
|
|
1227
1212
|
}
|
|
1228
|
-
for (const key
|
|
1229
|
-
if (
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1213
|
+
for (const key of Object.keys(n)) {
|
|
1214
|
+
if (key === "parent")
|
|
1215
|
+
continue;
|
|
1216
|
+
const child = n[key];
|
|
1217
|
+
if (Array.isArray(child)) {
|
|
1218
|
+
child.forEach(inspect);
|
|
1219
|
+
} else {
|
|
1220
|
+
inspect(child);
|
|
1236
1221
|
}
|
|
1237
1222
|
}
|
|
1238
1223
|
}
|
|
1239
|
-
inspect(node);
|
|
1224
|
+
inspect(node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression" ? node.body : node);
|
|
1240
1225
|
return found;
|
|
1241
1226
|
}
|
|
1242
1227
|
return {
|
|
1243
1228
|
JSXElement(node) {
|
|
1244
|
-
const openingElement = node
|
|
1245
|
-
if (openingElement.name
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
if (containsWindowNavigation(expression)) {
|
|
1229
|
+
const { openingElement } = node;
|
|
1230
|
+
if (openingElement.name.type === "JSXIdentifier" && openingElement.name.name === "button") {
|
|
1231
|
+
for (const attr of openingElement.attributes) {
|
|
1232
|
+
if (attr.type === "JSXAttribute" && attr.name.name === "onClick" && attr.value?.type === "JSXExpressionContainer") {
|
|
1233
|
+
const expr = attr.value.expression;
|
|
1234
|
+
if ((expr.type === "ArrowFunctionExpression" || expr.type === "FunctionExpression") && containsWindowNavigation(expr)) {
|
|
1251
1235
|
context.report({
|
|
1252
1236
|
node: attr,
|
|
1253
1237
|
message: "Use an anchor tag for navigation instead of a button with an onClick handler that uses window navigation methods."
|
package/package.json
CHANGED
|
@@ -11,94 +11,67 @@ export default {
|
|
|
11
11
|
},
|
|
12
12
|
create(context) {
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* @param {ASTNode} node - The node to inspect.
|
|
17
|
-
* @returns {boolean} True if a window navigation reference is found.
|
|
14
|
+
* Inspects an AST node *only* for MemberExpressions where
|
|
15
|
+
* the object is literally `window` and the property is `open` or `location`.
|
|
18
16
|
*/
|
|
19
17
|
function containsWindowNavigation(node) {
|
|
20
18
|
let found = false;
|
|
21
|
-
const visited = new WeakSet();
|
|
22
19
|
function inspect(n) {
|
|
23
|
-
if (!n ||
|
|
24
|
-
|
|
25
|
-
if (visited.has(n)) return;
|
|
26
|
-
visited.add(n);
|
|
27
|
-
|
|
28
|
-
// Check for MemberExpressions like window.location or window.open
|
|
29
|
-
if (n.type === "MemberExpression") {
|
|
30
|
-
if (
|
|
31
|
-
n.object &&
|
|
32
|
-
n.object.type === "Identifier" &&
|
|
33
|
-
n.object.name === "window" &&
|
|
34
|
-
n.property &&
|
|
35
|
-
((n.property.type === "Identifier" &&
|
|
36
|
-
(n.property.name === "location" ||
|
|
37
|
-
n.property.name === "open")) ||
|
|
38
|
-
(n.property.type === "Literal" &&
|
|
39
|
-
(n.property.value === "location" ||
|
|
40
|
-
n.property.value === "open")))
|
|
41
|
-
) {
|
|
42
|
-
found = true;
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
// Check for CallExpressions like window.open()
|
|
20
|
+
if (found || !n || typeof n !== "object") return;
|
|
21
|
+
// Only match MemberExpressions on the global window identifier
|
|
47
22
|
if (
|
|
48
|
-
n.type === "
|
|
49
|
-
n.
|
|
50
|
-
n.
|
|
23
|
+
n.type === "MemberExpression" &&
|
|
24
|
+
n.object.type === "Identifier" &&
|
|
25
|
+
n.object.name === "window" &&
|
|
26
|
+
n.property.type === "Identifier" &&
|
|
27
|
+
(n.property.name === "open" ||
|
|
28
|
+
n.property.name === "location")
|
|
51
29
|
) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
callee.object &&
|
|
55
|
-
callee.object.type === "Identifier" &&
|
|
56
|
-
callee.object.name === "window" &&
|
|
57
|
-
callee.property &&
|
|
58
|
-
callee.property.type === "Identifier" &&
|
|
59
|
-
callee.property.name === "open"
|
|
60
|
-
) {
|
|
61
|
-
found = true;
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
30
|
+
found = true;
|
|
31
|
+
return;
|
|
64
32
|
}
|
|
65
|
-
//
|
|
66
|
-
for (const key
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
33
|
+
// recurse into children—but skip walking back up via `parent`
|
|
34
|
+
for (const key of Object.keys(n)) {
|
|
35
|
+
if (key === "parent") continue;
|
|
36
|
+
const child = n[key];
|
|
37
|
+
if (Array.isArray(child)) {
|
|
38
|
+
child.forEach(inspect);
|
|
39
|
+
} else {
|
|
40
|
+
inspect(child);
|
|
74
41
|
}
|
|
75
42
|
}
|
|
76
43
|
}
|
|
77
|
-
|
|
44
|
+
// If it's a function, start at its body; otherwise start at the node itself
|
|
45
|
+
inspect(
|
|
46
|
+
node.type === "ArrowFunctionExpression" ||
|
|
47
|
+
node.type === "FunctionExpression"
|
|
48
|
+
? node.body
|
|
49
|
+
: node
|
|
50
|
+
);
|
|
78
51
|
return found;
|
|
79
52
|
}
|
|
80
53
|
|
|
81
54
|
return {
|
|
82
55
|
JSXElement(node) {
|
|
83
|
-
const openingElement = node
|
|
84
|
-
//
|
|
56
|
+
const { openingElement } = node;
|
|
57
|
+
// only care about <button ...>
|
|
85
58
|
if (
|
|
86
|
-
openingElement.name &&
|
|
87
59
|
openingElement.name.type === "JSXIdentifier" &&
|
|
88
60
|
openingElement.name.name === "button"
|
|
89
61
|
) {
|
|
90
|
-
|
|
91
|
-
const attributes = openingElement.attributes;
|
|
92
|
-
for (const attr of attributes) {
|
|
62
|
+
for (const attr of openingElement.attributes) {
|
|
93
63
|
if (
|
|
94
64
|
attr.type === "JSXAttribute" &&
|
|
95
|
-
attr.name &&
|
|
96
65
|
attr.name.name === "onClick" &&
|
|
97
|
-
attr.value
|
|
98
|
-
attr.value.type === "JSXExpressionContainer"
|
|
66
|
+
attr.value?.type === "JSXExpressionContainer"
|
|
99
67
|
) {
|
|
100
|
-
const
|
|
101
|
-
|
|
68
|
+
const expr = attr.value.expression;
|
|
69
|
+
// only inspect the inline function, not any Identifier calls
|
|
70
|
+
if (
|
|
71
|
+
(expr.type === "ArrowFunctionExpression" ||
|
|
72
|
+
expr.type === "FunctionExpression") &&
|
|
73
|
+
containsWindowNavigation(expr)
|
|
74
|
+
) {
|
|
102
75
|
context.report({
|
|
103
76
|
node: attr,
|
|
104
77
|
message:
|