.gitignore added
[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         messages: {
116             wrongQuotes: "Strings must use {{description}}."
117         }
118     },
119
120     create(context) {
121
122         const quoteOption = context.options[0],
123             settings = QUOTE_SETTINGS[quoteOption || "double"],
124             options = context.options[1],
125             allowTemplateLiterals = options && options.allowTemplateLiterals === true,
126             sourceCode = context.getSourceCode();
127         let avoidEscape = options && options.avoidEscape === true;
128
129         // deprecated
130         if (options === AVOID_ESCAPE) {
131             avoidEscape = true;
132         }
133
134         /**
135          * Determines if a given node is part of JSX syntax.
136          *
137          * This function returns `true` in the following cases:
138          *
139          * - `<div className="foo"></div>` ... If the literal is an attribute value, the parent of the literal is `JSXAttribute`.
140          * - `<div>foo</div>` ... If the literal is a text content, the parent of the literal is `JSXElement`.
141          * - `<>foo</>` ... If the literal is a text content, the parent of the literal is `JSXFragment`.
142          *
143          * In particular, this function returns `false` in the following cases:
144          *
145          * - `<div className={"foo"}></div>`
146          * - `<div>{"foo"}</div>`
147          *
148          * In both cases, inside of the braces is handled as normal JavaScript.
149          * The braces are `JSXExpressionContainer` nodes.
150          * @param {ASTNode} node The Literal node to check.
151          * @returns {boolean} True if the node is a part of JSX, false if not.
152          * @private
153          */
154         function isJSXLiteral(node) {
155             return node.parent.type === "JSXAttribute" || node.parent.type === "JSXElement" || node.parent.type === "JSXFragment";
156         }
157
158         /**
159          * Checks whether or not a given node is a directive.
160          * The directive is a `ExpressionStatement` which has only a string literal.
161          * @param {ASTNode} node A node to check.
162          * @returns {boolean} Whether or not the node is a directive.
163          * @private
164          */
165         function isDirective(node) {
166             return (
167                 node.type === "ExpressionStatement" &&
168                 node.expression.type === "Literal" &&
169                 typeof node.expression.value === "string"
170             );
171         }
172
173         /**
174          * Checks whether or not a given node is a part of directive prologues.
175          * See also: http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive
176          * @param {ASTNode} node A node to check.
177          * @returns {boolean} Whether or not the node is a part of directive prologues.
178          * @private
179          */
180         function isPartOfDirectivePrologue(node) {
181             const block = node.parent.parent;
182
183             if (block.type !== "Program" && (block.type !== "BlockStatement" || !astUtils.isFunction(block.parent))) {
184                 return false;
185             }
186
187             // Check the node is at a prologue.
188             for (let i = 0; i < block.body.length; ++i) {
189                 const statement = block.body[i];
190
191                 if (statement === node.parent) {
192                     return true;
193                 }
194                 if (!isDirective(statement)) {
195                     break;
196                 }
197             }
198
199             return false;
200         }
201
202         /**
203          * Checks whether or not a given node is allowed as non backtick.
204          * @param {ASTNode} node A node to check.
205          * @returns {boolean} Whether or not the node is allowed as non backtick.
206          * @private
207          */
208         function isAllowedAsNonBacktick(node) {
209             const parent = node.parent;
210
211             switch (parent.type) {
212
213                 // Directive Prologues.
214                 case "ExpressionStatement":
215                     return isPartOfDirectivePrologue(node);
216
217                 // LiteralPropertyName.
218                 case "Property":
219                 case "MethodDefinition":
220                     return parent.key === node && !parent.computed;
221
222                 // ModuleSpecifier.
223                 case "ImportDeclaration":
224                 case "ExportNamedDeclaration":
225                 case "ExportAllDeclaration":
226                     return parent.source === node;
227
228                 // Others don't allow.
229                 default:
230                     return false;
231             }
232         }
233
234         /**
235          * Checks whether or not a given TemplateLiteral node is actually using any of the special features provided by template literal strings.
236          * @param {ASTNode} node A TemplateLiteral node to check.
237          * @returns {boolean} Whether or not the TemplateLiteral node is using any of the special features provided by template literal strings.
238          * @private
239          */
240         function isUsingFeatureOfTemplateLiteral(node) {
241             const hasTag = node.parent.type === "TaggedTemplateExpression" && node === node.parent.quasi;
242
243             if (hasTag) {
244                 return true;
245             }
246
247             const hasStringInterpolation = node.expressions.length > 0;
248
249             if (hasStringInterpolation) {
250                 return true;
251             }
252
253             const isMultilineString = node.quasis.length >= 1 && UNESCAPED_LINEBREAK_PATTERN.test(node.quasis[0].value.raw);
254
255             if (isMultilineString) {
256                 return true;
257             }
258
259             return false;
260         }
261
262         return {
263
264             Literal(node) {
265                 const val = node.value,
266                     rawVal = node.raw;
267
268                 if (settings && typeof val === "string") {
269                     let isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) ||
270                         isJSXLiteral(node) ||
271                         astUtils.isSurroundedBy(rawVal, settings.quote);
272
273                     if (!isValid && avoidEscape) {
274                         isValid = astUtils.isSurroundedBy(rawVal, settings.alternateQuote) && rawVal.indexOf(settings.quote) >= 0;
275                     }
276
277                     if (!isValid) {
278                         context.report({
279                             node,
280                             messageId: "wrongQuotes",
281                             data: {
282                                 description: settings.description
283                             },
284                             fix(fixer) {
285                                 if (quoteOption === "backtick" && astUtils.hasOctalOrNonOctalDecimalEscapeSequence(rawVal)) {
286
287                                     /*
288                                      * An octal or non-octal decimal escape sequence in a template literal would
289                                      * produce syntax error, even in non-strict mode.
290                                      */
291                                     return null;
292                                 }
293
294                                 return fixer.replaceText(node, settings.convert(node.raw));
295                             }
296                         });
297                     }
298                 }
299             },
300
301             TemplateLiteral(node) {
302
303                 // Don't throw an error if backticks are expected or a template literal feature is in use.
304                 if (
305                     allowTemplateLiterals ||
306                     quoteOption === "backtick" ||
307                     isUsingFeatureOfTemplateLiteral(node)
308                 ) {
309                     return;
310                 }
311
312                 context.report({
313                     node,
314                     messageId: "wrongQuotes",
315                     data: {
316                         description: settings.description
317                     },
318                     fix(fixer) {
319                         if (isPartOfDirectivePrologue(node)) {
320
321                             /*
322                              * TemplateLiterals in a directive prologue aren't actually directives, but if they're
323                              * in the directive prologue, then fixing them might turn them into directives and change
324                              * the behavior of the code.
325                              */
326                             return null;
327                         }
328                         return fixer.replaceText(node, settings.convert(sourceCode.getText(node)));
329                     }
330                 });
331             }
332         };
333
334     }
335 };