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 CHANGED
@@ -1163,39 +1163,38 @@ var no_button_navigation_default = {
1163
1163
  },
1164
1164
  create(context) {
1165
1165
  function containsWindowNavigation(node) {
1166
- let found = false;
1166
+ let hasReplaceCall = false;
1167
+ let hasAllowedLocationRead = false;
1168
+ let hasBadLocationChange = false;
1167
1169
  function inspect(n, parent) {
1168
- if (found || !n || typeof n !== "object")
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
- found = true;
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
- found = true;
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
- found = true;
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
- found = true;
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
- if (parent && parent.type === "CallExpression" && Array.isArray(parent.arguments) && parent.arguments.length >= 3) {
1193
- const urlArg = parent.arguments[2];
1194
- if (!urlUsesAllowedLocation(urlArg)) {
1195
- found = true;
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
- return found;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-absolute",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "ESLint plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,12 +11,14 @@ export default {
11
11
  },
12
12
  create(context) {
13
13
  function containsWindowNavigation(node) {
14
- let found = false;
14
+ let hasReplaceCall = false;
15
+ let hasAllowedLocationRead = false;
16
+ let hasBadLocationChange = false;
15
17
 
16
18
  function inspect(n, parent) {
17
- if (found || !n || typeof n !== "object") return;
19
+ if (hasBadLocationChange) return;
18
20
 
19
- // 1) window.open(...)
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
- found = true;
29
+ hasBadLocationChange = true;
28
30
  return;
29
31
  }
30
32
 
31
- // 2) Assignment to window.location or window.location.something (e.g. window.location = "...", window.location.href = "...")
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
- found = true;
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
- // any assignment to a sub-property of window.location (href, pathname, etc.) = navigation
58
- found = true;
59
+ hasBadLocationChange = true;
59
60
  return;
60
61
  }
61
62
  }
62
63
 
63
- // 3) window.location.replace(...) (or any call on window.location.* besides .search/.hash)
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
- found = true;
76
+ hasBadLocationChange = true;
77
77
  return;
78
78
  }
79
79
  }
80
80
 
81
- // 4) window.history.pushState(...) or window.history.replaceState(...)
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
- if (
94
- parent &&
95
- parent.type === "CallExpression" &&
96
- Array.isArray(parent.arguments) &&
97
- parent.arguments.length >= 3
98
- ) {
99
- const urlArg = parent.arguments[2];
100
- if (!urlUsesAllowedLocation(urlArg)) {
101
- found = true;
102
- return;
103
- }
104
- // If urlArg references window.location.pathname/search/hash allowed
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 all child nodes
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
- return found;
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
  }