minor adjustment to readme
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / quotes.js
1 /**
2  * @fileoverview A rule to choose between single and double quote marks
3  * @author Matt DuVall <http://www.mattduvall.com/>, Brandon Payton
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Constants
16 //------------------------------------------------------------------------------
17
18 const QUOTE_SETTINGS = {
19     double: {
20         quote: "\"",
21         alternateQuote: "'",
22         description: "doublequote"
23     },
24     single: {
25         quote: "'",
26         alternateQuote: "\"",
27         description: "singlequote"
28     },
29     backtick: {
30         quote: "`",
31         alternateQuote: "\"",
32         description: "backtick"
33     }
34 };
35
36 // An unescaped newline is a newline preceded by an even number of backslashes.
37 const UNESCAPED_LINEBREAK_PATTERN = new RegExp(String.raw`(^|[^\\])(\\\\)*[${Array.from(astUtils.LINEBREAKS).join("")}]`, "u");
38
39 /**
40  * Switches quoting of javascript string between ' " and `
41  * escaping and unescaping as necessary.
42  * Only escaping of the minimal set of characters is changed.
43  * Note: escaping of newlines when switching from backtick to other quotes is not handled.
44  * @param {string} str A string to convert.
45  * @returns {string} The string with changed quotes.
46  * @private
47  */
48 QUOTE_SETTINGS.double.convert =
49 QUOTE_SETTINGS.single.convert =
50 QUOTE_SETTINGS.backtick.convert = function(str) {
51     const newQuote = this.quote;
52     const oldQuote = str[0];
53
54     if (newQuote === oldQuote) {
55         return str;
56     }
57     return newQuote + str.slice(1, -1).replace(/\\(\$\{|\r\n?|\n|.)|["'`]|\$\{|(\r\n?|\n)/gu, (match, escaped, newline) => {
58         if (escaped === oldQuote || oldQuote === "`" && escaped === "${") {
59             return escaped; // unescape
60         }
61         if (match === newQuote || newQuote === "`" && match === "${") {
62             return `\\${match}`; // escape
63         }
64         if (newline && oldQuote === "`") {
65             return "\\n"; // escape newlines
66         }
67         return match;
68     }) + newQuote;
69 };
70
71 const AVOID_ESCAPE = "avoid-escape";
72
73 //------------------------------------------------------------------------------
74 // Rule Definition
75 //------------------------------------------------------------------------------
76
77 module.exports = {
78     meta: {
79         type: "layout",
80
81         docs: {
82             description: "enforce the consistent use of either backticks, double, or single quotes",
83             category: "Stylistic Issues",
84             recommended: false,
85             url: "https://eslint.org/docs/rules/quotes"
86         },
87
88         fixable: "code",
89
90         schema: [
91             {
92                 enum: ["single", "double", "backtick"]
93             },
94             {
95                 anyOf: [
96                     {
97                         enum: ["avoid-escape"]
98                     },
99                     {
100                         type: "object",
101                         properties: {
102                             avoidEscape: {
103                                 type: "boolean"
104                             },
105                             allowTemplateLiterals: {
106                                 type: "boolean"
107                             }
108                         },
109                         additionalProperties: false
110                     }
111                 ]
112             }
113         ]
114     },
115
116     create(context) {
117
118         const quoteOption = context.options[0],
119             settings = QUOTE_SETTINGS[quoteOption || "double"],
120             options = context.options[1],
121             allowTemplateLiterals = options && options.allowTemplateLiterals === true,
122             sourceCode = context.getSourceCode();
123         let avoidEscape = options && options.avoidEscape === true;
124
125         // deprecated
126         if (options === AVOID_ESCAPE) {
127             avoidEscape = true;
128         }
129
130         /**
131          * Determines if a given node is part of JSX syntax.
132          *
133          * This function returns `true` in the following cases:
134          *
135          * - `<div className="foo"></div>` ... If the literal is an attribute value, the parent of the literal is `JSXAttribute`.
136          * - `<div>foo</div>` ... If the literal is a text content, the parent of the literal is `JSXElement`.
137          * - `<>foo</>` ... If the literal is a text content, the parent of the literal is `JSXFragment`.
138          *
139          * In particular, this function returns `false` in the following cases:
140          *
141          * - `<div className={"foo"}></div>`
142          * - `<div>{"foo"}</div>`
143          *
144          * In both cases, inside of the braces is handled as normal JavaScript.
145          * The braces are `JSXExpressionContainer` nodes.
146          * @param {ASTNode} node The Literal node to check.
147          * @returns {boolean} True if the node is a part of JSX, false if not.
148          * @private
149          */
150         function isJSXLiteral(node) {
151             return node.parent.type === "JSXAttribute" || node.parent.type === "JSXElement" || node.parent.type === "JSXFragment";
152         }
153
154         /**
155          * Checks whether or not a given node is a directive.
156          * The directive is a `ExpressionStatement` which has only a string literal.
157          * @param {ASTNode} node A node to check.
158          * @returns {boolean} Whether or not the node is a directive.
159          * @private
160          */
161         function isDirective(node) {
162             return (
163                 node.type === "ExpressionStatement" &&
164                 node.expression.type === "Literal" &&
165                 typeof node.expression.value === "string"
166             );
167         }
168
169         /**
170          * Checks whether or not a given node is a part of directive prologues.
171          * See also: http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive
172          * @param {ASTNode} node A node to check.
173          * @returns {boolean} Whether or not the node is a part of directive prologues.
174          * @private
175          */
176         function isPartOfDirectivePrologue(node) {
177             const block = node.parent.parent;
178
179             if (block.type !== "Program" && (block.type !== "BlockStatement" || !astUtils.isFunction(block.parent))) {
180                 return false;
181             }
182
183             // Check the node is at a prologue.
184             for (let i = 0; i < block.body.length; ++i) {
185                 const statement = block.body[i];
186
187                 if (statement === node.parent) {
188                     return true;
189                 }
190                 if (!isDirective(statement)) {
191                     break;
192                 }
193             }
194
195             return false;
196         }
197
198         /**
199          * Checks whether or not a given node is allowed as non backtick.
200          * @param {ASTNode} node A node to check.
201          * @returns {boolean} Whether or not the node is allowed as non backtick.
202          * @private
203          */
204         function isAllowedAsNonBacktick(node) {
205             const parent = node.parent;
206
207             switch (parent.type) {
208
209                 // Directive Prologues.
210                 case "ExpressionStatement":
211                     return isPartOfDirectivePrologue(node);
212
213                 // LiteralPropertyName.
214                 case "Property":
215                 case "MethodDefinition":
216                     return parent.key === node && !parent.computed;
217
218                 // ModuleSpecifier.
219                 case "ImportDeclaration":
220                 case "ExportNamedDeclaration":
221                 case "ExportAllDeclaration":
222                     return parent.source === node;
223
224                 // Others don't allow.
225                 default:
226                     return false;
227             }
228         }
229
230         /**
231          * Checks whether or not a given TemplateLiteral node is actually using any of the special features provided by template literal strings.
232          * @param {ASTNode} node A TemplateLiteral node to check.
233          * @returns {boolean} Whether or not the TemplateLiteral node is using any of the special features provided by template literal strings.
234          * @private
235          */
236         function isUsingFeatureOfTemplateLiteral(node) {
237             const hasTag = node.parent.type === "TaggedTemplateExpression" && node === node.parent.quasi;
238
239             if (hasTag) {
240                 return true;
241             }
242
243             const hasStringInterpolation = node.expressions.length > 0;
244
245             if (hasStringInterpolation) {
246                 return true;
247             }
248
249             const isMultilineString = node.quasis.length >= 1 && UNESCAPED_LINEBREAK_PATTERN.test(node.quasis[0].value.raw);
250
251             if (isMultilineString) {
252                 return true;
253             }
254
255             return false;
256         }
257
258         return {
259
260             Literal(node) {
261                 const val = node.value,
262                     rawVal = node.raw;
263
264                 if (settings && typeof val === "string") {
265                     let isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) ||
266                         isJSXLiteral(node) ||
267                         astUtils.isSurroundedBy(rawVal, settings.quote);
268
269                     if (!isValid && avoidEscape) {
270                         isValid = astUtils.isSurroundedBy(rawVal, settings.alternateQuote) && rawVal.indexOf(settings.quote) >= 0;
271                     }
272
273                     if (!isValid) {
274                         context.report({
275                             node,
276                             message: "Strings must use {{description}}.",
277                             data: {
278                                 description: settings.description
279                             },
280                             fix(fixer) {
281                                 if (quoteOption === "backtick" && astUtils.hasOctalEscapeSequence(rawVal)) {
282
283                                     // An octal escape sequence in a template literal would produce syntax error, even in non-strict mode.
284                                     return null;
285                                 }
286
287                                 return fixer.replaceText(node, settings.convert(node.raw));
288                             }
289                         });
290                     }
291                 }
292             },
293
294             TemplateLiteral(node) {
295
296                 // Don't throw an error if backticks are expected or a template literal feature is in use.
297                 if (
298                     allowTemplateLiterals ||
299                     quoteOption === "backtick" ||
300                     isUsingFeatureOfTemplateLiteral(node)
301                 ) {
302                     return;
303                 }
304
305                 context.report({
306                     node,
307                     message: "Strings must use {{description}}.",
308                     data: {
309                         description: settings.description
310                     },
311                     fix(fixer) {
312                         if (isPartOfDirectivePrologue(node)) {
313
314                             /*
315                              * TemplateLiterals in a directive prologue aren't actually directives, but if they're
316                              * in the directive prologue, then fixing them might turn them into directives and change
317                              * the behavior of the code.
318                              */
319                             return null;
320                         }
321                         return fixer.replaceText(node, settings.convert(sourceCode.getText(node)));
322                     }
323                 });
324             }
325         };
326
327     }
328 };