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
Conversation
LGTM |
@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. |
There was a problem hiding this 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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);
There was a problem hiding this comment.
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)
?
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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.
LGTM |
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. |
There was a problem hiding this comment.
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.
LGTM |
@nzakas I updated the docs, I hope I understood you correctly. |
There was a problem hiding this 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. ;) )
There was a problem hiding this 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) |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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"
?
LGTM |
@kaicataldo Done and done! |
options: ["always"] | ||
}, | ||
{ | ||
code: "tag /*here's a comment*/`Hello world`", |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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), "")
);
}
});
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Rewrote the intro do be more descriptive about tagged template literals. Also includes a "Further Reading" section.
LGTM |
There was a problem hiding this 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!
Thanks for contributing! |
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:
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.