Skip to content
Permalink
9bfb9ba527
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
176 lines (154 sloc) 6.6 KB
/**
* @fileoverview Rule to warn about using dot notation instead of square bracket notation when possible.
* @author Josh Perez
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
const keywords = require("./utils/keywords");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/u;
// `null` literal must be handled separately.
const literalTypesToCheck = new Set(["string", "boolean"]);
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "Enforce dot notation whenever possible",
recommended: false,
url: "https://eslint.org/docs/latest/rules/dot-notation"
},
schema: [
{
type: "object",
properties: {
allowKeywords: {
type: "boolean",
default: true
},
allowPattern: {
type: "string",
default: ""
}
},
additionalProperties: false
}
],
fixable: "code",
messages: {
useDot: "[{{key}}] is better written in dot notation.",
useBrackets: ".{{key}} is a syntax error."
}
},
create(context) {
const options = context.options[0] || {};
const allowKeywords = options.allowKeywords === void 0 || options.allowKeywords;
const sourceCode = context.sourceCode;
let allowPattern;
if (options.allowPattern) {
allowPattern = new RegExp(options.allowPattern, "u");
}
/**
* Check if the property is valid dot notation
* @param {ASTNode} node The dot notation node
* @param {string} value Value which is to be checked
* @returns {void}
*/
function checkComputedProperty(node, value) {
if (
validIdentifier.test(value) &&
(allowKeywords || !keywords.includes(String(value))) &&
!(allowPattern && allowPattern.test(value))
) {
const formattedValue = node.property.type === "Literal" ? JSON.stringify(value) : `\`${value}\``;
context.report({
node: node.property,
messageId: "useDot",
data: {
key: formattedValue
},
*fix(fixer) {
const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
const rightBracket = sourceCode.getLastToken(node);
const nextToken = sourceCode.getTokenAfter(node);
// Don't perform any fixes if there are comments inside the brackets.
if (sourceCode.commentsExistBetween(leftBracket, rightBracket)) {
return;
}
// Replace the brackets by an identifier.
if (!node.optional) {
yield fixer.insertTextBefore(
leftBracket,
astUtils.isDecimalInteger(node.object) ? " ." : "."
);
}
yield fixer.replaceTextRange(
[leftBracket.range[0], rightBracket.range[1]],
value
);
// Insert a space after the property if it will be connected to the next token.
if (
nextToken &&
rightBracket.range[1] === nextToken.range[0] &&
!astUtils.canTokensBeAdjacent(String(value), nextToken)
) {
yield fixer.insertTextAfter(node, " ");
}
}
});
}
}
return {
MemberExpression(node) {
if (
node.computed &&
node.property.type === "Literal" &&
(literalTypesToCheck.has(typeof node.property.value) || astUtils.isNullLiteral(node.property))
) {
checkComputedProperty(node, node.property.value);
}
if (
node.computed &&
astUtils.isStaticTemplateLiteral(node.property)
) {
checkComputedProperty(node, node.property.quasis[0].value.cooked);
}
if (
!allowKeywords &&
!node.computed &&
node.property.type === "Identifier" &&
keywords.includes(String(node.property.name))
) {
context.report({
node: node.property,
messageId: "useBrackets",
data: {
key: node.property.name
},
*fix(fixer) {
const dotToken = sourceCode.getTokenBefore(node.property);
// A statement that starts with `let[` is parsed as a destructuring variable declaration, not a MemberExpression.
if (node.object.type === "Identifier" && node.object.name === "let" && !node.optional) {
return;
}
// Don't perform any fixes if there are comments between the dot and the property name.
if (sourceCode.commentsExistBetween(dotToken, node.property)) {
return;
}
// Replace the identifier to brackets.
if (!node.optional) {
yield fixer.remove(dotToken);
}
yield fixer.replaceText(node.property, `["${node.property.name}"]`);
}
});
}
}
};
}
};