Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New: template-tag-spacing rule (fixes #7631) #7913

Merged
merged 8 commits into from Jan 27, 2017
Merged

New: template-tag-spacing rule (fixes #7631) #7913

merged 8 commits into from Jan 27, 2017

Conversation

jwilsson
Copy link
Contributor

What is the purpose of this pull request? (put an "X" next to item)

[ ] Documentation update
[ ] Bug fix (template)
[ X ] New rule (template)
[ ] Changes an existing rule (template)
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

Please describe what the rule should do:
This rule is very similiar to func-call-spacing in the way it will enforce spacing between template tags and their corresponding template literal.

What category of rule is this? (place an "X" next to just one item)

[ X ] Enforces code style
[ ] Warns about a potential error
[ ] Suggests an alternate way of doing something
[ ] Other (please specify:)

Provide 2-3 code examples that this rule will warn about:

/* eslint template-tag-spacing: ["error", "never"] */
func `foo`
/* eslint template-tag-spacing: ["error", "always"] */
func`foo`

Why should this rule be included in ESLint (instead of a plugin)?
Since it's really similar to func-call-spacing which is already included in ESLint it makes sense to enforce this styling too, especially since tagged template literals will become more and more common.

@eslintbot
Copy link

LGTM

@mention-bot
Copy link

@jwilsson, thanks for your PR! By analyzing the history of the files in this pull request, we identified @nzakas, @alberto and @kaicataldo to be potential reviewers.

Copy link
Member

@not-an-aardvark not-an-aardvark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This mostly looks good to me -- I just have a few small suggestions.


(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixes problems reported by this rule.

We can create functions whose parameters consist of template literals.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this line is slightly incorrect -- the function's arguments are the template strings and expressions, but the template literal itself doesn't get passed as an argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you're right. I rewrote the whole first paragraph and added a "Further reading" section to explain the whole concept a bit better.

*/
function checkSpacing(node) {
const tagToken = sourceCode.getLastToken(node.tag);
const literalToken = sourceCode.getTokenAfter(tagToken);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines will work incorrectly if the tag is surrounded by parens:

(foo)`bar`

As currently implemented, tagToken will refer to foo, and literalToken will refer to ), which isn't the intended behavior.

To fix this, I think it would be easier to get the tokens relative to the literal instead, since the literal can't be surrounded by parentheses.

const tagToken = sourceCode.getTokenBefore(node.quasi);
const literalToken = sourceCode.getFirstToken(node.quasi);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@not-an-aardvark Won't the first line in your example also spit out the ) token? Should that be sourceCode.getFirstToken(node.tag)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understood you correctly, but I think we do want the ) token in this case -- we care about the spacing between the ) and the template, not the value inside the parens and the template.

// we care about this space ↓
(          foo          )        `bar`
 // not this one ↑

@@ -231,6 +231,7 @@
"strict": "off",
"symbol-description": "off",
"template-curly-spacing": "off",
"template-tag-spacing": "off",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind also adding this to packages/eslint-config-eslint/default.yml and turning it on? I think it would be useful to dogfood the rule in the ESLint codebase.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@not-an-aardvark Should that be in a separate (chore) PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be fine with either.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea on the dog fooding! But agreed with @platinumazure that it should probably be a separate PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be happy to submit a chore PR once this one is finished.

meta: {
docs: {
description: "require or disallow spacing between template tags and their literals",
category: "ECMAScript 6",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, but I think we're generally trying to move stuff out of the "ECMAScript 6" category (also see eslint/archive-website#310). In my opinion, it might be better to put this in "Stylistic Issues".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Stylistic issues" sounds good for me as well.

@not-an-aardvark not-an-aardvark added accepted There is consensus among the team that this change meets the criteria for inclusion feature This change adds a new feature to ESLint rule Relates to ESLint's core rules labels Jan 12, 2017
@eslintbot
Copy link

LGTM

@jwilsson
Copy link
Contributor Author

I made the suggested changes. I rewrote the first paragraph of the documentation as well, to better explain what "tagged template literals" is.


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.

This rule can force usage of spacing _between_ the tag function and the template literal.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section shouldn't be about the rule, it should just be about the concept the rule relates to. So, can you remove this line and do a bit more explanation around what a tagged template is where spacing is allowed? I'd like to see a complete example with the function definition.

@eslintbot
Copy link

LGTM

@jwilsson
Copy link
Contributor Author

@nzakas I updated the docs, I hope I understood you correctly.

Copy link
Member

@nzakas nzakas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good enough for me! (And I'll try to overlook the missing link to my book in Further Reading. ;) )

Copy link
Member

@kaicataldo kaicataldo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for contributing! I have a few nitpicks about the documentation, and would like to request tests for when there's a comment between the tag and the template literal to make it clear what the behavior should be:

func/*here's a comment*/`Hello world`; // This should not warn
func /*here's a comment*/`Hello world`; // This should
func/*here's a comment*/ `Hello world`; // As should this

@@ -0,0 +1,78 @@
# Enforce Usage of Spacing in Tagged Template Literals (template-tag-spacing)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update this to match this description?

i.e. Require or disallow spacing between template tags and their literals (template-tag-spacing)

}
```

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there's a word missing here - maybe This rule has one option whose value can be set to "never" or "always"?

@eslintbot
Copy link

LGTM

@jwilsson
Copy link
Contributor Author

@kaicataldo Done and done!

options: ["always"]
},
{
code: "tag /*here's a comment*/`Hello world`",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating this! One final thing - this still gets autofixed so we do need an output property (even if it's just to assert that it doesn't get fixed). With the current logic, I believe it will remove the comment, correct? My preference would be to either figure out a way to keep the comment in the fixed output or to not fix if a comment exists (with a strong preference for the former), rather than simply remove the comment. Thoughts?

@not-an-aardvark Can you be a resource here? I think you've written more autofixes than the rest of us combined by now!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the best way to fix this would be to get a list of all the comments between the tag and the template literal, then concatenate them to get the resulting text between the two tokens. This would preserve the whitespace in the comments themselves and remove any other whitespace.

However, we should make sure we don't change the behavior of this code:

foo // line comment
`bar`

The implementation I mentioned above would fix that to

foo// line comment`bar`

To avoid this, I think we should avoid doing a fix if there are any line comments between the tag and the template literal.

So I would do something along the lines of

context.report({
  // ...
  fix(fixer) {
    const comments = sourceCode.getComments(node.quasi).leading;

    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), "")
    );
  }
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, of course. Sorry about that!

But I'm guessing we shouldn't try to fix anything in cases like this:

/* eslint template-tag-spacing: ["error", "always"] */
tag/*here's a comment*/`Hello world`

Since we wouldn't know where to put the space.

@not-an-aardvark Thanks for the explanation and example!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwilsson That seems reasonable. Another option would be to just always put the space immediately after the tag.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, I added the expected output to all tests and opted for the "space immediately after tag" approach when autofixing comments.

@eslintbot
Copy link

LGTM

Copy link
Member

@kaicataldo kaicataldo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great to me. Thanks for contributing to ESLint!

@not-an-aardvark not-an-aardvark merged commit 09546a4 into eslint:master Jan 27, 2017
@not-an-aardvark
Copy link
Member

Thanks for contributing!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion archived due to age This issue has been archived; please open a new issue for any further discussion feature This change adds a new feature to ESLint rule Relates to ESLint's core rules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants