Skip to content

Commit

Permalink
feat: valid-typeof always ban undefined (#15635)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
Zzzen and mdjermanovic authored Mar 5, 2022
1 parent 8f675b1 commit 57b8a57
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 4 deletions.
38 changes: 37 additions & 1 deletion lib/rules/valid-typeof.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ module.exports = {
url: "https://eslint.org/docs/rules/valid-typeof"
},

hasSuggestions: true,

schema: [
{
type: "object",
Expand All @@ -33,7 +35,8 @@ module.exports = {
],
messages: {
invalidValue: "Invalid typeof comparison value.",
notString: "Typeof comparisons should be to string literals."
notString: "Typeof comparisons should be to string literals.",
suggestString: 'Use `"{{type}}"` instead of `{{type}}`.'
}
},

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

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

let globalScope;

/**
* Checks whether the given node represents a reference to a global variable that is not declared in the source code.
* These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
* @param {ASTNode} node `Identifier` node to check.
* @returns {boolean} `true` if the node is a reference to a global variable.
*/
function isReferenceToGlobalVariable(node) {
const variable = globalScope.set.get(node.name);

return variable && variable.defs.length === 0 &&
variable.references.some(ref => ref.identifier === node);
}

/**
* Determines whether a node is a typeof expression.
* @param {ASTNode} node The node
Expand All @@ -59,6 +77,10 @@ module.exports = {

return {

Program() {
globalScope = context.getScope();
},

UnaryExpression(node) {
if (isTypeofExpression(node)) {
const parent = context.getAncestors().pop();
Expand All @@ -72,6 +94,20 @@ module.exports = {
if (VALID_TYPES.indexOf(value) === -1) {
context.report({ node: sibling, messageId: "invalidValue" });
}
} else if (sibling.type === "Identifier" && sibling.name === "undefined" && isReferenceToGlobalVariable(sibling)) {
context.report({
node: sibling,
messageId: requireStringLiterals ? "notString" : "invalidValue",
suggest: [
{
messageId: "suggestString",
data: { type: "undefined" },
fix(fixer) {
return fixer.replaceText(sibling, '"undefined"');
}
}
]
});
} else if (requireStringLiterals && !isTypeofExpression(sibling)) {
context.report({ node: sibling, messageId: "notString" });
}
Expand Down
55 changes: 52 additions & 3 deletions tests/lib/rules/valid-typeof.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ ruleTester.run("valid-typeof", rule, {
"typeof(foo) == 'string'",
"typeof(foo) != 'string'",
"var oddUse = typeof foo + 'thing'",
"function f(undefined) { typeof x === undefined }",
{
code: "typeof foo === 'number'",
options: [{ requireStringLiterals: true }]
Expand Down Expand Up @@ -136,6 +137,21 @@ ruleTester.run("valid-typeof", rule, {
options: [{ requireStringLiterals: true }],
errors: [{ messageId: "invalidValue", type: "Literal" }]
},
{
code: "if (typeof bar !== undefined) {}",
errors: [
{
messageId: "invalidValue",
type: "Identifier",
suggestions: [
{
messageId: "suggestString",
data: { type: "undefined" },
output: 'if (typeof bar !== "undefined") {}'
}
]
}]
},
{
code: "typeof foo == Object",
options: [{ requireStringLiterals: true }],
Expand All @@ -144,17 +160,50 @@ ruleTester.run("valid-typeof", rule, {
{
code: "typeof foo === undefined",
options: [{ requireStringLiterals: true }],
errors: [{ messageId: "notString", type: "Identifier" }]
errors: [
{
messageId: "notString",
type: "Identifier",
suggestions: [
{
messageId: "suggestString",
data: { type: "undefined" },
output: 'typeof foo === "undefined"'
}
]
}]
},
{
code: "undefined === typeof foo",
options: [{ requireStringLiterals: true }],
errors: [{ messageId: "notString", type: "Identifier" }]
errors: [
{
messageId: "notString",
type: "Identifier",
suggestions: [
{
messageId: "suggestString",
data: { type: "undefined" },
output: '"undefined" === typeof foo'
}
]
}]
},
{
code: "undefined == typeof foo",
options: [{ requireStringLiterals: true }],
errors: [{ messageId: "notString", type: "Identifier" }]
errors: [
{
messageId: "notString",
type: "Identifier",
suggestions: [
{
messageId: "suggestString",
data: { type: "undefined" },
output: '"undefined" == typeof foo'
}
]
}]
},
{
code: "typeof foo === `undefined${foo}`",
Expand Down

0 comments on commit 57b8a57

Please sign in to comment.