-
Notifications
You must be signed in to change notification settings - Fork 181
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
Create rule S6959: "Array.reduce()" calls should include an initial value #4626
Conversation
70a144f
to
ecefde7
Compare
ecefde7
to
5f2dbfe
Compare
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.
Please add an invalid test case before merging
code: 'xs.reduce((acc, x) => acc + x);', | ||
}, | ||
], | ||
invalid: [], |
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.
could you add an invalid case?
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.
If you come back to the rule implementation, you might have noticed this weird check:
SonarJS/packages/jsts/src/rules/S6959/rule.ts
Lines 32 to 35 in 5f2dbfe
const services = context.parserServices; | |
if (!isRequiredParserServices(services)) { | |
return {}; | |
} |
While the function isRequiredParserServices
isn't well-named, it actually tests that type-checking is available. If not, the rule turns into a noop. In order to test this behavior, this file uses ESLint rule tester for code coverage purposes. We currently don't have any other way to test this edge case (or at least, we haven't invested time on a better solution so far). That's why there is intentionally no invalid test case here.
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 reading and re-reading and I don't understand.
In this test, is there type-checking available?
I assume no, and thanks to that this rule becomes a no-op, as the code sample that is provided is missing the initial value.
Why isn't there typechecking in this test then? What should be the parserOptions to include type-checking? Or am I completely off-base here?
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.
When we parse JS/TS code, we rely on TypeScript ESLint parser which is able to parse both syntaxes. In addition, TypeScript ESLint wraps some utilities from TypeScript compiler, including type-checking. However, to benefit from type-checking, you need to configure the parsing options with a TSConfig somewhere.
By default, ESLint's rule tester uses Esprima (I think) to parse the code being tested. If you want to have type-checking available while using this rule tester, you first need to manually configure the parser to use as follows:
SonarJS/packages/jsts/src/rules/S117/unit.test.ts
Lines 23 to 26 in 911655e
const ruleTester = new RuleTester({ | |
parser: require.resolve('@typescript-eslint/parser'), | |
parserOptions: { ecmaVersion: 2018 }, | |
}); |
However, this is not enough as it only allows us to parse TypeScript syntax as well. To enable type-checking, you need to tweak ESLint's rule tester with a TSConfig. I am not going to show how because we want to avoid that and prefer using the comment-based testing framework which always makes type-checking available.
I'm reading and re-reading and I don't understand. In this test, is there type-checking available?
No, type-checking is not available because we use ESLint's rule tester without customization.
I assume no, and thanks to that this rule becomes a no-op, as the code sample that is provided is missing the initial value.
Exactly!
Why isn't there typechecking in this test then? What should be the parserOptions to include type-checking? Or am I completely off-base here?
- Configure the parser options to use TypeScript ESLint parser
- Configure ESLint's rule tester with a TSConfig
return xs.reduce((acc, x) => acc + x, 0); // Compliant | ||
} | ||
|
||
function coverage(x: any) { |
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.
what is this for?
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 file as a whole is not executed at all but rather denotes a test file with actual unit tests. The file uses a homemade testing framework that we call comment-based testing framework. A test file contains special comments indicating that a rule should raise an issue at specific locations. For instance, this is what is meant here:
SonarJS/packages/jsts/src/rules/S6959/cb.fixture.ts
Lines 1 to 3 in 5f2dbfe
function noncompliant(xs: number[]) { | |
return xs.reduce((acc, x) => acc + x); // Noncompliant {{Add an initial value to this "reduce()" call.}} | |
} |
The comment // Noncompliant
indicates that an issue should be raised at line 2 and whatever is inside {{...}}
is the expected message of the reported issue. We could assert even more things like code fixes, starting and ending columns, and secondary locations.
Coming back to your question, I guess you should understand by now what's the purpose of the function coverage
. No issue will be raised here, the rule will visit those method invocations for the sake of increasing the code coverage of the rule implementation, this line in other words:
if (isCallingMethod(node, 1, 'reduce') && isArray(node.callee.object, services)) { |
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.
So this is just to increase code-coverage? That seems counter-productive and actually adds confusion where it shouldn't.
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 don't necessarily agree that's confusing since the test file explicitly separates what's noncompliant from what's compliant and what's for coverage. Maybe I am missing something to make it less confusion.
Feel free to suggest anything that comes to your mind :)
"status": "ready", | ||
"remediation": { | ||
"func": "Constant\/Issue", | ||
"constantCost": "5min" |
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.
heh, that's generous if not intimidating
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 point! Could you actually comment on this PR?
Each rule has a description, and a rule description is stored in the RSPEC GitHub repository, a company-wide database for all Sonar rule descriptions. New descriptions or description updates are first introduced in this repository through pull requests and eventually merged. To bring back those changes in a given analyzer, we then use an internal tool called rule-api
that fetches any changes for any rule of the analyzer or changes for a specific rule.
You will get familiar with this tool tomorrow when starting the release process of the analyzer.
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.
Thank you for the explanations. It's now making a bit more sense, but still I left a few questions.
return xs.reduce((acc, x) => acc + x, 0); // Compliant | ||
} | ||
|
||
function coverage(x: any) { |
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.
So this is just to increase code-coverage? That seems counter-productive and actually adds confusion where it shouldn't.
code: 'xs.reduce((acc, x) => acc + x);', | ||
}, | ||
], | ||
invalid: [], |
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 reading and re-reading and I don't understand.
In this test, is there type-checking available?
I assume no, and thanks to that this rule becomes a no-op, as the code sample that is provided is missing the initial value.
Why isn't there typechecking in this test then? What should be the parserOptions to include type-checking? Or am I completely off-base here?
function isArray(node: estree.Node) { | ||
if (isRequiredParserServices(services)) { | ||
return isArrayType(node, services); | ||
} else { | ||
return isArrayExpression(getUniqueWriteUsageOrNode(context, node)); | ||
} | ||
} |
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.
@zglicz I updated the array detection so that it tries to determine syntactically if a node denotes an array in case type-checking is missing for whatever reason.
56e5486
to
7e77e0d
Compare
7e77e0d
to
c6a23d3
Compare
Quality Gate passedIssues Measures |
Fixes #4591