UNPKG

5.66 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 },
27
28 schema: [
29 {
30 type: "object",
31 properties: {
32 allowKeywords: {
33 type: "boolean"
34 },
35 allowPattern: {
36 type: "string"
37 }
38 },
39 additionalProperties: false
40 }
41 ],
42
43 fixable: "code"
44 },
45
46 create(context) {
47 const options = context.options[0] || {};
48 const allowKeywords = options.allowKeywords === void 0 || !!options.allowKeywords;
49 const sourceCode = context.getSourceCode();
50
51 let allowPattern;
52
53 if (options.allowPattern) {
54 allowPattern = new RegExp(options.allowPattern);
55 }
56
57 return {
58 MemberExpression(node) {
59 if (
60 node.computed &&
61 node.property.type === "Literal" &&
62 validIdentifier.test(node.property.value) &&
63 (allowKeywords || keywords.indexOf(String(node.property.value)) === -1)
64 ) {
65 if (!(allowPattern && allowPattern.test(node.property.value))) {
66 context.report({
67 node: node.property,
68 message: "[{{propertyValue}}] is better written in dot notation.",
69 data: {
70 propertyValue: JSON.stringify(node.property.value)
71 },
72 fix(fixer) {
73 const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
74 const rightBracket = sourceCode.getLastToken(node);
75
76 if (sourceCode.getFirstTokenBetween(leftBracket, rightBracket, { includeComments: true, filter: astUtils.isCommentToken })) {
77
78 // Don't perform any fixes if there are comments inside the brackets.
79 return null;
80 }
81
82 const tokenAfterProperty = sourceCode.getTokenAfter(rightBracket);
83 const needsSpaceAfterProperty = tokenAfterProperty &&
84 rightBracket.range[1] === tokenAfterProperty.range[0] &&
85 !astUtils.canTokensBeAdjacent(String(node.property.value), tokenAfterProperty);
86
87 const textBeforeDot = astUtils.isDecimalInteger(node.object) ? " " : "";
88 const textAfterProperty = needsSpaceAfterProperty ? " " : "";
89
90 return fixer.replaceTextRange(
91 [leftBracket.range[0], rightBracket.range[1]],
92 `${textBeforeDot}.${node.property.value}${textAfterProperty}`
93 );
94 }
95 });
96 }
97 }
98 if (
99 !allowKeywords &&
100 !node.computed &&
101 keywords.indexOf(String(node.property.name)) !== -1
102 ) {
103 context.report({
104 node: node.property,
105 message: ".{{propertyName}} is a syntax error.",
106 data: {
107 propertyName: node.property.name
108 },
109 fix(fixer) {
110 const dot = sourceCode.getTokenBefore(node.property);
111 const textAfterDot = sourceCode.text.slice(dot.range[1], node.property.range[0]);
112
113 if (textAfterDot.trim()) {
114
115 // Don't perform any fixes if there are comments between the dot and the property name.
116 return null;
117 }
118
119 if (node.object.type === "Identifier" && node.object.name === "let") {
120
121 /*
122 * A statement that starts with `let[` is parsed as a destructuring variable declaration, not
123 * a MemberExpression.
124 */
125 return null;
126 }
127
128 return fixer.replaceTextRange(
129 [dot.range[0], node.property.range[1]],
130 `[${textAfterDot}"${node.property.name}"]`
131 );
132 }
133 });
134 }
135 }
136 };
137 }
138};