-
Notifications
You must be signed in to change notification settings - Fork 22
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 rule: no-hooks-from-ancestor-modules (#93) #94
New rule: no-hooks-from-ancestor-modules (#93) #94
Conversation
2fdd644
to
3889d07
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.
Hi @raycohen, thanks for contributing!
This is a good first pass, but there are a few things that could be improved:
First, the documentation's examples are a little confusing, because there are extra hook calls that aren't actually impacted by the rule-- but this could be confusing to the reader, who wants to know what lines will or won't warn based on how they invoke hooks. I've left some comments.
At a high level, just understand that it's more important to show the ESLint workings in this documentation, rather than show sensible/typical QUnit usage. (For example, of course an ancestor module that declares hooks
would use them at the outer level before declaring the inner module-- but that's not important for the ESLint rule and it could just get in the way of the user trying to understand this rule.)
Also, this could use some more test cases. Please add some test cases involving:
- Use of multiple hooks in a single module level (including multiple uses of the same hook, such as multple
beforeEach
calls) - Three levels of modules
- Anything else you can think of that would show the rule as being more robust 😄
Thanks again for contributing, I really appreciate it! Let me know if you have any questions on this review.
```js | ||
|
||
QUnit.module("outer module", function(hooks) { | ||
hooks.beforeEach(function() {}); |
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 line should not warn, so it might be good to simplify this example by removing this line (or maybe just adding a comment saying no warning would appear on this line). That way, the inner hooks.beforeEach
is the only code that a reader would interpret as having a lint error.
|
||
|
||
QUnit.module("outer module", function(outerModuleHooks) { | ||
outerModuleHooks.beforeEach(function() {}); |
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 line should not warn, so it might be good to simplify this example by removing this line (or maybe just adding a comment saying no warning would appear on this line). That way, the inner outerHooks.beforeEach
is the only code that a reader would interpret as having a lint error.
outerModuleHooks.beforeEach(function() {}); | ||
|
||
QUnit.module("inner module", function(innerModuleHooks) { | ||
innerModuleHooks.beforeEach(function() {}); |
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 line should not warn, so it might be good to simplify this example by removing this line (or maybe just adding a comment saying no warning would appear on this line). That way, the outerModuleHooks.beforeEach
is the only code that a reader would interpret as having a lint error.
}, | ||
fixable: null, | ||
messages: { | ||
noHooksFromAncestorModules: "Do not use hooks from an ancestor module." |
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 message is fine, but since you're passing a context, you might as well use it here!
I would suggest using the full text of the hook call (use context.getSourceCode().getText()
on the callee node of the CallExpression) and use a message similar to the following: "{{hook}} is from an ancestor module."
What do you think?
"CallExpression:exit": function (node) { | ||
if (utils.isModule(node.callee)) { | ||
contextStack.pop(); | ||
hooksStack.pop(); |
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 doesn't look correct. It's possible that a module could use multiple hooks (e.g., beforeEach and afterEach), so having only one pop
here probably won't always work.
I would suggest having another if
branch checking to see if the CallExpression
being exited is a hook, and popping the hooksStack
at that point.
With the current code, you could write some tests involving both beforeEach
and afterEach
hooks in the same module. When you run those, you will probably see some weird behavior with regard to the lint message. You can then use those tests to be more confident that your state management is good. 😄
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 naming is the issue here - I pushed updates where I tried to clarify what I am storing in the stack and when
b231361
to
5e9cbde
Compare
@platinumazure thanks for the feedback, I took another pass. Let me know what you think |
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.
Nice improvements! Just a few things left.
Regarding duplicating tests for module
calls-- I think it's okay if you just have one or two tests that use module
, rather than duplicating all of them. Thanks!
}, | ||
fixable: null, | ||
messages: { | ||
"noHooksFromAncestorModules": "Do not use hooks from an ancestor module. You called {{usedHookIdentifierName}}.{{hookName}} inside module({{containingModuleDescription}})." |
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 love the greater level of detail here! ...Now, unfortunately, I need to ask if we can shorten the message. 😅
Let me know if you need help thinking of how you could shorten the message!
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 could drop the "inside module(...)" portion, or are you looking for even shorter?
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.
Even shorter might be better! If we want to mention the module's description, maybe we could do something like this?
"noHooksFromAncestorModules": "Do not use hooks from an ancestor module. You called {{usedHookIdentifierName}}.{{hookName}} inside module({{containingModuleDescription}})." | |
"noHooksFromAncestorModules": "Do not use `{{usedHookIdentifierName}}.{{hookName}}` from module({{containingModuleDescription}}) inside nested module." |
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've shortened the message even more - since you'll see where the warning is, there's probably no extra value to the description of the containing module.
Friendly ping @raycohen, are you still interested in getting this landed? No rush, just checking 😄 |
Yes, just got back from parental leave so I will try to get to this |
7ac9109
to
3139573
Compare
Made some updates. I wasn't sure which config this should go in. I went with 2️⃣ as my guess. |
// Helpers | ||
//------------------------------------------------------------------------------ | ||
|
||
function createError({ invokedMethodName, expectedHookIdentifierName, usedHookIdentifierName, containingModuleDescription }) { |
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.
You can also add some plain unit tests for this function at the bottom of this file.
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.
For what it's worth, I don't think it's necessary to test a one-statement helper function in the test when it's pretty clear what it should do.
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.
Oh true. My goal was to ensure the final error string looks right. But now I realized the string is not actually exposed 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'll clean up the things I ended up not using
- clean up unused strings
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 good so far. Thanks for helping to resolve the open questions!
Only real issue at this point is the inclusion of the rule in any configurations. We need to avoid modifying the shared configurations in semver-minor releases, which this PR would otherwise fall under. Please either remove the rule from the configurations, or let me know that you're okay with this being marked as a semver-major breaking PR. Thanks!
README.md
Outdated
@@ -47,6 +47,7 @@ Each rule has emojis denoting: | |||
| :two: | [no-global-expect](./docs/rules/no-global-expect.md) | disallow global expect| | |||
| :two: | [no-global-module-test](./docs/rules/no-global-module-test.md) | disallow global module/test/asyncTest| | |||
| :two: | [no-global-stop-start](./docs/rules/no-global-stop-start.md) | disallow global stop/start| | |||
| :two: | [no-hooks-from-ancestor-modules](./docs/rules/no-hooks-from-ancestor-modules.md) | disallow the use of hooks from ancestor modules| |
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.
As far as configs go, I think this makes sense to have in recommended and two (eventually-- see next paragraph).
We can't make changes to the exported configs until a major release per the semver policy. If you're okay with waiting until a major release for this rule to go in, that's fine, but I don't know when that will be.
I recommend not including this rule in a config in this PR; we can release this as a minor release, and then there should be an issue for v6 planning and we could make sure this gets added into recommended and two configs in the major release. Let me know what you think.
index.js
Outdated
@@ -43,6 +43,7 @@ module.exports = { | |||
"qunit/no-global-expect": "error", | |||
"qunit/no-global-module-test": "error", | |||
"qunit/no-global-stop-start": "error", | |||
"qunit/no-hooks-from-ancestor-modules": "error", |
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.
As noted in another comment, we can't make changes to the exported configs until a major release. Also, I think this makes sense to include in both "recommended" and "two" (in the next major release). But for this PR, please remove this line (unless you are okay with this going in the next major release).
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.
updated!
// Helpers | ||
//------------------------------------------------------------------------------ | ||
|
||
function createError({ invokedMethodName, expectedHookIdentifierName, usedHookIdentifierName, containingModuleDescription }) { |
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.
For what it's worth, I don't think it's necessary to test a one-statement helper function in the test when it's pretty clear what it should do.
3139573
to
c0f316d
Compare
c0f316d
to
b612727
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.
Just one possible issue in the documentation examples, otherwise this looks ready to go! Thanks for all the hard work on this!
Also, feel free to just push new commits on this branch-- makes it easier to review just the most recent changes. No need to squash and force push as I can do that on merge. Thanks! |
Co-authored-by: Kevin Partington <platinumazure@gmail.com>
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 awesome, thanks! I'll merge ASAP.
Thanks for your persistence on this one @raycohen! |
No description provided.