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
support ignoreConstructors
option for no-empty-blocks
#418
support ignoreConstructors
option for no-empty-blocks
#418
Conversation
…commit (#93) * chore: remove global import warnings * chore: fix 'Explicit mark state visibility' warnings * chore: use capslock for constant names * chore: Use camelCase for Contract name * chore: use mixedCase for function name * chore: fix unused variables * chore: fix avoid low level calls * chore: fix solhint no-inline-assembly * chore: fix solhint no empty blocks. Could be better once protofire/solhint#418 gets merged * chore: fix solhint max-states-count * chore: fix compilation warnings * chore: add forge build to pre-commit * chore: add --deny-warnings to CI * fix: remove --deny-warnings from husky --------- Co-authored-by: Orland0x <37511817+Orland0x@users.noreply.github.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.
I like this implementation ✨
However there's a thing I'd like to discuss:
the following is valid with the ignoreConstructors
option
constuctor() {}
however the code doesn't do anything useful, and IMO it'd be good practice to
get rid of it.
the case in which this rule (without the option herein defined) is annoying is
where you have a constructor with a member initialization list:
constructor(uint a) Base(a) {}
since the linter complains about the empty code block but you can't get rid of
the constructor definition, since it's necessary to call the base constructor.
Therefore I propose to change the default behaviour of the rule so it always
ignores constructors with both a member initialization list and an empty body
and also always raises an error on an empty constructor without said
initialization
quick patch on top of 584479629
to show what I mean:
diff --git a/lib/rules/best-practises/no-empty-blocks.js b/lib/rules/best-practises/no-empty-blocks.js
index ba14fa5..1cd88dc 100644
--- a/lib/rules/best-practises/no-empty-blocks.js
+++ b/lib/rules/best-practises/no-empty-blocks.js
@@ -54,0 +55 @@ class NoEmptyBlocksChecker extends BaseChecker {
+ node.body.modifiers = node.modifiers
@@ -58 +59 @@ class NoEmptyBlocksChecker extends BaseChecker {
- if (node.isConstructor && this.ignoreConstructors) {
+ if (node.isConstructor && node.modifiers.length > 0) {
diff --git a/test/rules/best-practises/no-empty-blocks.js b/test/rules/best-practises/no-empty-blocks.js
index 83ae325..5727461 100644
--- a/test/rules/best-practises/no-empty-blocks.js
+++ b/test/rules/best-practises/no-empty-blocks.js
@@ -29,0 +30 @@ describe('Linter - no-empty-blocks', () => {
+ contractWith('constructor (uint256 a) Base(a) { }'),
@@ -117,10 +117,0 @@ describe('Linter - no-empty-blocks', () => {
- it('should not raise error for constructor when ignoreConstructors is set to true', () => {
- const code = contractWith(`constructor () {}`)
-
- const report = linter.processStr(code, {
- rules: { 'no-empty-blocks': ['warn', { ignoreConstructors: true }] },
- })
-
- assertNoWarnings(report)
- })
-
what do y'all think?
@juanpcapurro Thank you for your comments. I agree your comments because I mostly use empty constructor only for inherited contract.
In this case, javascript allow us to skip writing But, your comments can work for some, so how about adding an optional setting as belows. Firstly, it will keep the current behavior as below.
Additinally, it allows to configure more detail as below.
How do you think about the above optional setting? |
I'm not sure I follow you. I just did a small test with the default contract for a
Could you provide an example of a contract that doesn't compile because of not having a constructor (including solidity version)? or do you mean something else? |
@juanpcapurro oh, thank you, good to me. And I will reflect your feedback. |
b117715
to
9e95930
Compare
9e95930
to
c81ffe6
Compare
@juanpcapurro I've pushed the commit reflecting your feedback. |
@dbale-altoros Could you enable CI for this PR? |
@juanpcapurro done |
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.
the blocking issue currently is wether to modify the AST or not, and the redundant testcases are secondary
@@ -113,6 +115,26 @@ describe('Linter - no-empty-blocks', () => { | |||
assertNoWarnings(report) | |||
}) | |||
|
|||
it('should raise error for empty block of [constructor]', () => { |
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.
isn't this whole hunk redundant with the two above?
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 are right. They were redundant so I've removed them.
assertWarnsCount(report, 1) | ||
}) | ||
|
||
it('should not raise error for empty block of inherited [constructor] having modifiers', () => { |
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 do the brackets mean?
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.
Basically, it was removed. But I denote the syntax keywards by brackets as the way others do
@@ -27,7 +27,15 @@ class NoEmptyBlocksChecker extends BaseChecker { | |||
this._validateContractPartsCount(node) | |||
} | |||
|
|||
FunctionDefinition(node) { | |||
node.body.shouldIgnore = node.isConstructor && node.modifiers.length > 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.
Keep in mind the AST is shared for all visitors, so this property will exist in the Blocks that are bodies of a function in all rule definitions that are executed after this one.
Therefore, an effort should be made not to pollute the AST with information not globally relevant.
My personally preferred approach would be to add the isConstructor
and modifiers
fields of the FunctionDefinition
node to its body
, and then assert isConstructors && modifiers.length > 0
in the listener for Block
nodes.
Having those two names in the Block
node makes it more clear what they are if they show up in the execution of another rule.
However, I'm not sure that it's a good practise to modify the AST that's gonna be used by other rules, and I'd like the opinion of @fvictorio on this topic. (I grepped a bit and couldn't find many usages of the node
variable as an lvalue)
If it's problematic to modify the AST in this way, then you'd have to store the information of what blocks to ignore in the ComplexityScore
class. One such way could be to have an object whose keys are a string that looks like ${range[0]-range[1]}
, and it's value is wether to ignore the block or not.
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.
The way this is done in other rules is by adding a field to the checker instance. So:
this.shouldIgnore = ...
Agree that modifying the node is not a 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.
@fvictorio @juanpcapurro I agree what you've mentioned. Howeve whether to be constructor could be validated in FunctionDefinition
. Therefore, I needed to recognize if its block is passed by constructor or not. Do you have any other good idea to pass it to the block.
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 about using node.parent
in the Block
callback to check if the block corresponds to a constructor, and then checking if there are modifiers?
Something similar to that is done in a following line to check if the block corresponds to a fallback 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.
@fvictorio Thank for good to me. I've change it to use node.parent and checked it working properly.
@hackartists thanks for this contribution!! |
A new example
You can set
ignoreConstructors
simlar withfunc-visibility
rule.Related Issue
#242