UNPKG

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