-
Notifications
You must be signed in to change notification settings - Fork 49
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
Catch & fix out-of-order assertion arguments #307
Conversation
@ninevra wow, a lot of effort must have gone into this! I wasn't online much this weekend so I haven't been able to look at this yet, sorry. |
@ninevra the code looks great to me… but I'm actually not all that familiar with our ESLint plugin. @sindresorhus would you be able to have a look? I'm happy with the design decisions. Thank you for calling them out so explicitly. I believe this is a breaking change? That's fine, we may as well make ESLint 7 the minimal version, but if you could confirm that'd be great 😄 |
outOfOrderErrors make sense and remain fixable in combination with message errors. In the presence of too many assertion arguments, something weird is going on, so it makes little sense to try to predict which of the arguments is the expected value. outOfOrderErrors can't occur in combination with too few assertion arguments.
Fixes eslint complexity warning on create().
a2b64cb
to
c08dea6
Compare
Rebased on v10.5.0 and pushed tests for @novemberborn, I agree this is a breaking change. It changes the behavior of an existing rule in a manner that is always enabled and could result in new reports without any change to the user's codebase. Users updating could see new reports, possibly breaking their CI; therefore, this should be a major version. If shipping this in a minor version is desired, the new behavior could be put behind an option or moved to an entirely new rule (and defaulted to off or warn in the recommended config). I implemented this as a breaking change because that appeared to be the consensus in #159 (comment). |
Thank for clarifying. I'll make sure we ship it as such. |
@sindresorhus would you have time to take a look at this? |
Is this something you intend to work on in this PR?
I think that's totally fine and pragmatic. This is an extreme edge-case nobody should be doing. It's not worth caring about.
You should open an issue on
I guess it's an incompatibility with the TypeScript parser. |
I can if you think it's warranted. I hadn't planned to work on it in this PR, because ideally the solution would involve upstreaming fixes for typescript parser compatibility and safer variable resolution in
I've already opened such an issue at mysticatea/eslint-utils#10. |
I think that could be useful, yes, but it's not worth blocking this PR. It can be done later on when the upstream changes are done. |
Thanks for your great work on this, @ninevra 👍🏻 |
Great! When we land #310 we can ship the next major version. |
I've isolated the cause of the Turning on variable resolution only appeared to trigger the problem because detailed explanation
test('.add() - throttled 10, concurrency 5', async t => {
const result: number[] = [];
const queue = new PQueue({
concurrency: 5,
intervalCap: 10,
interval: 1000,
autoStart: false
});
const firstValue = [...new Array(5).keys()];
const secondValue = [...new Array(10).keys()];
const thirdValue = [...new Array(13).keys()];
thirdValue.forEach(async value => queue.add(async () => {
await delay(300);
result.push(value);
}));
queue.start();
t.deepEqual(result, []);
(async () => {
await delay(400);
t.deepEqual(result, firstValue);
t.is(queue.pending, 5);
})();
(async () => {
await delay(700);
t.deepEqual(result, secondValue);
})();
(async () => {
await delay(1200);
t.is(queue.pending, 3);
t.deepEqual(result, secondValue);
})();
await delay(1400);
t.deepEqual(result, thirdValue);
}); With scope resolution off, TL;DR: there was no typescript compatibility error and this issue is already fixed in master. The only remaining issue blocking identifier resolution is mysticatea/eslint-utils#10. |
This PR adds the capability to detect and fix many cases where the user has called an assertion function with the expected value first and the actual value second. Closes #159. This is a breaking change.
If the first argument to
t.deepEqual()
,t.is()
,t.not()
,t.notDeepEqual()
,t.throws()
, ort.throwsAsync()
is a static expression, such as{a: [1, 2, 3]}.['a']
, and the second argument is a non-static expression, then theassertion-arguments
rule reports an error.Similarly, if the first argument to
t.assert()
,t.false()
,t.falsy()
,t.true()
, ort.truthy()
is a binary relation (e.g.1 < 2
), the left side of the relation is a static expression, and the right side of the relation is a non-static expression, then theassertion-arguments
rule reports an error.These errors are usually fixable; the autofix swaps the first and second arguments to the assertion. For the single-argument assertions, the relational operator is inverted if appropriate. They are not fixable if the autofix would change the relationship between either argument and any comment.
This should be a sound means of detecting expected arguments because supplying a static actual argument asserts nothing about the code under test. That is,
t.deepEqual({a: 1}, expected);
tests nothing.Static values are computed using
eslint-utils
'getStaticValue()
function, which seems quite comprehensive.eslint-utils
was already in the dependency graph ofeslint-plugin-ava
; this PR lifts it to top-level.The PR incidentally adds
assertion-arguments
support fort.throwsAsync()
,t.notThrowsAsync()
, andt.assert()
.t.throwsAsync()
andt.assert()
are compatible with the new functionality.Support for
t.like()
is blocked by PR #306. Merging #306 will enablet.like()
support, and this branch can then add tests fort.like()
support.Potentially interesting design decisions:
assertion-arguments
rather than adding a new rule, and don't provide an option to disable the check. This is per the previous discussion in Rule proposal: Put actual values first and expected values second in assertion #159.power-assert
, per the proposal in Rule proposal: Put actual values first and expected values second in assertion #159.t.is(/* left comment */ 'static', dynamic /* right comment */)
produces an error, but does not provide a fix.// comment
) must not be placed before any other tokens./* eslint-disable */
and/* eslint-enable */
are particularly problematic.t.throws()
andt.throwsAsync()
, where the thrower (analogous to actual) is often a function. Int.throws(() => {}, TypeError);
the left argument is static-valued (technically, all function expressions which don't close over dynamic values are static-valued) and the right is non-static. There is a mistake here, in that() => {}
tests nothing, but the arguments are clearly not out of order. Currently,eslint-utils#getStaticValue()
doesn't compute static-valued functions, but I've added a type-check in case a future minor version of it does.undefined
,NaN
,Infinity
) to be static. These are non-configurable, non-writable data properties of the global object, and can be shadowed in nested scopes by non-static values.Especially questionable design decisions:
a = 1
orvoid a()
) to be static.eslint-utils
, but it either has false positives (reporting side effects on pure static MemberExpresssions) or false negatives (reportingvoid ident.prop
as pure, even thoughident.prop
could be a getter or proxy trap), depending on configuration.Potential further improvements:
const
-declared immutable values could feasibly be proven static.undefined
andconst expected = 'static'; t.is(expected, actual);
.eslint-utils
; however, its implementation has multiple major issues.eslint-utils
trusts the user not to redefine globals likeundefined
orRegExp
. If the user does this, some identifiers will be erroneously treated as static, and reports might be issued in error.eslint-utils
treats mutable values as static whenever the initial declaration is static, and doesn't check for future modifications to the value. So,const dynamic = {a: 1}; dynamic.b = foo();
is erroneously reported as static. This could cause reports to be issued in error.eslint-utils
breaks thep-queue
integration test; lintingp-queue
'stest/test.ts
thereafter errors withExpected `index` to be a number.
Occurred while linting <...>/p-queue/test/test.ts:672
. I have not been able to identify the cause.🦄
IssueHunt Summary
Referenced issues
This pull request has been submitted to: