Skip to content

Commit 57b8a57

Browse files
Zzzenmdjermanovic
andauthored
feat: valid-typeof always ban undefined (#15635)
* feat: `valid-typeof`: always ban `undefined` * change error message and check definition of `undefined` * check string literal first * add suggestions * Apply suggestions from code review Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> * suggest string when `requireStringLiterals` is on Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
1 parent 8f675b1 commit 57b8a57

File tree

2 files changed

+89
-4
lines changed

2 files changed

+89
-4
lines changed

lib/rules/valid-typeof.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ module.exports = {
1919
url: "https://eslint.org/docs/rules/valid-typeof"
2020
},
2121

22+
hasSuggestions: true,
23+
2224
schema: [
2325
{
2426
type: "object",
@@ -33,7 +35,8 @@ module.exports = {
3335
],
3436
messages: {
3537
invalidValue: "Invalid typeof comparison value.",
36-
notString: "Typeof comparisons should be to string literals."
38+
notString: "Typeof comparisons should be to string literals.",
39+
suggestString: 'Use `"{{type}}"` instead of `{{type}}`.'
3740
}
3841
},
3942

@@ -44,6 +47,21 @@ module.exports = {
4447

4548
const requireStringLiterals = context.options[0] && context.options[0].requireStringLiterals;
4649

50+
let globalScope;
51+
52+
/**
53+
* Checks whether the given node represents a reference to a global variable that is not declared in the source code.
54+
* These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
55+
* @param {ASTNode} node `Identifier` node to check.
56+
* @returns {boolean} `true` if the node is a reference to a global variable.
57+
*/
58+
function isReferenceToGlobalVariable(node) {
59+
const variable = globalScope.set.get(node.name);
60+
61+
return variable && variable.defs.length === 0 &&
62+
variable.references.some(ref => ref.identifier === node);
63+
}
64+
4765
/**
4866
* Determines whether a node is a typeof expression.
4967
* @param {ASTNode} node The node
@@ -59,6 +77,10 @@ module.exports = {
5977

6078
return {
6179

80+
Program() {
81+
globalScope = context.getScope();
82+
},
83+
6284
UnaryExpression(node) {
6385
if (isTypeofExpression(node)) {
6486
const parent = context.getAncestors().pop();
@@ -72,6 +94,20 @@ module.exports = {
7294
if (VALID_TYPES.indexOf(value) === -1) {
7395
context.report({ node: sibling, messageId: "invalidValue" });
7496
}
97+
} else if (sibling.type === "Identifier" && sibling.name === "undefined" && isReferenceToGlobalVariable(sibling)) {
98+
context.report({
99+
node: sibling,
100+
messageId: requireStringLiterals ? "notString" : "invalidValue",
101+
suggest: [
102+
{
103+
messageId: "suggestString",
104+
data: { type: "undefined" },
105+
fix(fixer) {
106+
return fixer.replaceText(sibling, '"undefined"');
107+
}
108+
}
109+
]
110+
});
75111
} else if (requireStringLiterals && !isTypeofExpression(sibling)) {
76112
context.report({ node: sibling, messageId: "notString" });
77113
}

tests/lib/rules/valid-typeof.js

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ ruleTester.run("valid-typeof", rule, {
4545
"typeof(foo) == 'string'",
4646
"typeof(foo) != 'string'",
4747
"var oddUse = typeof foo + 'thing'",
48+
"function f(undefined) { typeof x === undefined }",
4849
{
4950
code: "typeof foo === 'number'",
5051
options: [{ requireStringLiterals: true }]
@@ -136,6 +137,21 @@ ruleTester.run("valid-typeof", rule, {
136137
options: [{ requireStringLiterals: true }],
137138
errors: [{ messageId: "invalidValue", type: "Literal" }]
138139
},
140+
{
141+
code: "if (typeof bar !== undefined) {}",
142+
errors: [
143+
{
144+
messageId: "invalidValue",
145+
type: "Identifier",
146+
suggestions: [
147+
{
148+
messageId: "suggestString",
149+
data: { type: "undefined" },
150+
output: 'if (typeof bar !== "undefined") {}'
151+
}
152+
]
153+
}]
154+
},
139155
{
140156
code: "typeof foo == Object",
141157
options: [{ requireStringLiterals: true }],
@@ -144,17 +160,50 @@ ruleTester.run("valid-typeof", rule, {
144160
{
145161
code: "typeof foo === undefined",
146162
options: [{ requireStringLiterals: true }],
147-
errors: [{ messageId: "notString", type: "Identifier" }]
163+
errors: [
164+
{
165+
messageId: "notString",
166+
type: "Identifier",
167+
suggestions: [
168+
{
169+
messageId: "suggestString",
170+
data: { type: "undefined" },
171+
output: 'typeof foo === "undefined"'
172+
}
173+
]
174+
}]
148175
},
149176
{
150177
code: "undefined === typeof foo",
151178
options: [{ requireStringLiterals: true }],
152-
errors: [{ messageId: "notString", type: "Identifier" }]
179+
errors: [
180+
{
181+
messageId: "notString",
182+
type: "Identifier",
183+
suggestions: [
184+
{
185+
messageId: "suggestString",
186+
data: { type: "undefined" },
187+
output: '"undefined" === typeof foo'
188+
}
189+
]
190+
}]
153191
},
154192
{
155193
code: "undefined == typeof foo",
156194
options: [{ requireStringLiterals: true }],
157-
errors: [{ messageId: "notString", type: "Identifier" }]
195+
errors: [
196+
{
197+
messageId: "notString",
198+
type: "Identifier",
199+
suggestions: [
200+
{
201+
messageId: "suggestString",
202+
data: { type: "undefined" },
203+
output: '"undefined" == typeof foo'
204+
}
205+
]
206+
}]
158207
},
159208
{
160209
code: "typeof foo === `undefined${foo}`",

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy