Skip to content

Commit e016384

Browse files
not-an-aardvarkmysticatea
authored andcommitted
Update: add fixer for quote-props (fixes #6996) (#7095)
1 parent 35f7be9 commit e016384

File tree

3 files changed

+202
-37
lines changed

3 files changed

+202
-37
lines changed

docs/rules/quote-props.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# require quotes around object literal property names (quote-props)
22

3+
(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixes problems reported by this rule.
4+
35
Object literal property names can be defined in two ways: using literals or using strings. For example, these two objects are equivalent:
46

57
```js

lib/rules/quote-props.js

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ module.exports = {
6161
maxItems: 2
6262
}
6363
]
64-
}
64+
},
65+
66+
fixable: "code"
6567
},
6668

6769
create(context) {
@@ -74,7 +76,8 @@ module.exports = {
7476
MESSAGE_UNNECESSARY = "Unnecessarily quoted property '{{property}}' found.",
7577
MESSAGE_UNQUOTED = "Unquoted property '{{property}}' found.",
7678
MESSAGE_NUMERIC = "Unquoted number literal '{{property}}' used as key.",
77-
MESSAGE_RESERVED = "Unquoted reserved word '{{property}}' used as key.";
79+
MESSAGE_RESERVED = "Unquoted reserved word '{{property}}' used as key.",
80+
sourceCode = context.getSourceCode();
7881

7982

8083
/**
@@ -100,6 +103,31 @@ module.exports = {
100103
(tokens[0].type === "Numeric" && !skipNumberLiterals && String(+tokens[0].value) === tokens[0].value));
101104
}
102105

106+
/**
107+
* Returns a string representation of a property node with quotes removed
108+
* @param {ASTNode} key Key AST Node, which may or may not be quoted
109+
* @returns {string} A replacement string for this property
110+
*/
111+
function getUnquotedKey(key) {
112+
return key.type === "Identifier" ? key.name : key.value;
113+
}
114+
115+
/**
116+
* Returns a string representation of a property node with quotes added
117+
* @param {ASTNode} key Key AST Node, which may or may not be quoted
118+
* @returns {string} A replacement string for this property
119+
*/
120+
function getQuotedKey(key) {
121+
if (key.type === "Literal" && typeof key.value === "string") {
122+
123+
// If the key is already a string literal, don't replace the quotes with double quotes.
124+
return sourceCode.getText(key);
125+
}
126+
127+
// Otherwise, the key is either an identifier or a number literal.
128+
return `"${key.type === "Identifier" ? key.name : key.value}"`;
129+
}
130+
103131
/**
104132
* Ensures that a property's key is quoted only when necessary
105133
* @param {ASTNode} node Property AST node
@@ -131,12 +159,27 @@ module.exports = {
131159
}
132160

133161
if (CHECK_UNNECESSARY && areQuotesRedundant(key.value, tokens, NUMBERS)) {
134-
context.report(node, MESSAGE_UNNECESSARY, {property: key.value});
162+
context.report({
163+
node,
164+
message: MESSAGE_UNNECESSARY,
165+
data: {property: key.value},
166+
fix: fixer => fixer.replaceText(key, getUnquotedKey(key))
167+
});
135168
}
136169
} else if (KEYWORDS && key.type === "Identifier" && isKeyword(key.name)) {
137-
context.report(node, MESSAGE_RESERVED, {property: key.name});
170+
context.report({
171+
node,
172+
message: MESSAGE_RESERVED,
173+
data: {property: key.name},
174+
fix: fixer => fixer.replaceText(key, getQuotedKey(key))
175+
});
138176
} else if (NUMBERS && key.type === "Literal" && typeof key.value === "number") {
139-
context.report(node, MESSAGE_NUMERIC, {property: key.value});
177+
context.report({
178+
node,
179+
message: MESSAGE_NUMERIC,
180+
data: {property: key.value},
181+
fix: fixer => fixer.replaceText(key, getQuotedKey(key))
182+
});
140183
}
141184
}
142185

@@ -149,8 +192,11 @@ module.exports = {
149192
const key = node.key;
150193

151194
if (!node.method && !node.computed && !node.shorthand && !(key.type === "Literal" && typeof key.value === "string")) {
152-
context.report(node, MESSAGE_UNQUOTED, {
153-
property: key.name || key.value
195+
context.report({
196+
node,
197+
message: MESSAGE_UNQUOTED,
198+
data: {property: key.name || key.value},
199+
fix: fixer => fixer.replaceText(key, getQuotedKey(key))
154200
});
155201
}
156202
}
@@ -162,8 +208,9 @@ module.exports = {
162208
* @returns {void}
163209
*/
164210
function checkConsistency(node, checkQuotesRedundancy) {
165-
let quotes = false,
166-
lackOfQuotes = false,
211+
const quotedProps = [],
212+
unquotedProps = [];
213+
let keywordKeyName = null,
167214
necessaryQuotes = false;
168215

169216
node.properties.forEach(function(property) {
@@ -176,7 +223,7 @@ module.exports = {
176223

177224
if (key.type === "Literal" && typeof key.value === "string") {
178225

179-
quotes = true;
226+
quotedProps.push(property);
180227

181228
if (checkQuotesRedundancy) {
182229
try {
@@ -189,21 +236,40 @@ module.exports = {
189236
necessaryQuotes = necessaryQuotes || !areQuotesRedundant(key.value, tokens) || KEYWORDS && isKeyword(tokens[0].value);
190237
}
191238
} else if (KEYWORDS && checkQuotesRedundancy && key.type === "Identifier" && isKeyword(key.name)) {
239+
unquotedProps.push(property);
192240
necessaryQuotes = true;
193-
context.report(node, "Properties should be quoted as '{{property}}' is a reserved word.", {property: key.name});
241+
keywordKeyName = key.name;
194242
} else {
195-
lackOfQuotes = true;
196-
}
197-
198-
if (quotes && lackOfQuotes) {
199-
context.report(node, "Inconsistently quoted property '{{key}}' found.", {
200-
key: key.name || key.value
201-
});
243+
unquotedProps.push(property);
202244
}
203245
});
204246

205-
if (checkQuotesRedundancy && quotes && !necessaryQuotes) {
206-
context.report(node, "Properties shouldn't be quoted as all quotes are redundant.");
247+
if (checkQuotesRedundancy && quotedProps.length && !necessaryQuotes) {
248+
quotedProps.forEach(property => {
249+
context.report({
250+
node: property,
251+
message: "Properties shouldn't be quoted as all quotes are redundant.",
252+
fix: fixer => fixer.replaceText(property.key, getUnquotedKey(property.key))
253+
});
254+
});
255+
} else if (unquotedProps.length && keywordKeyName) {
256+
unquotedProps.forEach(property => {
257+
context.report({
258+
node: property,
259+
message: "Properties should be quoted as '{{property}}' is a reserved word.",
260+
data: {property: keywordKeyName},
261+
fix: fixer => fixer.replaceText(property.key, getQuotedKey(property.key))
262+
});
263+
});
264+
} else if (quotedProps.length && unquotedProps.length) {
265+
unquotedProps.forEach(property => {
266+
context.report({
267+
node: property,
268+
message: "Inconsistently quoted property '{{key}}' found.",
269+
data: {key: property.key.name || property.key.value},
270+
fix: fixer => fixer.replaceText(property.key, getQuotedKey(property.key))
271+
});
272+
});
207273
}
208274
}
209275

0 commit comments

Comments
 (0)