From ea16de4e7c6f661398b0b7843f95e5f307c89551 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Tue, 26 Nov 2019 20:26:22 -0800 Subject: [PATCH] Fix: Support tagged template literal generics in no-unexpected-multiline (#11698) --- lib/rules/no-unexpected-multiline.js | 8 + .../tagged-template-with-generic-1.js | 245 +++++++++++++ .../tagged-template-with-generic-2.js | 247 +++++++++++++ .../tagged-template-with-generic-3.js | 245 +++++++++++++ ...agged-template-with-generic-and-comment.js | 325 ++++++++++++++++++ tests/lib/rules/no-unexpected-multiline.js | 49 ++- 6 files changed, 1118 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-1.js create mode 100644 tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-2.js create mode 100644 tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-3.js create mode 100644 tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-and-comment.js diff --git a/lib/rules/no-unexpected-multiline.js b/lib/rules/no-unexpected-multiline.js index 8026e172223..eb72008a294 100644 --- a/lib/rules/no-unexpected-multiline.js +++ b/lib/rules/no-unexpected-multiline.js @@ -74,6 +74,14 @@ module.exports = { if (node.tag.loc.end.line === node.quasi.loc.start.line) { return; } + + // handle generics type parameters on template tags + const tokenBefore = sourceCode.getTokenBefore(node.quasi); + + if (tokenBefore.loc.end.line === node.quasi.loc.start.line) { + return; + } + context.report({ node, loc: node.loc.start, messageId: "taggedTemplate" }); }, diff --git a/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-1.js b/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-1.js new file mode 100644 index 00000000000..ba6cb389d77 --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-1.js @@ -0,0 +1,245 @@ +"use strict"; + +/* + * Parsed on astexplorer.net using @typescript-eslint/parser@1.4.2 + * + * Source: + * tag` + * multiline + * `; + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "TaggedTemplateExpression", + typeParameters: { + type: "TSTypeParameterInstantiation", + range: [3, 12], + loc: { + start: { + line: 1, + column: 3 + }, + end: { + line: 1, + column: 12 + } + }, + params: [ + { + type: "TSTypeReference", + typeName: { + type: "Identifier", + name: "generic", + range: [4, 11], + loc: { + start: { + line: 1, + column: 4 + }, + end: { + line: 1, + column: 11 + } + } + }, + range: [4, 11], + loc: { + start: { + line: 1, + column: 4 + }, + end: { + line: 1, + column: 11 + } + } + } + ] + }, + tag: { + type: "Identifier", + name: "tag", + range: [0, 3], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 3 + } + } + }, + quasi: { + type: "TemplateLiteral", + quasis: [ + { + type: "TemplateElement", + value: { + raw: "\n multiline\n", + cooked: "\n multiline\n" + }, + tail: true, + range: [12, 29], + loc: { + start: { + line: 1, + column: 12 + }, + end: { + line: 3, + column: 1 + } + } + } + ], + expressions: [], + range: [12, 29], + loc: { + start: { + line: 1, + column: 12 + }, + end: { + line: 3, + column: 1 + } + } + }, + range: [0, 29], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 3, + column: 1 + } + } + }, + range: [0, 30], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 3, + column: 2 + } + } + } + ], + sourceType: "script", + range: [0, 30], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 3, + column: 2 + } + }, + tokens: [ + { + type: "Identifier", + value: "tag", + range: [0, 3], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 3 + } + } + }, + { + type: "Punctuator", + value: "<", + range: [3, 4], + loc: { + start: { + line: 1, + column: 3 + }, + end: { + line: 1, + column: 4 + } + } + }, + { + type: "Identifier", + value: "generic", + range: [4, 11], + loc: { + start: { + line: 1, + column: 4 + }, + end: { + line: 1, + column: 11 + } + } + }, + { + type: "Punctuator", + value: ">", + range: [11, 12], + loc: { + start: { + line: 1, + column: 11 + }, + end: { + line: 1, + column: 12 + } + } + }, + { + type: "Template", + value: "`\n multiline\n`", + range: [12, 29], + loc: { + start: { + line: 1, + column: 12 + }, + end: { + line: 3, + column: 1 + } + } + }, + { + type: "Punctuator", + value: ";", + range: [29, 30], + loc: { + start: { + line: 3, + column: 1 + }, + end: { + line: 3, + column: 2 + } + } + } + ], + comments: [] +}); diff --git a/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-2.js b/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-2.js new file mode 100644 index 00000000000..4ac20dc735f --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-2.js @@ -0,0 +1,247 @@ +"use strict"; + +/* + * Parsed on astexplorer.net using @typescript-eslint/parser@1.4.2 + * + * Source: + * tag< + * generic + * >` + * multiline + * `; + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "TaggedTemplateExpression", + typeParameters: { + type: "TSTypeParameterInstantiation", + range: [3, 16], + loc: { + start: { + line: 1, + column: 3 + }, + end: { + line: 3, + column: 1 + } + }, + params: [ + { + type: "TSTypeReference", + typeName: { + type: "Identifier", + name: "generic", + range: [7, 14], + loc: { + start: { + line: 2, + column: 2 + }, + end: { + line: 2, + column: 9 + } + } + }, + range: [7, 14], + loc: { + start: { + line: 2, + column: 2 + }, + end: { + line: 2, + column: 9 + } + } + } + ] + }, + tag: { + type: "Identifier", + name: "tag", + range: [0, 3], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 3 + } + } + }, + quasi: { + type: "TemplateLiteral", + quasis: [ + { + type: "TemplateElement", + value: { + raw: "\n multiline\n", + cooked: "\n multiline\n" + }, + tail: true, + range: [16, 33], + loc: { + start: { + line: 3, + column: 1 + }, + end: { + line: 5, + column: 1 + } + } + } + ], + expressions: [], + range: [16, 33], + loc: { + start: { + line: 3, + column: 1 + }, + end: { + line: 5, + column: 1 + } + } + }, + range: [0, 33], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 5, + column: 1 + } + } + }, + range: [0, 34], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 5, + column: 2 + } + } + } + ], + sourceType: "script", + range: [0, 34], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 5, + column: 2 + } + }, + tokens: [ + { + type: "Identifier", + value: "tag", + range: [0, 3], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 3 + } + } + }, + { + type: "Punctuator", + value: "<", + range: [3, 4], + loc: { + start: { + line: 1, + column: 3 + }, + end: { + line: 1, + column: 4 + } + } + }, + { + type: "Identifier", + value: "generic", + range: [7, 14], + loc: { + start: { + line: 2, + column: 2 + }, + end: { + line: 2, + column: 9 + } + } + }, + { + type: "Punctuator", + value: ">", + range: [15, 16], + loc: { + start: { + line: 3, + column: 0 + }, + end: { + line: 3, + column: 1 + } + } + }, + { + type: "Template", + value: "`\n multiline\n`", + range: [16, 33], + loc: { + start: { + line: 3, + column: 1 + }, + end: { + line: 5, + column: 1 + } + } + }, + { + type: "Punctuator", + value: ";", + range: [33, 34], + loc: { + start: { + line: 5, + column: 1 + }, + end: { + line: 5, + column: 2 + } + } + } + ], + comments: [] +}); diff --git a/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-3.js b/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-3.js new file mode 100644 index 00000000000..e9018e1acdd --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-3.js @@ -0,0 +1,245 @@ +"use strict"; + +/* + * Parsed on astexplorer.net using @typescript-eslint/parser@1.4.2 + * + * Source: + * tag< + * generic + * >`multiline`; + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "TaggedTemplateExpression", + typeParameters: { + type: "TSTypeParameterInstantiation", + range: [3, 16], + loc: { + start: { + line: 1, + column: 3 + }, + end: { + line: 3, + column: 1 + } + }, + params: [ + { + type: "TSTypeReference", + typeName: { + type: "Identifier", + name: "generic", + range: [7, 14], + loc: { + start: { + line: 2, + column: 2 + }, + end: { + line: 2, + column: 9 + } + } + }, + range: [7, 14], + loc: { + start: { + line: 2, + column: 2 + }, + end: { + line: 2, + column: 9 + } + } + } + ] + }, + tag: { + type: "Identifier", + name: "tag", + range: [0, 3], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 3 + } + } + }, + quasi: { + type: "TemplateLiteral", + quasis: [ + { + type: "TemplateElement", + value: { + raw: "multiline", + cooked: "multiline" + }, + tail: true, + range: [16, 27], + loc: { + start: { + line: 3, + column: 1 + }, + end: { + line: 3, + column: 12 + } + } + } + ], + expressions: [], + range: [16, 27], + loc: { + start: { + line: 3, + column: 1 + }, + end: { + line: 3, + column: 12 + } + } + }, + range: [0, 27], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 3, + column: 12 + } + } + }, + range: [0, 28], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 3, + column: 13 + } + } + } + ], + sourceType: "script", + range: [0, 28], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 3, + column: 13 + } + }, + tokens: [ + { + type: "Identifier", + value: "tag", + range: [0, 3], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 3 + } + } + }, + { + type: "Punctuator", + value: "<", + range: [3, 4], + loc: { + start: { + line: 1, + column: 3 + }, + end: { + line: 1, + column: 4 + } + } + }, + { + type: "Identifier", + value: "generic", + range: [7, 14], + loc: { + start: { + line: 2, + column: 2 + }, + end: { + line: 2, + column: 9 + } + } + }, + { + type: "Punctuator", + value: ">", + range: [15, 16], + loc: { + start: { + line: 3, + column: 0 + }, + end: { + line: 3, + column: 1 + } + } + }, + { + type: "Template", + value: "`multiline`", + range: [16, 27], + loc: { + start: { + line: 3, + column: 1 + }, + end: { + line: 3, + column: 12 + } + } + }, + { + type: "Punctuator", + value: ";", + range: [27, 28], + loc: { + start: { + line: 3, + column: 12 + }, + end: { + line: 3, + column: 13 + } + } + } + ], + comments: [] +}); diff --git a/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-and-comment.js b/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-and-comment.js new file mode 100644 index 00000000000..e8c8ca148b6 --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-and-comment.js @@ -0,0 +1,325 @@ +"use strict"; + +/** + * Parsed on astexplorer.net using @typescript-eslint/parser@2.6.1 + * + * Source: + */ +// const x = aaaa< +// test +// >/* +// test +// */`foo` + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "VariableDeclaration", + declarations: [ + { + type: "VariableDeclarator", + id: { + type: "Identifier", + name: "x", + range: [6, 7], + loc: { + start: { + line: 1, + column: 6 + }, + end: { + line: 1, + column: 7 + } + } + }, + init: { + type: "TaggedTemplateExpression", + typeParameters: { + type: "TSTypeParameterInstantiation", + range: [14, 24], + loc: { + start: { + line: 1, + column: 14 + }, + end: { + line: 3, + column: 1 + } + }, + params: [ + { + type: "TSTypeReference", + typeName: { + type: "Identifier", + name: "test", + range: [18, 22], + loc: { + start: { + line: 2, + column: 2 + }, + end: { + line: 2, + column: 6 + } + } + }, + range: [18, 22], + loc: { + start: { + line: 2, + column: 2 + }, + end: { + line: 2, + column: 6 + } + } + } + ] + }, + tag: { + type: "Identifier", + name: "aaaa", + range: [10, 14], + loc: { + start: { + line: 1, + column: 10 + }, + end: { + line: 1, + column: 14 + } + } + }, + quasi: { + type: "TemplateLiteral", + quasis: [ + { + type: "TemplateElement", + value: { + raw: "foo", + cooked: "foo" + }, + tail: true, + range: [34, 39], + loc: { + start: { + line: 5, + column: 2 + }, + end: { + line: 5, + column: 7 + } + } + } + ], + expressions: [], + range: [34, 39], + loc: { + start: { + line: 5, + column: 2 + }, + end: { + line: 5, + column: 7 + } + } + }, + range: [10, 39], + loc: { + start: { + line: 1, + column: 10 + }, + end: { + line: 5, + column: 7 + } + } + }, + range: [6, 39], + loc: { + start: { + line: 1, + column: 6 + }, + end: { + line: 5, + column: 7 + } + } + } + ], + kind: "const", + range: [0, 39], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 5, + column: 7 + } + } + } + ], + sourceType: "module", + range: [0, 39], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 5, + column: 7 + } + }, + tokens: [ + { + type: "Keyword", + value: "const", + range: [0, 5], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 5 + } + } + }, + { + type: "Identifier", + value: "x", + range: [6, 7], + loc: { + start: { + line: 1, + column: 6 + }, + end: { + line: 1, + column: 7 + } + } + }, + { + type: "Punctuator", + value: "=", + range: [8, 9], + loc: { + start: { + line: 1, + column: 8 + }, + end: { + line: 1, + column: 9 + } + } + }, + { + type: "Identifier", + value: "aaaa", + range: [10, 14], + loc: { + start: { + line: 1, + column: 10 + }, + end: { + line: 1, + column: 14 + } + } + }, + { + type: "Punctuator", + value: "<", + range: [14, 15], + loc: { + start: { + line: 1, + column: 14 + }, + end: { + line: 1, + column: 15 + } + } + }, + { + type: "Identifier", + value: "test", + range: [18, 22], + loc: { + start: { + line: 2, + column: 2 + }, + end: { + line: 2, + column: 6 + } + } + }, + { + type: "Punctuator", + value: ">", + range: [23, 24], + loc: { + start: { + line: 3, + column: 0 + }, + end: { + line: 3, + column: 1 + } + } + }, + { + type: "Template", + value: "`foo`", + range: [34, 39], + loc: { + start: { + line: 5, + column: 2 + }, + end: { + line: 5, + column: 7 + } + } + } + ], + comments: [ + { + type: "Block", + value: "\ntest\n", + range: [24, 34], + loc: { + start: { + line: 3, + column: 1 + }, + end: { + line: 5, + column: 2 + } + } + } + ] +}); diff --git a/tests/lib/rules/no-unexpected-multiline.js b/tests/lib/rules/no-unexpected-multiline.js index 5811015c30f..6d0eba33ca5 100644 --- a/tests/lib/rules/no-unexpected-multiline.js +++ b/tests/lib/rules/no-unexpected-multiline.js @@ -82,7 +82,35 @@ ruleTester.run("no-unexpected-multiline", rule, { ` 5 / (5 / 5) - ` + `, + + // https://github.com/eslint/eslint/issues/11650 + { + code: ` + tag\` + multiline + \`; + `, + parser: require.resolve("../../fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-1") + }, + { + code: ` + tag< + generic + >\` + multiline + \`; + `, + parser: require.resolve("../../fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-2") + }, + { + code: ` + tag< + generic + >\`multiline\`; + `, + parser: require.resolve("../../fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-3") + } ], invalid: [ { @@ -215,6 +243,25 @@ ruleTester.run("no-unexpected-multiline", rule, { column: 17, messageId: "division" }] + }, + + // https://github.com/eslint/eslint/issues/11650 + { + code: ` + const x = aaaa< + test + >/* + test + */\`foo\` + `, + parser: require.resolve("../../fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-and-comment"), + errors: [ + { + line: 1, + column: 11, + messageId: "taggedTemplate" + } + ] } ] });