Skip to content

Commit 05fd3aa

Browse files
Merge pull request #656 from protofire/feat/improve-ordering
feat: improve ordering rule
2 parents 9a793e7 + 357a241 commit 05fd3aa

File tree

6 files changed

+371
-107
lines changed

6 files changed

+371
-107
lines changed

docs/rules.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ title: "Rule Index of Solhint"
4444
| [var-name-mixedcase](./rules/naming/var-name-mixedcase.md) | Variable names must be in mixedCase. (Does not check IMMUTABLES, use immutable-vars-naming) | $~~~~~~~~$✔️ | |
4545
| [imports-on-top](./rules/order/imports-on-top.md) | Import statements must be on top. | $~~~~~~~~$✔️ | |
4646
| [imports-order](./rules/naming/imports-order.md) | Order the imports of the contract to follow a certain hierarchy (read "Notes section") | | |
47-
| [ordering](./rules/order/ordering.md) | Check order of elements in file and inside each contract, according to the style guide. | | |
47+
| [ordering](./rules/order/ordering.md) | Check order of elements in file and inside each contract, according to the style guide | | |
4848
| [visibility-modifier-order](./rules/order/visibility-modifier-order.md) | Visibility modifier must be first in list of modifiers. | $~~~~~~~~$✔️ | |
4949
5050

docs/rules/order/ordering.md

Lines changed: 176 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ title: "ordering | Solhint"
99
![Default Severity Badge warn](https://img.shields.io/badge/Default%20Severity-warn-yellow)
1010

1111
## Description
12-
Check order of elements in file and inside each contract, according to the style guide.
12+
Check order of elements in file and inside each contract, according to the style guide
1313

1414
## Options
1515
This rule accepts a string option for rule severity. Must be one of "error", "warn", "off". Defaults to warn.
@@ -58,14 +58,14 @@ library MyLibrary {
5858
}
5959
6060
contract MyContract {
61-
struct InnerStruct {
62-
bool flag;
63-
}
64-
6561
enum InnerEnum {
6662
A, B, C
6763
}
6864
65+
struct InnerStruct {
66+
bool flag;
67+
}
68+
6969
uint public x;
7070
uint public y;
7171
@@ -120,14 +120,14 @@ library MyLibrary {
120120
contract MyContract {
121121
using MyLibrary for uint;
122122
123-
struct InnerStruct {
124-
bool flag;
125-
}
126-
127123
enum InnerEnum {
128124
A, B, C
129125
}
130126
127+
struct InnerStruct {
128+
bool flag;
129+
}
130+
131131
address payable owner;
132132
uint public x;
133133
uint public y;
@@ -198,14 +198,98 @@ library MyLibrary {
198198
contract MyContract {
199199
using MyLibrary for uint;
200200
201+
enum InnerEnum {
202+
A, B, C
203+
}
204+
201205
struct InnerStruct {
202206
bool flag;
203207
}
204208
209+
address payable owner;
210+
uint public x;
211+
uint public y;
212+
213+
event MyEvent(address a);
214+
215+
modifier onlyOwner {
216+
require(
217+
msg.sender == owner,
218+
"Only owner can call this function."
219+
);
220+
_;
221+
}
222+
223+
constructor () public {}
224+
225+
receive() external payable {}
226+
227+
fallback () external {}
228+
229+
function myExternalFunction() external {}
230+
function myExternalViewFunction() external view {}
231+
function myExternalPureFunction() external pure {}
232+
233+
function myPublicFunction() public {}
234+
function myPublicViewFunction() public view {}
235+
function myPublicPureFunction() public pure {}
236+
237+
function myInternalFunction() internal {}
238+
function myInternalViewFunction() internal view {}
239+
function myInternalPureFunction() internal pure {}
240+
241+
function myPrivateFunction() private {}
242+
function myPrivateViewFunction() private view {}
243+
function myPrivatePureFunction() private pure {}
244+
}
245+
246+
```
247+
248+
#### All units are in order - ^0.7.0
249+
250+
```solidity
251+
252+
pragma solidity ^0.7.0;
253+
254+
import "./some/library.sol";
255+
import "./some/other-library.sol";
256+
257+
uint constant FOO_BAR = 42;
258+
259+
enum MyEnum {
260+
Foo,
261+
Bar
262+
}
263+
264+
struct MyStruct {
265+
uint x;
266+
uint y;
267+
}
268+
269+
interface IBox {
270+
function getValue() public;
271+
function setValue(uint) public;
272+
}
273+
274+
library MyLibrary {
275+
function add(uint a, uint b, uint c) public returns (uint) {
276+
return a + b + c;
277+
}
278+
}
279+
280+
contract MyContract {
281+
using MyLibrary for uint;
282+
205283
enum InnerEnum {
206284
A, B, C
207285
}
208286
287+
struct InnerStruct {
288+
bool flag;
289+
}
290+
291+
uint constant v;
292+
uint immutable w;
209293
address payable owner;
210294
uint public x;
211295
uint public y;
@@ -339,6 +423,89 @@ contract MyContract {
339423
340424
```
341425

426+
#### file-level constant before import
427+
428+
```solidity
429+
430+
uint256 constant FOO_BAR = 42;
431+
import {A} from "./A.sol";
432+
433+
```
434+
435+
#### file-level constant after type definition
436+
437+
```solidity
438+
439+
struct foo { uint x; }
440+
uint256 constant FOO_BAR = 42;
441+
442+
```
443+
444+
#### immutable after mutable
445+
446+
```solidity
447+
448+
contract Foo {
449+
uint bar;
450+
uint immutable foo;
451+
}
452+
453+
```
454+
455+
#### constant after immutable
456+
457+
```solidity
458+
459+
contract Foo {
460+
uint immutable bar;
461+
uint constant foo;
462+
}
463+
464+
```
465+
466+
#### constant after mutable
467+
468+
```solidity
469+
470+
contract Foo {
471+
uint bar;
472+
uint constant foo;
473+
}
474+
475+
```
476+
477+
#### file-scoped struct before enum
478+
479+
```solidity
480+
481+
struct MyStruct {
482+
uint x;
483+
uint y;
484+
}
485+
enum MyEnum {
486+
Foo,
487+
Bar
488+
}
489+
490+
```
491+
492+
#### contract-scoped struct before enum
493+
494+
```solidity
495+
496+
contract MyContract {
497+
struct MyStruct {
498+
uint x;
499+
uint y;
500+
}
501+
enum MyEnum {
502+
Foo,
503+
Bar
504+
}
505+
}
506+
507+
```
508+
342509
## Version
343510
This rule was introduced in [Solhint 3.2.0](https://github.com/protofire/solhint/blob/v3.2.0)
344511

lib/rules/order/ordering.js

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const meta = {
66
type: 'order',
77

88
docs: {
9-
description: `Check order of elements in file and inside each contract, according to the style guide.`,
9+
description: `Check order of elements in file and inside each contract, according to the style guide`,
1010
category: 'Style Guide Rules',
1111
examples: {
1212
good: require('../../../test/fixtures/order/ordering-correct'),
@@ -75,10 +75,6 @@ function getMutabilityWeight({ baseWeight, stateMutability }) {
7575
}
7676
}
7777

78-
function isTypeDeclaration(node) {
79-
return ['StructDefinition', 'EnumDefinition'].includes(node.type)
80-
}
81-
8278
function sourceUnitPartOrder(node) {
8379
if (node.type === 'PragmaDirective') {
8480
return [0, 'pragma directive']
@@ -92,8 +88,12 @@ function sourceUnitPartOrder(node) {
9288
return [20, 'file level constant']
9389
}
9490

95-
if (node.type === 'EnumDefinition' || node.type === 'StructDefinition') {
96-
return [30, 'type definition']
91+
if (node.type === 'EnumDefinition') {
92+
return [30, 'enum definition']
93+
}
94+
95+
if (node.type === 'StructDefinition') {
96+
return [35, 'struct definition']
9797
}
9898

9999
if (node.type === 'CustomErrorDefinition') {
@@ -121,31 +121,39 @@ function sourceUnitPartOrder(node) {
121121
throw new Error('Unrecognized source unit part, please report this issue')
122122
}
123123

124-
function isInitializerModifier(modifiers, targetName, targetArguments) {
125-
// search the modifiers array with the name === 'initializer'
126-
return modifiers.some(
127-
(modifier) => modifier.name === targetName && modifier.arguments === targetArguments
128-
)
129-
}
130-
131124
function contractPartOrder(node) {
132125
if (node.type === 'UsingForDeclaration') {
133126
return [0, 'using for declaration']
134127
}
135128

136-
if (isTypeDeclaration(node)) {
137-
let label
138-
if (node.type === 'StructDefinition') {
139-
label = 'struct definition'
140-
} else {
141-
label = 'enum definition'
142-
}
129+
if (node.type === 'EnumDefinition') {
130+
return [10, 'enum definition']
131+
}
143132

144-
return [10, label]
133+
if (node.type === 'StructDefinition') {
134+
return [15, 'struct definition']
145135
}
146136

147137
if (node.type === 'StateVariableDeclaration') {
148-
return [20, 'state variable declaration']
138+
// the grammar: https://docs.soliditylang.org/en/latest/grammar.html
139+
// forbids declaration of multiple state variables in the same
140+
// StateVariableDeclaration, however in the AST they show up in an array,
141+
// similar to regular VariableDeclarationStatements, which allow
142+
// VariableDefinitionTuples inside them and therefore can declare many
143+
// variables at once
144+
if (node.variables.length !== 1) {
145+
throw new Error(
146+
'state variable definition with more than one variable. Please report this issue'
147+
)
148+
}
149+
const variable = node.variables[0]
150+
if (variable.isDeclaredConst) {
151+
return [20, 'contract constant declaration']
152+
} else if (variable.isImmutable) {
153+
return [22, 'contract immutable declaration']
154+
} else {
155+
return [25, 'state variable declaration']
156+
}
149157
}
150158

151159
if (node.type === 'EventDefinition') {
@@ -160,13 +168,8 @@ function contractPartOrder(node) {
160168
return [40, 'modifier definition']
161169
}
162170

163-
if (
164-
node.isConstructor ||
165-
(node.type === 'FunctionDefinition' &&
166-
node.name === 'initialize' &&
167-
isInitializerModifier(node.modifiers, 'initializer', null))
168-
) {
169-
return [50, 'constructor/initializer']
171+
if (node.isConstructor) {
172+
return [50, 'constructor']
170173
}
171174

172175
if (isReceiveFunction(node)) {

0 commit comments

Comments
 (0)