Skip to content

Commit

Permalink
New: template-curly-spacing rule (fixes #5049)
Browse files Browse the repository at this point in the history
  • Loading branch information
mysticatea committed Jan 31, 2016
1 parent 2dfa5dc commit e96ffd2
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/eslint.json
Expand Up @@ -194,6 +194,7 @@
"space-unary-ops": 0,
"spaced-comment": 0,
"strict": 0,
"template-curly-spacing": 0,
"use-isnan": 2,
"valid-jsdoc": 0,
"valid-typeof": 2,
Expand Down
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -236,6 +236,7 @@ These rules are only relevant to ES6 environments.
* [prefer-spread](prefer-spread.md) - suggest using the spread operator instead of `.apply()`.
* [prefer-template](prefer-template.md) - suggest using template literals instead of strings concatenation
* [require-yield](require-yield.md) - disallow generator functions that do not have `yield`
* [template-curly-spacing](template-curly-spacing.md) - enforce spacing around embedded expressions of template strings (fixable)
* [yield-star-spacing](yield-star-spacing.md) - enforce spacing around the `*` in `yield*` expressions (fixable)


Expand Down
79 changes: 79 additions & 0 deletions docs/rules/template-curly-spacing.md
@@ -0,0 +1,79 @@
# Enforce Usage of Spacing in Template Strings (template-curly-spacing)

We can embed expressions in template strings with using a pair of `${` and `}`.
This rule can force usage of spacing inside of the curly brace pair according to style guides.

```js
let hello = `hello, ${people.name}!`;
```

**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line.

## Rule Details

This rule aims to maintain consistency around the spacing inside of template literals.

### Options

```json
{
"template-curly-spacing": [2, "never"]
}
```

This rule has one option which has either `"never"` or `"always"` as value.

* `"never"` (by default) - Disallows spaces inside of the curly brace pair.
* `"always"` - Requires one or more spaces inside of the curly brace pair.

The following patterns are considered problems when configured `"never"`:

```js
/*eslint template-curly-spacing: 2*/

`hello, ${ people.name}!`; /*error Unexpected space(s) after '${'.*/
`hello, ${people.name }!`; /*error Unexpected space(s) before '}'.*/

`hello, ${ people.name }!`; /*error Unexpected space(s) after '${'.*/
/*error Unexpected space(s) before '}'.*/
```

The following patterns are considered problems when configured `"always"`:

```js
/*eslint template-curly-spacing: [2, "always"]*/

`hello, ${ people.name}!`; /*error Expected space(s) before '}'.*/
`hello, ${people.name }!`; /*error Expected space(s) after '${'.*/

`hello, ${people.name}!`; /*error Expected space(s) after '${'.*/
/*error Expected space(s) before '}'.*/
```

The following patterns are not considered problems when configured `"never"`:

```js
/*eslint template-curly-spacing: 2*/

`hello, ${people.name}!`;

`hello, ${
people.name
}!`;
```

The following patterns are not considered problems when configured `"always"`:

```js
/*eslint template-curly-spacing: [2, "always"]*/

`hello, ${ people.name }!`;

`hello, ${
people.name
}!`;
```

## When Not To Use It

If you don't want to be notified about usage of spacing inside of template strings, then it's safe to disable this rule.
102 changes: 102 additions & 0 deletions lib/rules/template-curly-spacing.js
@@ -0,0 +1,102 @@
/**
* @fileoverview Rule to enforce spacing around embedded expressions of template strings
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

var astUtils = require("../ast-utils");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

var OPEN_PAREN = /\$\{$/;
var CLOSE_PAREN = /^\}/;

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = function(context) {
var sourceCode = context.getSourceCode();
var always = context.options[0] === "always";
var prefix = always ? "Expected" : "Unexpected";

/**
* Checks spacing before `}` of a given token.
* @param {Token} token - A token to check. This is a Template token.
* @returns {void}
*/
function checkSpacingBefore(token) {
var prevToken = sourceCode.getTokenBefore(token);
if (prevToken &&
CLOSE_PAREN.test(token.value) &&
astUtils.isTokenOnSameLine(prevToken, token) &&
sourceCode.isSpaceBetweenTokens(prevToken, token) !== always
) {
context.report({
loc: token.loc.start,
message: prefix + " space(s) before '}'.",
fix: function(fixer) {
if (always) {
return fixer.insertTextBefore(token, " ");
}
return fixer.removeRange([
prevToken.range[1],
token.range[0]
]);
}
});
}
}

/**
* Checks spacing after `${` of a given token.
* @param {Token} token - A token to check. This is a Template token.
* @returns {void}
*/
function checkSpacingAfter(token) {
var nextToken = sourceCode.getTokenAfter(token);
if (nextToken &&
OPEN_PAREN.test(token.value) &&
astUtils.isTokenOnSameLine(token, nextToken) &&
sourceCode.isSpaceBetweenTokens(token, nextToken) !== always
) {
context.report({
loc: {
line: token.loc.end.line,
column: token.loc.end.column - 2
},
message: prefix + " space(s) after '${'.",
fix: function(fixer) {
if (always) {
return fixer.insertTextAfter(token, " ");
}
return fixer.removeRange([
token.range[1],
nextToken.range[0]
]);
}
});
}
}

return {
TemplateElement: function(node) {
var token = sourceCode.getFirstToken(node);
checkSpacingBefore(token);
checkSpacingAfter(token);
}
};
};

module.exports.schema = [
{enum: ["always", "never"]}
];
104 changes: 104 additions & 0 deletions tests/lib/rules/template-curly-spacing.js
@@ -0,0 +1,104 @@
/**
* @fileoverview Tests for template-curly-spacing rule.
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

var rule = require("../../../lib/rules/template-curly-spacing"),
RuleTester = require("../../../lib/testers/rule-tester");

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

var ruleTester = new RuleTester();
ruleTester.run("template-curly-spacing", rule, {
valid: [
{code: "{ foo }"},
{code: "`${foo} ${bar}`", parserOptions: {ecmaVersion: 6}},
{code: "`${foo} ${bar} ${\n baz\n}`", options: ["never"], parserOptions: {ecmaVersion: 6}},
{code: "`${ foo } ${ bar } ${\n baz\n}`", options: ["always"], parserOptions: {ecmaVersion: 6}},
{code: "tag`${foo} ${bar}`", parserOptions: {ecmaVersion: 6}},
{code: "tag`${foo} ${bar} ${\n baz\n}`", options: ["never"], parserOptions: {ecmaVersion: 6}},
{code: "tag`${ foo } ${ bar } ${\n baz\n}`", options: ["always"], parserOptions: {ecmaVersion: 6}}
],
invalid: [
{
code: "`${ foo } ${ bar }`",
output: "`${foo} ${bar}`",
errors: [
{message: "Unexpected space(s) after '${'.", column: 2},
{message: "Unexpected space(s) before '}'.", column: 9},
{message: "Unexpected space(s) after '${'.", column: 11},
{message: "Unexpected space(s) before '}'.", column: 18}
],
parserOptions: {ecmaVersion: 6}
},
{
code: "`${ foo } ${ bar }`",
output: "`${foo} ${bar}`",
errors: [
{message: "Unexpected space(s) after '${'.", column: 2},
{message: "Unexpected space(s) before '}'.", column: 9},
{message: "Unexpected space(s) after '${'.", column: 11},
{message: "Unexpected space(s) before '}'.", column: 18}
],
options: ["never"],
parserOptions: {ecmaVersion: 6}
},
{
code: "`${foo} ${bar}`",
output: "`${ foo } ${ bar }`",
errors: [
{message: "Expected space(s) after '${'.", column: 2},
{message: "Expected space(s) before '}'.", column: 7},
{message: "Expected space(s) after '${'.", column: 9},
{message: "Expected space(s) before '}'.", column: 14}
],
options: ["always"],
parserOptions: {ecmaVersion: 6}
},
{
code: "tag`${ foo } ${ bar }`",
output: "tag`${foo} ${bar}`",
errors: [
{message: "Unexpected space(s) after '${'.", column: 5},
{message: "Unexpected space(s) before '}'.", column: 12},
{message: "Unexpected space(s) after '${'.", column: 14},
{message: "Unexpected space(s) before '}'.", column: 21}
],
parserOptions: {ecmaVersion: 6}
},
{
code: "tag`${ foo } ${ bar }`",
output: "tag`${foo} ${bar}`",
errors: [
{message: "Unexpected space(s) after '${'.", column: 5},
{message: "Unexpected space(s) before '}'.", column: 12},
{message: "Unexpected space(s) after '${'.", column: 14},
{message: "Unexpected space(s) before '}'.", column: 21}
],
options: ["never"],
parserOptions: {ecmaVersion: 6}
},
{
code: "tag`${foo} ${bar}`",
output: "tag`${ foo } ${ bar }`",
errors: [
{message: "Expected space(s) after '${'.", column: 5},
{message: "Expected space(s) before '}'.", column: 10},
{message: "Expected space(s) after '${'.", column: 12},
{message: "Expected space(s) before '}'.", column: 17}
],
options: ["always"],
parserOptions: {ecmaVersion: 6}
}
]
});

0 comments on commit e96ffd2

Please sign in to comment.