UNPKG

6.44 kBJavaScriptView Raw
1/**
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("../util/ast-utils");
12
13//------------------------------------------------------------------------------
14// Rule Definition
15//------------------------------------------------------------------------------
16
17const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/u;
18const keywords = require("../util/keywords");
19
20module.exports = {
21 meta: {
22 type: "suggestion",
23
24 docs: {
25 description: "enforce dot notation whenever possible",
26 category: "Best Practices",
27 recommended: false,
28 url: "https://eslint.org/docs/rules/dot-notation"
29 },
30
31 schema: [
32 {
33 type: "object",
34 properties: {
35 allowKeywords: {
36 type: "boolean",
37 default: true
38 },
39 allowPattern: {
40 type: "string",
41 default: ""
42 }
43 },
44 additionalProperties: false
45 }
46 ],
47
48 fixable: "code",
49
50 messages: {
51 useDot: "[{{key}}] is better written in dot notation.",
52 useBrackets: ".{{key}} is a syntax error."
53 }
54 },
55
56 create(context) {
57 const options = context.options[0] || {};
58 const allowKeywords = options.allowKeywords === void 0 || options.allowKeywords;
59 const sourceCode = context.getSourceCode();
60
61 let allowPattern;
62
63 if (options.allowPattern) {
64 allowPattern = new RegExp(options.allowPattern); // eslint-disable-line require-unicode-regexp
65 }
66
67 /**
68 * Check if the property is valid dot notation
69 * @param {ASTNode} node The dot notation node
70 * @param {string} value Value which is to be checked
71 * @returns {void}
72 */
73 function checkComputedProperty(node, value) {
74 if (
75 validIdentifier.test(value) &&
76 (allowKeywords || keywords.indexOf(String(value)) === -1) &&
77 !(allowPattern && allowPattern.test(value))
78 ) {
79 const formattedValue = node.property.type === "Literal" ? JSON.stringify(value) : `\`${value}\``;
80
81 context.report({
82 node: node.property,
83 messageId: "useDot",
84 data: {
85 key: formattedValue
86 },
87 fix(fixer) {
88 const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
89 const rightBracket = sourceCode.getLastToken(node);
90
91 if (sourceCode.getFirstTokenBetween(leftBracket, rightBracket, { includeComments: true, filter: astUtils.isCommentToken })) {
92
93 // Don't perform any fixes if there are comments inside the brackets.
94 return null;
95 }
96
97 const tokenAfterProperty = sourceCode.getTokenAfter(rightBracket);
98 const needsSpaceAfterProperty = tokenAfterProperty &&
99 rightBracket.range[1] === tokenAfterProperty.range[0] &&
100 !astUtils.canTokensBeAdjacent(String(value), tokenAfterProperty);
101
102 const textBeforeDot = astUtils.isDecimalInteger(node.object) ? " " : "";
103 const textAfterProperty = needsSpaceAfterProperty ? " " : "";
104
105 return fixer.replaceTextRange(
106 [leftBracket.range[0], rightBracket.range[1]],
107 `${textBeforeDot}.${value}${textAfterProperty}`
108 );
109 }
110 });
111 }
112 }
113
114 return {
115 MemberExpression(node) {
116 if (
117 node.computed &&
118 node.property.type === "Literal"
119 ) {
120 checkComputedProperty(node, node.property.value);
121 }
122 if (
123 node.computed &&
124 node.property.type === "TemplateLiteral" &&
125 node.property.expressions.length === 0
126 ) {
127 checkComputedProperty(node, node.property.quasis[0].value.cooked);
128 }
129 if (
130 !allowKeywords &&
131 !node.computed &&
132 keywords.indexOf(String(node.property.name)) !== -1
133 ) {
134 context.report({
135 node: node.property,
136 messageId: "useBrackets",
137 data: {
138 key: node.property.name
139 },
140 fix(fixer) {
141 const dot = sourceCode.getTokenBefore(node.property);
142 const textAfterDot = sourceCode.text.slice(dot.range[1], node.property.range[0]);
143
144 if (textAfterDot.trim()) {
145
146 // Don't perform any fixes if there are comments between the dot and the property name.
147 return null;
148 }
149
150 if (node.object.type === "Identifier" && node.object.name === "let") {
151
152 /*
153 * A statement that starts with `let[` is parsed as a destructuring variable declaration, not
154 * a MemberExpression.
155 */
156 return null;
157 }
158
159 return fixer.replaceTextRange(
160 [dot.range[0], node.property.range[1]],
161 `[${textAfterDot}"${node.property.name}"]`
162 );
163 }
164 });
165 }
166 }
167 };
168 }
169};