source: imaps-frontend/node_modules/eslint/lib/rules/quotes.js@ d565449

main
Last change on this file since d565449 was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

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