eslint-plugin-absolute 0.1.2 → 0.1.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 +19 -37
- package/package.json +1 -1
- package/src/rules/no-button-navigation.js +41 -64
package/dist/index.js
CHANGED
|
@@ -1163,39 +1163,38 @@ var no_button_navigation_default = {
|
|
|
1163
1163
|
},
|
|
1164
1164
|
create(context) {
|
|
1165
1165
|
function containsWindowNavigation(node) {
|
|
1166
|
-
let
|
|
1166
|
+
let hasReplaceCall = false;
|
|
1167
|
+
let hasAllowedLocationRead = false;
|
|
1168
|
+
let hasBadLocationChange = false;
|
|
1167
1169
|
function inspect(n, parent) {
|
|
1168
|
-
if (
|
|
1170
|
+
if (hasBadLocationChange)
|
|
1169
1171
|
return;
|
|
1170
1172
|
if (n.type === "MemberExpression" && n.object.type === "Identifier" && n.object.name === "window" && n.property.type === "Identifier" && n.property.name === "open") {
|
|
1171
|
-
|
|
1173
|
+
hasBadLocationChange = true;
|
|
1172
1174
|
return;
|
|
1173
1175
|
}
|
|
1174
1176
|
if (n.type === "AssignmentExpression" && n.left.type === "MemberExpression") {
|
|
1175
1177
|
const left = n.left;
|
|
1176
1178
|
if (left.object.type === "Identifier" && left.object.name === "window" && left.property.type === "Identifier" && left.property.name === "location") {
|
|
1177
|
-
|
|
1179
|
+
hasBadLocationChange = true;
|
|
1178
1180
|
return;
|
|
1179
1181
|
}
|
|
1180
1182
|
if (left.object.type === "MemberExpression" && left.object.object.type === "Identifier" && left.object.object.name === "window" && left.object.property.type === "Identifier" && left.object.property.name === "location") {
|
|
1181
|
-
|
|
1183
|
+
hasBadLocationChange = true;
|
|
1182
1184
|
return;
|
|
1183
1185
|
}
|
|
1184
1186
|
}
|
|
1185
1187
|
if (n.type === "MemberExpression" && n.object.type === "MemberExpression" && n.object.object.type === "Identifier" && n.object.object.name === "window" && n.object.property.type === "Identifier" && n.object.property.name === "location" && n.property.type === "Identifier" && n.property.name === "replace") {
|
|
1186
1188
|
if (parent && parent.type === "CallExpression") {
|
|
1187
|
-
|
|
1189
|
+
hasBadLocationChange = true;
|
|
1188
1190
|
return;
|
|
1189
1191
|
}
|
|
1190
1192
|
}
|
|
1191
1193
|
if (n.type === "MemberExpression" && n.object.type === "MemberExpression" && n.object.object.type === "Identifier" && n.object.object.name === "window" && n.object.property.type === "Identifier" && n.object.property.name === "history" && n.property.type === "Identifier" && (n.property.name === "pushState" || n.property.name === "replaceState")) {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
return;
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1194
|
+
hasReplaceCall = true;
|
|
1195
|
+
}
|
|
1196
|
+
if (n.type === "MemberExpression" && n.object.type === "MemberExpression" && n.object.object.type === "Identifier" && n.object.object.name === "window" && n.object.property.type === "Identifier" && n.object.property.name === "location" && n.property.type === "Identifier" && (n.property.name === "search" || n.property.name === "pathname" || n.property.name === "hash")) {
|
|
1197
|
+
hasAllowedLocationRead = true;
|
|
1199
1198
|
}
|
|
1200
1199
|
for (const key of Object.keys(n)) {
|
|
1201
1200
|
if (key === "parent")
|
|
@@ -1206,33 +1205,16 @@ var no_button_navigation_default = {
|
|
|
1206
1205
|
} else {
|
|
1207
1206
|
inspect(child, n);
|
|
1208
1207
|
}
|
|
1209
|
-
|
|
1210
|
-
}
|
|
1211
|
-
function urlUsesAllowedLocation(argNode) {
|
|
1212
|
-
let allowed = false;
|
|
1213
|
-
function check(n) {
|
|
1214
|
-
if (allowed || !n || typeof n !== "object")
|
|
1215
|
-
return;
|
|
1216
|
-
if (n.type === "MemberExpression" && n.object.type === "MemberExpression" && n.object.object.type === "Identifier" && n.object.object.name === "window" && n.object.property.type === "Identifier" && n.object.property.name === "location" && n.property.type === "Identifier" && (n.property.name === "pathname" || n.property.name === "search" || n.property.name === "hash")) {
|
|
1217
|
-
allowed = true;
|
|
1208
|
+
if (hasBadLocationChange)
|
|
1218
1209
|
return;
|
|
1219
|
-
}
|
|
1220
|
-
for (const key of Object.keys(n)) {
|
|
1221
|
-
if (key === "parent")
|
|
1222
|
-
continue;
|
|
1223
|
-
const c = n[key];
|
|
1224
|
-
if (Array.isArray(c)) {
|
|
1225
|
-
c.forEach(check);
|
|
1226
|
-
} else {
|
|
1227
|
-
check(c);
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
1210
|
}
|
|
1231
|
-
check(argNode);
|
|
1232
|
-
return allowed;
|
|
1233
1211
|
}
|
|
1234
1212
|
inspect(node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression" ? node.body : node, null);
|
|
1235
|
-
|
|
1213
|
+
if (hasBadLocationChange)
|
|
1214
|
+
return true;
|
|
1215
|
+
if (hasReplaceCall && !hasAllowedLocationRead)
|
|
1216
|
+
return true;
|
|
1217
|
+
return false;
|
|
1236
1218
|
}
|
|
1237
1219
|
return {
|
|
1238
1220
|
JSXElement(node) {
|
|
@@ -1244,7 +1226,7 @@ var no_button_navigation_default = {
|
|
|
1244
1226
|
if ((expr.type === "ArrowFunctionExpression" || expr.type === "FunctionExpression") && containsWindowNavigation(expr)) {
|
|
1245
1227
|
context.report({
|
|
1246
1228
|
node: attr,
|
|
1247
|
-
message: "Use an anchor tag for navigation instead of a button with an onClick handler that changes the path. Only query/hash updates are allowed."
|
|
1229
|
+
message: "Use an anchor tag for navigation instead of a button with an onClick handler that changes the path. Only query/hash updates (reading window.location.search, .pathname, or .hash) are allowed."
|
|
1248
1230
|
});
|
|
1249
1231
|
}
|
|
1250
1232
|
}
|
package/package.json
CHANGED
|
@@ -11,12 +11,14 @@ export default {
|
|
|
11
11
|
},
|
|
12
12
|
create(context) {
|
|
13
13
|
function containsWindowNavigation(node) {
|
|
14
|
-
let
|
|
14
|
+
let hasReplaceCall = false;
|
|
15
|
+
let hasAllowedLocationRead = false;
|
|
16
|
+
let hasBadLocationChange = false;
|
|
15
17
|
|
|
16
18
|
function inspect(n, parent) {
|
|
17
|
-
if (
|
|
19
|
+
if (hasBadLocationChange) return;
|
|
18
20
|
|
|
19
|
-
//
|
|
21
|
+
// window.open(...) → always bad
|
|
20
22
|
if (
|
|
21
23
|
n.type === "MemberExpression" &&
|
|
22
24
|
n.object.type === "Identifier" &&
|
|
@@ -24,11 +26,11 @@ export default {
|
|
|
24
26
|
n.property.type === "Identifier" &&
|
|
25
27
|
n.property.name === "open"
|
|
26
28
|
) {
|
|
27
|
-
|
|
29
|
+
hasBadLocationChange = true;
|
|
28
30
|
return;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
//
|
|
33
|
+
// Assigning to window.location or window.location.* → always bad
|
|
32
34
|
if (
|
|
33
35
|
n.type === "AssignmentExpression" &&
|
|
34
36
|
n.left.type === "MemberExpression"
|
|
@@ -42,7 +44,7 @@ export default {
|
|
|
42
44
|
left.property.type === "Identifier" &&
|
|
43
45
|
left.property.name === "location"
|
|
44
46
|
) {
|
|
45
|
-
|
|
47
|
+
hasBadLocationChange = true;
|
|
46
48
|
return;
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -54,13 +56,12 @@ export default {
|
|
|
54
56
|
left.object.property.type === "Identifier" &&
|
|
55
57
|
left.object.property.name === "location"
|
|
56
58
|
) {
|
|
57
|
-
|
|
58
|
-
found = true;
|
|
59
|
+
hasBadLocationChange = true;
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
//
|
|
64
|
+
// window.location.replace(...) → bad
|
|
64
65
|
if (
|
|
65
66
|
n.type === "MemberExpression" &&
|
|
66
67
|
n.object.type === "MemberExpression" &&
|
|
@@ -71,14 +72,13 @@ export default {
|
|
|
71
72
|
n.property.type === "Identifier" &&
|
|
72
73
|
n.property.name === "replace"
|
|
73
74
|
) {
|
|
74
|
-
// parent should be a CallExpression
|
|
75
75
|
if (parent && parent.type === "CallExpression") {
|
|
76
|
-
|
|
76
|
+
hasBadLocationChange = true;
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
//
|
|
81
|
+
// window.history.pushState(...) or replaceState(...)
|
|
82
82
|
if (
|
|
83
83
|
n.type === "MemberExpression" &&
|
|
84
84
|
n.object.type === "MemberExpression" &&
|
|
@@ -90,22 +90,26 @@ export default {
|
|
|
90
90
|
(n.property.name === "pushState" ||
|
|
91
91
|
n.property.name === "replaceState")
|
|
92
92
|
) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
93
|
+
hasReplaceCall = true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Reading window.location.search, .pathname, or .hash → mark as allowed location read
|
|
97
|
+
if (
|
|
98
|
+
n.type === "MemberExpression" &&
|
|
99
|
+
n.object.type === "MemberExpression" &&
|
|
100
|
+
n.object.object.type === "Identifier" &&
|
|
101
|
+
n.object.object.name === "window" &&
|
|
102
|
+
n.object.property.type === "Identifier" &&
|
|
103
|
+
n.object.property.name === "location" &&
|
|
104
|
+
n.property.type === "Identifier" &&
|
|
105
|
+
(n.property.name === "search" ||
|
|
106
|
+
n.property.name === "pathname" ||
|
|
107
|
+
n.property.name === "hash")
|
|
108
|
+
) {
|
|
109
|
+
hasAllowedLocationRead = true;
|
|
106
110
|
}
|
|
107
111
|
|
|
108
|
-
// Recurse into
|
|
112
|
+
// Recurse into children
|
|
109
113
|
for (const key of Object.keys(n)) {
|
|
110
114
|
if (key === "parent") continue;
|
|
111
115
|
const child = n[key];
|
|
@@ -114,45 +118,10 @@ export default {
|
|
|
114
118
|
} else {
|
|
115
119
|
inspect(child, n);
|
|
116
120
|
}
|
|
121
|
+
if (hasBadLocationChange) return;
|
|
117
122
|
}
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
function urlUsesAllowedLocation(argNode) {
|
|
121
|
-
let allowed = false;
|
|
122
|
-
|
|
123
|
-
function check(n) {
|
|
124
|
-
if (allowed || !n || typeof n !== "object") return;
|
|
125
|
-
// Look for window.location.pathname, .search, or .hash anywhere
|
|
126
|
-
if (
|
|
127
|
-
n.type === "MemberExpression" &&
|
|
128
|
-
n.object.type === "MemberExpression" &&
|
|
129
|
-
n.object.object.type === "Identifier" &&
|
|
130
|
-
n.object.object.name === "window" &&
|
|
131
|
-
n.object.property.type === "Identifier" &&
|
|
132
|
-
n.object.property.name === "location" &&
|
|
133
|
-
n.property.type === "Identifier" &&
|
|
134
|
-
(n.property.name === "pathname" ||
|
|
135
|
-
n.property.name === "search" ||
|
|
136
|
-
n.property.name === "hash")
|
|
137
|
-
) {
|
|
138
|
-
allowed = true;
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
for (const key of Object.keys(n)) {
|
|
142
|
-
if (key === "parent") continue;
|
|
143
|
-
const c = n[key];
|
|
144
|
-
if (Array.isArray(c)) {
|
|
145
|
-
c.forEach(check);
|
|
146
|
-
} else {
|
|
147
|
-
check(c);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
check(argNode);
|
|
153
|
-
return allowed;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
125
|
inspect(
|
|
157
126
|
node.type === "ArrowFunctionExpression" ||
|
|
158
127
|
node.type === "FunctionExpression"
|
|
@@ -160,7 +129,15 @@ export default {
|
|
|
160
129
|
: node,
|
|
161
130
|
null
|
|
162
131
|
);
|
|
163
|
-
|
|
132
|
+
|
|
133
|
+
// If there was any direct bad change (open, assign to location, or location.replace), flag it.
|
|
134
|
+
if (hasBadLocationChange) return true;
|
|
135
|
+
|
|
136
|
+
// If there was a history.replaceState or pushState, but no allowed read of window.location.*, flag it.
|
|
137
|
+
if (hasReplaceCall && !hasAllowedLocationRead) return true;
|
|
138
|
+
|
|
139
|
+
// Otherwise, allow (no flag).
|
|
140
|
+
return false;
|
|
164
141
|
}
|
|
165
142
|
|
|
166
143
|
return {
|
|
@@ -185,7 +162,7 @@ export default {
|
|
|
185
162
|
context.report({
|
|
186
163
|
node: attr,
|
|
187
164
|
message:
|
|
188
|
-
"Use an anchor tag for navigation instead of a button with an onClick handler that changes the path. Only query/hash updates are allowed."
|
|
165
|
+
"Use an anchor tag for navigation instead of a button with an onClick handler that changes the path. Only query/hash updates (reading window.location.search, .pathname, or .hash) are allowed."
|
|
189
166
|
});
|
|
190
167
|
}
|
|
191
168
|
}
|