diff --git a/conf/eslint.json b/conf/eslint.json index 75e6431e608..774a06d8f59 100755 --- a/conf/eslint.json +++ b/conf/eslint.json @@ -233,6 +233,7 @@ "strict": "off", "symbol-description": "off", "template-curly-spacing": "off", + "template-tag-spacing": "off", "unicode-bom": "off", "use-isnan": "error", "valid-jsdoc": "off", diff --git a/docs/rules/template-tag-spacing.md b/docs/rules/template-tag-spacing.md new file mode 100644 index 00000000000..56d17f69337 --- /dev/null +++ b/docs/rules/template-tag-spacing.md @@ -0,0 +1,78 @@ +# Require or disallow spacing between template tags and their literals (template-tag-spacing) + +(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixes problems reported by this rule. + +With ES6, it's possible to create functions called [tagged template literals](#further-reading) where the function parameters consist of a template literal's strings and expressions. + +When using tagged template literals, it's possible to insert whitespace between the tag function and the template literal. Since this whitespace is optional, the following lines are equivalent: + +```js +let hello = func`Hello world`; +let hello = func `Hello world`; +``` + +## Rule Details + +This rule aims to maintain consistency around the spacing between template tag functions and their template literals. + +## Options + +```json +{ + "template-tag-spacing": ["error", "never"] +} +``` + +This rule has one option whose value can be set to "never" or "always" + +* `"never"` (default) - Disallows spaces between a tag function and its template literal. +* `"always"` - Requires one or more spaces between a tag function and its template literal. + +## Examples + +### never + +Examples of **incorrect** code for this rule with the default `"never"` option: + +```js +/*eslint template-tag-spacing: "error"*/ + +func `Hello world`; +``` + +Examples of **correct** code for this rule with the default `"never"` option: + +```js +/*eslint template-tag-spacing: "error"*/ + +func`Hello world`; +``` + +### always + +Examples of **incorrect** code for this rule with the `"always"` option: + +```js +/*eslint template-tag-spacing: ["error", "always"]*/ + +func`Hello world`; +``` + +Examples of **correct** code for this rule with the `"always"` option: + +```js +/*eslint template-tag-spacing: ["error", "always"]*/ + +func `Hello world`; +``` + +## When Not To Use It + +If you don't want to be notified about usage of spacing between tag functions and their template literals, then it's safe to disable this rule. + +## Further Reading + +If you want to learn more about tagged template literals, check out the links below: + +* [Template literals (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals) +* [Examples of using tagged template literals (Exploring ES6)](http://exploringjs.com/es6/ch_template-literals.html#_examples-of-using-tagged-template-literals) diff --git a/lib/rules/template-tag-spacing.js b/lib/rules/template-tag-spacing.js new file mode 100755 index 00000000000..808fe443891 --- /dev/null +++ b/lib/rules/template-tag-spacing.js @@ -0,0 +1,77 @@ +/** + * @fileoverview Rule to check spacing between template tags and their literals + * @author Jonathan Wilsson + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: "require or disallow spacing between template tags and their literals", + category: "Stylistic Issues", + recommended: false + }, + + fixable: "whitespace", + + schema: [ + { enum: ["always", "never"] } + ] + }, + + create(context) { + const never = context.options[0] !== "always"; + const sourceCode = context.getSourceCode(); + + /** + * Check if a space is present between a template tag and its literal + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkSpacing(node) { + const tagToken = sourceCode.getTokenBefore(node.quasi); + const literalToken = sourceCode.getFirstToken(node.quasi); + const hasWhitespace = sourceCode.isSpaceBetweenTokens(tagToken, literalToken); + + if (never && hasWhitespace) { + context.report({ + node, + loc: tagToken.loc.start, + message: "Unexpected space between template tag and template literal.", + fix(fixer) { + const comments = sourceCode.getComments(node.quasi).leading; + + // Don't fix anything if there's a single line comment after the template tag + if (comments.some(comment => comment.type === "Line")) { + return null; + } + + return fixer.replaceTextRange( + [tagToken.range[1], literalToken.range[0]], + comments.reduce((text, comment) => text + sourceCode.getText(comment), "") + ); + } + }); + } else if (!never && !hasWhitespace) { + context.report({ + node, + loc: tagToken.loc.start, + message: "Missing space between template tag and template literal.", + fix(fixer) { + return fixer.insertTextAfter(tagToken, " "); + } + }); + } + } + + return { + TaggedTemplateExpression: checkSpacing + }; + } +}; diff --git a/tests/lib/rules/template-tag-spacing.js b/tests/lib/rules/template-tag-spacing.js new file mode 100644 index 00000000000..22f6415acc7 --- /dev/null +++ b/tests/lib/rules/template-tag-spacing.js @@ -0,0 +1,277 @@ +/** + * @fileoverview Tests for template-tag-spacing rule. + * @author Jonathan Wilsson + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/template-tag-spacing"), + RuleTester = require("../../../lib/testers/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); + +ruleTester.run("template-tag-spacing", rule, { + valid: [ + "tag`name`", + { code: "tag`name`", options: ["never"] }, + { code: "tag `name`", options: ["always"] }, + "tag`hello ${name}`", + { code: "tag`hello ${name}`", options: ["never"] }, + { code: "tag `hello ${name}`", options: ["always"] }, + "tag/*here's a comment*/`Hello world`", + { code: "tag/*here's a comment*/`Hello world`", options: ["never"] }, + { code: "tag /*here's a comment*/`Hello world`", options: ["always"] }, + { code: "tag/*here's a comment*/ `Hello world`", options: ["always"] }, + "new tag`name`", + { code: "new tag`name`", options: ["never"] }, + { code: "new tag `name`", options: ["always"] }, + "new tag`hello ${name}`", + { code: "new tag`hello ${name}`", options: ["never"] }, + { code: "new tag `hello ${name}`", options: ["always"] }, + "(tag)`name`", + { code: "(tag)`name`", options: ["never"] }, + { code: "(tag) `name`", options: ["always"] }, + "(tag)`hello ${name}`", + { code: "(tag)`hello ${name}`", options: ["never"] }, + { code: "(tag) `hello ${name}`", options: ["always"] }, + "new (tag)`name`", + { code: "new (tag)`name`", options: ["never"] }, + { code: "new (tag) `name`", options: ["always"] }, + "new (tag)`hello ${name}`", + { code: "new (tag)`hello ${name}`", options: ["never"] }, + { code: "new (tag) `hello ${name}`", options: ["always"] } + ], + invalid: [ + { + code: "tag `name`", + output: "tag`name`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ] + }, + { + code: "tag `name`", + output: "tag`name`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ], + options: ["never"] + }, + { + code: "tag`name`", + output: "tag `name`", + errors: [ + { message: "Missing space between template tag and template literal." } + ], + options: ["always"] + }, + { + code: "tag /*here's a comment*/`Hello world`", + output: "tag/*here's a comment*/`Hello world`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ], + options: ["never"] + }, + { + code: "tag/*here's a comment*/ `Hello world`", + output: "tag/*here's a comment*/`Hello world`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ], + options: ["never"] + }, + { + code: "tag/*here's a comment*/`Hello world`", + output: "tag /*here's a comment*/`Hello world`", + errors: [ + { message: "Missing space between template tag and template literal." } + ], + options: ["always"] + }, + { + code: "tag // here's a comment \n`bar`", + output: "tag // here's a comment \n`bar`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ] + }, + { + code: "tag // here's a comment \n`bar`", + output: "tag // here's a comment \n`bar`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ], + options: ["never"] + }, + { + code: "tag `hello ${name}`", + output: "tag`hello ${name}`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ] + }, + { + code: "tag `hello ${name}`", + output: "tag`hello ${name}`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ], + options: ["never"] + }, + { + code: "tag`hello ${name}`", + output: "tag `hello ${name}`", + errors: [ + { message: "Missing space between template tag and template literal." } + ], + options: ["always"] + }, + { + code: "new tag `name`", + output: "new tag`name`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ] + }, + { + code: "new tag `name`", + output: "new tag`name`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ], + options: ["never"] + }, + { + code: "new tag`name`", + output: "new tag `name`", + errors: [ + { message: "Missing space between template tag and template literal." } + ], + options: ["always"] + }, + { + code: "new tag `hello ${name}`", + output: "new tag`hello ${name}`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ] + }, + { + code: "new tag `hello ${name}`", + output: "new tag`hello ${name}`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ], + options: ["never"] + }, + { + code: "new tag`hello ${name}`", + output: "new tag `hello ${name}`", + errors: [ + { message: "Missing space between template tag and template literal." } + ], + options: ["always"] + }, + { + code: "(tag) `name`", + output: "(tag)`name`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ] + }, + { + code: "(tag) `name`", + output: "(tag)`name`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ], + options: ["never"] + }, + { + code: "(tag)`name`", + output: "(tag) `name`", + errors: [ + { message: "Missing space between template tag and template literal." } + ], + options: ["always"] + }, + { + code: "(tag) `hello ${name}`", + output: "(tag)`hello ${name}`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ] + }, + { + code: "(tag) `hello ${name}`", + output: "(tag)`hello ${name}`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ], + options: ["never"] + }, + { + code: "(tag)`hello ${name}`", + output: "(tag) `hello ${name}`", + errors: [ + { message: "Missing space between template tag and template literal." } + ], + options: ["always"] + }, + { + code: "new (tag) `name`", + output: "new (tag)`name`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ] + }, + { + code: "new (tag) `name`", + output: "new (tag)`name`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ], + options: ["never"] + }, + { + code: "new (tag)`name`", + output: "new (tag) `name`", + errors: [ + { message: "Missing space between template tag and template literal." } + ], + options: ["always"] + }, + { + code: "new (tag) `hello ${name}`", + output: "new (tag)`hello ${name}`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ] + }, + { + code: "new (tag) `hello ${name}`", + output: "new (tag)`hello ${name}`", + errors: [ + { message: "Unexpected space between template tag and template literal." } + ], + options: ["never"] + }, + { + code: "new (tag)`hello ${name}`", + output: "new (tag) `hello ${name}`", + errors: [ + { message: "Missing space between template tag and template literal." } + ], + options: ["always"] + } + ] +});