-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
feat: make no-misleading-character-class
report more granular errors
#18082
Conversation
✅ Deploy Preview for docs-eslint canceled.
|
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 it's fine to produce more errors here and not mark it as breaking. It seems more like a bug fix to me.
default: | ||
return null; | ||
// Only literals and expression-less templates generate granular errors. | ||
if (!(node.type === "TemplateLiteral" && !node.expressions.length || node.type === "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.
Finding this a bit difficult to understand, maybe this?
if (!(node.type === "TemplateLiteral" && !node.expressions.length || node.type === "Literal")) { | |
const isTemplateWithoutExpressions = node.type === "TemplateLiteral" && node.expressions.length === 0; | |
if (!isTemplateWithoutExpressions && node.type !== "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.
Funny, I was going to suggest if (node.type !== "TemplateLiteral" || (node.type === "Literal" && node.expressions.length)) {
. No preference between the two from me. 🙂
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! I realized that that check was always falsy, so I just removed it. It's because strings and templates without expressions always produce granular reports now, and so the default case isn't hit any more. Somehow I missed that while testing.
package.json
Outdated
@@ -72,6 +72,7 @@ | |||
"@nodelib/fs.walk": "^1.2.8", | |||
"ajv": "^6.12.4", | |||
"chalk": "^4.0.0", | |||
"char-source": "^0.0.0", |
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, since you're the author of this package, is there any reason it's just not included in this PR as a utility?
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.
Note that that package contains logic that isn't needed in ESLint. The whole validation part for one, including 50% of the unit tests, is not necessary because the syntax of the arguments can be assumed to be valid since it was checked by the parser. There's also additional information about used features (for example, to tell if a string literal is valid in strict mode or not) which seems superfluous unless we expect to use it in ESLint somewhere later.
So if we are going to extract a utility from that package we should probably do some refactoring to remove the unnecessary logic.
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.
Yes, let's remove the unnecessary code, of course. As it seems like the package was created just for this purpose (?), I'm assuming that's not an issue.
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.
Done in 53e262a, thanks!
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 a great start! 👏 on going through the effort of making the parser. Looks from the files on GitHub like it was a lot of tricky work.
Since you mentioned bringing the code inline, requesting changes on that before doing a deeper review. Also I think I might have missed something with the charInfos
caching?
}] | ||
}, | ||
|
||
/* eslint-disable lines-around-comment -- see https://github.com/eslint/eslint/issues/18081 */ |
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.
[Question] Why not put the comment outside the code
? It's not necessary for the test, so I'd think that preferable even ignoring the lines-around-comment
shenanigan.
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.
Well, that's not for the comment inside the code
, it's this comment that is producing errors now. I though that the comments in the code would make it easier to locate a test in the source code given its title, because the code
as it's calculated here doesn't match what is printed in the console.
default: | ||
return null; | ||
// Only literals and expression-less templates generate granular errors. | ||
if (!(node.type === "TemplateLiteral" && !node.expressions.length || node.type === "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.
Funny, I was going to suggest if (node.type !== "TemplateLiteral" || (node.type === "Literal" && node.expressions.length)) {
. No preference between the two from me. 🙂
lib/rules/utils/char-source.js
Outdated
* @returns {Generator<CodeUnit>} Zero, one or two `CodeUnit` elements. | ||
*/ | ||
function *mapEscapeSequenceOrLineContinuation(reader) { | ||
const start = reader.pos++; |
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.
A little unclear here...are you intentionally moving the reader position by one? Or did you intend for const start = reader.pos + 1
?
Either way, I think this line needs reworking to make its intention clear.
lib/rules/utils/char-source.js
Outdated
/** | ||
* An object used to keep track of the position in a source text where the next characters will be read. | ||
*/ | ||
class SourceReader { |
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 might rename this as TextSource
. SourceReader
implies that there's a read()
method or something that this object is supposed to be doing, but it's just a data structure.
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 the feedback. I've added a read()
method in 448d9a9 to read characters relative to the current position. So it's no longer necessary to store the position in a variable before accessing the source like it was done before. And I'm using +=
for relative position changes. I think this makes the code a little clearer but if that's not the case we can revert to how it was done previously and just address the suggestions.
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 like the read()
!
SourceReader
is still a little ambiguous IMO. Maybe, CharsReader
? CharactersReader
? TextReader
? Not a blocker from my end.
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! I don't have a preference for the name, so pick one that sounds better to you.
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 vote for TextReader
then, to align with #18082 (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.
Renamed in 072f256.
@@ -32,6 +32,12 @@ class SourceReader { | |||
this.source = source; | |||
this.pos = 0; | |||
} | |||
|
|||
read(offset = 0, length = 1) { |
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 add some JSDoc here?
lib/rules/utils/char-source.js
Outdated
|
||
reader.pos = posAfterBackslash + octalStr.length; | ||
reader.pos += octalStr.length - 1; |
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.
Because you refactored to include a read()
method in SourceReader
, it might make things clearer if you also created a advance()
method that moves pos
rather than augmenting the value like this. So this code would become:
reader.pos += octalStr.length - 1; | |
reader.advance(octalStr.length - 1); |
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 👍🏻
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.
All done in b1cf05f.
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.
LGTM. Would like @JoshuaKGoldberg to review his changes before merging.
codeUnits ??= parseStringLiteral(source); | ||
start = offset + codeUnits[firstIndex].start; | ||
end = offset + codeUnits[lastIndex].end; | ||
} else { // RegExp 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.
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.
Fixed in 7bb7e55.
let start; | ||
let end; | ||
|
||
if (node.type === "TemplateLiteral") { |
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.
We should check if the template literal has expressions. For example, this crashes:
/* eslint no-misleading-character-class: "error" */
new RegExp(`${"[👍]"}`);
TypeError: Cannot read properties of undefined (reading 'start')
Occurred while linting C:\projects\eslint\foo.js:3
Rule: "no-misleading-character-class"
at C:\projects\eslint\lib\rules\no-misleading-character-class.js:294:64
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.
Correct! Those cases should be covered by unit tests.
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.
Fixed in 7bb7e55.
This could be a bug in the current implementation: RegExp(/[👍]/u);
The rule complains about a missing console.log(RegExp(/[👍]/u).flags);
// "u" |
Prerequisites checklist
What is the purpose of this pull request? (put an "X" next to an item)
[ ] Documentation update
[ ] Bug fix (template)
[ ] New rule (template)
[X] Changes an existing rule (template)
[ ] Add autofix to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:
What rule do you want to change?
no-misleading-character-class
What change do you want to make (place an "X" next to just one item)?
[X] Generate more warnings
[ ] Generate fewer warnings
[ ] Implement autofix
[ ] Implement suggestions
How will the change be implemented (place an "X" next to just one item)?
[ ] A new option
[X] A new default behavior
[ ] Other
Please provide some example code that this change will affect:
What does the rule currently do for this code?
Reports one problem per
RegExp
expression (Playground link).What will the rule do after it's changed?
A granular problem will be reported for each problematic character class. For the first
RegExp
, two problems will be reported.What changes did you make? (Give an overview)
This is an attempt to extend granular error reporting for the
no-misleading-character-class
rule to string and template literal patterns whose source code doesn't match the string value. This includes literals with escape sequences, line continuations, and unescaped CRLF line breaks in templates.This change was suggested during the implementation of granular errors, but it was postponed to keep the work manageable: #17515 (comment).
Is there anything you'd like reviewers to focus on?
new RegExp(`[ \\ufe0f]${0}`)
, but I haven't explored that yet.