blob: 1cd908f7a2c25ef1ebedb29bfa07a6b9b53c6957 [file] [log] [blame]
Yang Guo4fd355c2019-09-19 10:59:03 +02001/**
2 * @fileoverview Rule to warn about using dot notation instead of square bracket notation when possible.
3 * @author Josh Perez
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Requirements
9//------------------------------------------------------------------------------
10
11const astUtils = require("./utils/ast-utils");
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +010012const keywords = require("./utils/keywords");
Yang Guo4fd355c2019-09-19 10:59:03 +020013
14//------------------------------------------------------------------------------
15// Rule Definition
16//------------------------------------------------------------------------------
17
18const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/u;
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +010019
20// `null` literal must be handled separately.
21const literalTypesToCheck = new Set(["string", "boolean"]);
Yang Guo4fd355c2019-09-19 10:59:03 +020022
23module.exports = {
24 meta: {
25 type: "suggestion",
26
27 docs: {
28 description: "enforce dot notation whenever possible",
Yang Guo4fd355c2019-09-19 10:59:03 +020029 recommended: false,
30 url: "https://eslint.org/docs/rules/dot-notation"
31 },
32
33 schema: [
34 {
35 type: "object",
36 properties: {
37 allowKeywords: {
38 type: "boolean",
39 default: true
40 },
41 allowPattern: {
42 type: "string",
43 default: ""
44 }
45 },
46 additionalProperties: false
47 }
48 ],
49
50 fixable: "code",
51
52 messages: {
53 useDot: "[{{key}}] is better written in dot notation.",
54 useBrackets: ".{{key}} is a syntax error."
55 }
56 },
57
58 create(context) {
59 const options = context.options[0] || {};
60 const allowKeywords = options.allowKeywords === void 0 || options.allowKeywords;
61 const sourceCode = context.getSourceCode();
62
63 let allowPattern;
64
65 if (options.allowPattern) {
66 allowPattern = new RegExp(options.allowPattern, "u");
67 }
68
69 /**
70 * Check if the property is valid dot notation
71 * @param {ASTNode} node The dot notation node
72 * @param {string} value Value which is to be checked
73 * @returns {void}
74 */
75 function checkComputedProperty(node, value) {
76 if (
77 validIdentifier.test(value) &&
78 (allowKeywords || keywords.indexOf(String(value)) === -1) &&
79 !(allowPattern && allowPattern.test(value))
80 ) {
81 const formattedValue = node.property.type === "Literal" ? JSON.stringify(value) : `\`${value}\``;
82
83 context.report({
84 node: node.property,
85 messageId: "useDot",
86 data: {
87 key: formattedValue
88 },
Tim van der Lippe16aca392020-11-13 11:37:13 +000089 *fix(fixer) {
Yang Guo4fd355c2019-09-19 10:59:03 +020090 const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
91 const rightBracket = sourceCode.getLastToken(node);
Tim van der Lippe16aca392020-11-13 11:37:13 +000092 const nextToken = sourceCode.getTokenAfter(node);
Yang Guo4fd355c2019-09-19 10:59:03 +020093
Tim van der Lippe16aca392020-11-13 11:37:13 +000094 // Don't perform any fixes if there are comments inside the brackets.
95 if (sourceCode.commentsExistBetween(leftBracket, rightBracket)) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010096 return;
Yang Guo4fd355c2019-09-19 10:59:03 +020097 }
98
Tim van der Lippe16aca392020-11-13 11:37:13 +000099 // Replace the brackets by an identifier.
100 if (!node.optional) {
101 yield fixer.insertTextBefore(
102 leftBracket,
103 astUtils.isDecimalInteger(node.object) ? " ." : "."
104 );
105 }
106 yield fixer.replaceTextRange(
Yang Guo4fd355c2019-09-19 10:59:03 +0200107 [leftBracket.range[0], rightBracket.range[1]],
Tim van der Lippe16aca392020-11-13 11:37:13 +0000108 value
Yang Guo4fd355c2019-09-19 10:59:03 +0200109 );
Tim van der Lippe16aca392020-11-13 11:37:13 +0000110
111 // Insert a space after the property if it will be connected to the next token.
112 if (
113 nextToken &&
114 rightBracket.range[1] === nextToken.range[0] &&
115 !astUtils.canTokensBeAdjacent(String(value), nextToken)
116 ) {
117 yield fixer.insertTextAfter(node, " ");
118 }
Yang Guo4fd355c2019-09-19 10:59:03 +0200119 }
120 });
121 }
122 }
123
124 return {
125 MemberExpression(node) {
126 if (
127 node.computed &&
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +0100128 node.property.type === "Literal" &&
129 (literalTypesToCheck.has(typeof node.property.value) || astUtils.isNullLiteral(node.property))
Yang Guo4fd355c2019-09-19 10:59:03 +0200130 ) {
131 checkComputedProperty(node, node.property.value);
132 }
133 if (
134 node.computed &&
135 node.property.type === "TemplateLiteral" &&
136 node.property.expressions.length === 0
137 ) {
138 checkComputedProperty(node, node.property.quasis[0].value.cooked);
139 }
140 if (
141 !allowKeywords &&
142 !node.computed &&
Tim van der Lippe0fb47802021-11-08 16:23:10 +0000143 node.property.type === "Identifier" &&
Yang Guo4fd355c2019-09-19 10:59:03 +0200144 keywords.indexOf(String(node.property.name)) !== -1
145 ) {
146 context.report({
147 node: node.property,
148 messageId: "useBrackets",
149 data: {
150 key: node.property.name
151 },
Tim van der Lippe16aca392020-11-13 11:37:13 +0000152 *fix(fixer) {
153 const dotToken = sourceCode.getTokenBefore(node.property);
Yang Guo4fd355c2019-09-19 10:59:03 +0200154
Tim van der Lippe16aca392020-11-13 11:37:13 +0000155 // A statement that starts with `let[` is parsed as a destructuring variable declaration, not a MemberExpression.
156 if (node.object.type === "Identifier" && node.object.name === "let" && !node.optional) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100157 return;
Yang Guo4fd355c2019-09-19 10:59:03 +0200158 }
159
Tim van der Lippe16aca392020-11-13 11:37:13 +0000160 // Don't perform any fixes if there are comments between the dot and the property name.
161 if (sourceCode.commentsExistBetween(dotToken, node.property)) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100162 return;
Yang Guo4fd355c2019-09-19 10:59:03 +0200163 }
164
Tim van der Lippe16aca392020-11-13 11:37:13 +0000165 // Replace the identifier to brackets.
166 if (!node.optional) {
167 yield fixer.remove(dotToken);
168 }
169 yield fixer.replaceText(node.property, `["${node.property.name}"]`);
Yang Guo4fd355c2019-09-19 10:59:03 +0200170 }
171 });
172 }
173 }
174 };
175 }
176};