Skip to content

Commit 229db23

Browse files
Merge pull request #488 from protofire/develop
Merge Develop into Master v3.6.2 (a)
2 parents 48258ca + 400248d commit 229db23

File tree

20 files changed

+324
-37
lines changed

20 files changed

+324
-37
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
## [3.6.2] - 2023-08-17
2+
### Added
3+
- New Rule: `one-contract-per-file` - Enforces the use of ONE contract per file [#487](https://github.com/protofire/solhint/pull/487)
4+
5+
6+
### Fixed
7+
- `foundry-test-functions` - Modified regex to include invariant and statefulFuzz tests [#484](https://github.com/protofire/solhint/pull/484)
8+
- `quotes` - To allow quotes inside double quotes and viceversa [#485](https://github.com/protofire/solhint/pull/485)
9+
- `JSON` - Formatter returning JS object instead of standard json [#490](https://github.com/protofire/solhint/pull/490)
10+
11+
12+
113
## [3.6.1] - 2023-08-11
214

315
### BREAKING CHANGE

conf/rulesets/solhint-all.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module.exports = Object.freeze({
1616
'no-global-import': 'warn',
1717
'no-unused-import': 'warn',
1818
'no-unused-vars': 'warn',
19+
'one-contract-per-file': 'warn',
1920
'payable-fallback': 'warn',
2021
'reason-string': [
2122
'warn',

conf/rulesets/solhint-recommended.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module.exports = Object.freeze({
1313
'no-global-import': 'warn',
1414
'no-unused-import': 'warn',
1515
'no-unused-vars': 'warn',
16+
'one-contract-per-file': 'warn',
1617
'payable-fallback': 'warn',
1718
'reason-string': [
1819
'warn',

docs/rules.md

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,31 @@ title: "Rule Index of Solhint"
66

77
## Best Practise Rules
88

9-
| Rule Id | Error | Recommended | Deprecated |
10-
| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | ------------ | ---------- |
11-
| [code-complexity](./rules/best-practises/code-complexity.md) | Function has cyclomatic complexity "current" but allowed no more than maxcompl. | | |
12-
| [custom-errors](./rules/best-practises/custom-errors.md) | Enforces the use of Custom Errors over Require and Revert statements | $~~~~~~~~$✔️ | |
13-
| [explicit-types](./rules/best-practises/explicit-types.md) | Forbid or enforce explicit types (like uint256) that have an alias (like uint). | $~~~~~~~~$✔️ | |
14-
| [function-max-lines](./rules/best-practises/function-max-lines.md) | Function body contains "count" lines but allowed no more than maxlines. | | |
15-
| [max-line-length](./rules/best-practises/max-line-length.md) | Line length must be no more than maxlen. | | |
16-
| [max-states-count](./rules/best-practises/max-states-count.md) | Contract has "some count" states declarations but allowed no more than maxstates. | $~~~~~~~~$✔️ | |
17-
| [no-console](./rules/best-practises/no-console.md) | No console.log/logInt/logBytesX/logString/etc & No hardhat and forge-std console.sol import statements. | $~~~~~~~~$✔️ | |
18-
| [no-empty-blocks](./rules/best-practises/no-empty-blocks.md) | Code block has zero statements inside. Exceptions apply. | $~~~~~~~~$✔️ | |
19-
| [no-global-import](./rules/best-practises/no-global-import.md) | Import statement includes an entire file instead of selected symbols. | $~~~~~~~~$✔️ | |
20-
| [no-unused-import](./rules/best-practises/no-unused-import.md) | Imported object name is not being used by the contract. | $~~~~~~~~$✔️ | |
21-
| [no-unused-vars](./rules/best-practises/no-unused-vars.md) | Variable "name" is unused. | $~~~~~~~~$✔️ | |
22-
| [payable-fallback](./rules/best-practises/payable-fallback.md) | When fallback is not payable you will not be able to receive ethers. | $~~~~~~~~$✔️ | |
23-
| [reason-string](./rules/best-practises/reason-string.md) | Require or revert statement must have a reason string and check that each reason string is at most N characters long. | $~~~~~~~~$✔️ | |
24-
| [constructor-syntax](./rules/best-practises/constructor-syntax.md) | Constructors should use the new constructor keyword. | | |
9+
| Rule Id | Error | Recommended | Deprecated |
10+
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | ---------- |
11+
| [code-complexity](./rules/best-practises/code-complexity.md) | Function has cyclomatic complexity "current" but allowed no more than maxcompl. | | |
12+
| [custom-errors](./rules/best-practises/custom-errors.md) | Enforces the use of Custom Errors over Require and Revert statements | $~~~~~~~~$✔️ | |
13+
| [explicit-types](./rules/best-practises/explicit-types.md) | Forbid or enforce explicit types (like uint256) that have an alias (like uint). | $~~~~~~~~$✔️ | |
14+
| [function-max-lines](./rules/best-practises/function-max-lines.md) | Function body contains "count" lines but allowed no more than maxlines. | | |
15+
| [max-line-length](./rules/best-practises/max-line-length.md) | Line length must be no more than maxlen. | | |
16+
| [max-states-count](./rules/best-practises/max-states-count.md) | Contract has "some count" states declarations but allowed no more than maxstates. | $~~~~~~~~$✔️ | |
17+
| [no-console](./rules/best-practises/no-console.md) | No console.log/logInt/logBytesX/logString/etc & No hardhat and forge-std console.sol import statements. | $~~~~~~~~$✔️ | |
18+
| [no-empty-blocks](./rules/best-practises/no-empty-blocks.md) | Code block has zero statements inside. Exceptions apply. | $~~~~~~~~$✔️ | |
19+
| [no-global-import](./rules/best-practises/no-global-import.md) | Import statement includes an entire file instead of selected symbols. | $~~~~~~~~$✔️ | |
20+
| [no-unused-import](./rules/best-practises/no-unused-import.md) | Imported object name is not being used by the contract. | $~~~~~~~~$✔️ | |
21+
| [no-unused-vars](./rules/best-practises/no-unused-vars.md) | Variable "name" is unused. | $~~~~~~~~$✔️ | |
22+
| [one-contract-per-file](./rules/best-practises/one-contract-per-file.md) | Enforces the use of ONE Contract per file see [here](https://docs.soliditylang.org/en/v0.8.21/style-guide.html#contract-and-library-names) | $~~~~~~~~$✔️ | |
23+
| [payable-fallback](./rules/best-practises/payable-fallback.md) | When fallback is not payable you will not be able to receive ethers. | $~~~~~~~~$✔️ | |
24+
| [reason-string](./rules/best-practises/reason-string.md) | Require or revert statement must have a reason string and check that each reason string is at most N characters long. | $~~~~~~~~$✔️ | |
25+
| [constructor-syntax](./rules/best-practises/constructor-syntax.md) | Constructors should use the new constructor keyword. | | |
2526
2627

2728
## Miscellaneous
2829

2930
| Rule Id | Error | Recommended | Deprecated |
3031
| --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ---------- |
3132
| [comprehensive-interface](./rules/miscellaneous/comprehensive-interface.md) | Check that all public or external functions are override. This is iseful to make sure that the whole API is extracted in an interface. | | |
32-
| [quotes](./rules/miscellaneous/quotes.md) | Use double quotes for string literals. Values must be 'single' or 'double'. | $~~~~~~~~$✔️ | |
33+
| [quotes](./rules/miscellaneous/quotes.md) | Enforces the use of double or simple quotes as configured for string literals. Values must be 'single' or 'double'. | $~~~~~~~~$✔️ | |
3334
3435

3536
## Style Guide Rules

docs/rules/best-practises/custom-errors.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ revert("Insufficient Balance");
6363
```
6464

6565
## Version
66-
This rule is introduced in the latest version.
66+
This rule was introduced in [Solhint 3.6.1](https://github.com/protofire/solhint/tree/v3.6.1)
6767

6868
## Resources
6969
- [Rule source](https://github.com/protofire/solhint/tree/master/lib/rules/best-practises/custom-errors.js)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
warning: "This is a dynamically generated file. Do not edit manually."
3+
layout: "default"
4+
title: "one-contract-per-file | Solhint"
5+
---
6+
7+
# one-contract-per-file
8+
![Recommended Badge](https://img.shields.io/badge/-Recommended-brightgreen)
9+
![Category Badge](https://img.shields.io/badge/-Best%20Practise%20Rules-informational)
10+
![Default Severity Badge warn](https://img.shields.io/badge/Default%20Severity-warn-yellow)
11+
> The {"extends": "solhint:recommended"} property in a configuration file enables this rule.
12+
13+
14+
## Description
15+
Enforces the use of ONE Contract per file see [here](https://docs.soliditylang.org/en/v0.8.21/style-guide.html#contract-and-library-names)
16+
17+
## Options
18+
This rule accepts a string option of rule severity. Must be one of "error", "warn", "off". Default to warn.
19+
20+
### Example Config
21+
```json
22+
{
23+
"rules": {
24+
"one-contract-per-file": "warn"
25+
}
26+
}
27+
```
28+
29+
30+
## Examples
31+
This rule does not have examples.
32+
33+
## Version
34+
This rule is introduced in the latest version.
35+
36+
## Resources
37+
- [Rule source](https://github.com/protofire/solhint/tree/master/lib/rules/best-practises/one-contract-per-file.js)
38+
- [Document source](https://github.com/protofire/solhint/tree/master/docs/rules/best-practises/one-contract-per-file.md)
39+
- [Test cases](https://github.com/protofire/solhint/tree/master/test/rules/best-practises/one-contract-per-file.js)

docs/rules/miscellaneous/quotes.md

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ title: "quotes | Solhint"
1212
1313

1414
## Description
15-
Use double quotes for string literals. Values must be 'single' or 'double'.
15+
Enforces the use of double or simple quotes as configured for string literals. Values must be 'single' or 'double'.
1616

1717
## Options
1818
This rule accepts an array of options:
@@ -32,11 +32,13 @@ This rule accepts an array of options:
3232
}
3333
```
3434

35+
### Notes
36+
- This rule allows to put a double quote inside single quote string and viceversa
3537

3638
## Examples
3739
### 👍 Examples of **correct** code for this rule
3840

39-
#### String with double quotes
41+
#### Configured with double quotes
4042

4143
```solidity
4244
@@ -49,9 +51,47 @@ This rule accepts an array of options:
4951
5052
```
5153

54+
#### Configured with single quotes
55+
56+
```solidity
57+
58+
pragma solidity 0.4.4;
59+
60+
61+
contract A {
62+
string private a = 'test';
63+
}
64+
65+
```
66+
67+
#### Configured with double quotes
68+
69+
```solidity
70+
string private constant STR = "You shall 'pass' !";
71+
```
72+
73+
#### Configured with single quotes
74+
75+
```solidity
76+
string private constant STR = 'You shall "pass" !';
77+
```
78+
5279
### 👎 Examples of **incorrect** code for this rule
5380

54-
#### String with single quotes
81+
#### Configured with single quotes
82+
83+
```solidity
84+
85+
pragma solidity 0.4.4;
86+
87+
88+
contract A {
89+
string private a = "test";
90+
}
91+
92+
```
93+
94+
#### Configured with double quotes
5595

5696
```solidity
5797

docs/rules/naming/foundry-test-functions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ This rule accepts an array of options:
3333
- Supported Regex: ```test(Fork)?(Fuzz)?(Fail)?_(Revert(If_|When_){1})?\w{1,}```
3434
- This rule should be executed in a separate folder with a separate .solhint.json => ```solhint --config .solhint.json testFolder/**/*.sol```
3535
- This rule applies only to `external` and `public` functions
36-
- This rule skips the `setUp()` function
36+
- This rule skips the `setUp()` function by default
3737

3838
## Examples
3939
### 👍 Examples of **correct** code for this rule
@@ -65,7 +65,7 @@ function numberIs42() public {}
6565
```
6666

6767
## Version
68-
This rule is introduced in the latest version.
68+
This rule was introduced in [Solhint 3.6.1](https://github.com/protofire/solhint/tree/v3.6.1)
6969

7070
## Resources
7171
- [Rule source](https://github.com/protofire/solhint/tree/master/lib/rules/naming/foundry-test-functions.js)

docs/rules/naming/named-return-values.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function checkBalance(address wallet) external view returns(uint256) {}
4242
```
4343

4444
## Version
45-
This rule is introduced in the latest version.
45+
This rule was introduced in [Solhint 3.6.1](https://github.com/protofire/solhint/tree/v3.6.1)
4646

4747
## Resources
4848
- [Rule source](https://github.com/protofire/solhint/tree/master/lib/rules/naming/named-return-values.js)

lib/common/identifier-naming.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,18 @@ module.exports = {
3131
return text && text[0] === '_'
3232
},
3333

34-
isNotFoundryTestCase(text) {
35-
const regex = /^test(Fork)?(Fuzz)?(Fail)?_(Revert(If_|When_){1})?\w{1,}$/
36-
return !match(text, regex)
34+
isFoundryTestCase(text) {
35+
// this one checks CamelCase after test keyword
36+
// const regexTest = /^test(Fork)?(Fuzz)?(Fail)?(_)?[A-Z](Revert(If_|When_){1})?\w{1,}$/
37+
38+
const regexTest = /^test(Fork)?(Fuzz)?(Fail)?(_)?(Revert(If_|When_){1})?\w{1,}$/
39+
const matchRegexTest = match(text, regexTest)
40+
41+
// this one checks CamelCase after test keyword
42+
// const regexInvariant = /^(invariant|statefulFuzz)(_)?[A-Z]\w{1,}$/
43+
const regexInvariant = /^(invariant|statefulFuzz)(_)?\w{1,}$/
44+
const matchRegexInvariant = match(text, regexInvariant)
45+
46+
return matchRegexTest || matchRegexInvariant
3747
},
3848
}

lib/formatters/json.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,5 @@ module.exports = function (results) {
5858

5959
if (finalMessage) allMessages.push(finalMessage)
6060

61-
return allMessages.length > 0 ? JSON.parse(JSON.stringify(allMessages)) : ''
61+
return allMessages.length > 0 ? JSON.stringify(allMessages) : ''
6262
}

lib/rules/best-practises/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const NoGlobalImportsChecker = require('./no-global-import')
1111
const NoUnusedImportsChecker = require('./no-unused-import')
1212
const ExplicitTypesChecker = require('./explicit-types')
1313
const CustomErrorsChecker = require('./custom-errors')
14+
const OneContractPerFileChecker = require('./one-contract-per-file')
1415

1516
module.exports = function checkers(reporter, config, inputSrc, tokens) {
1617
return [
@@ -27,5 +28,6 @@ module.exports = function checkers(reporter, config, inputSrc, tokens) {
2728
new NoUnusedImportsChecker(reporter, tokens),
2829
new ExplicitTypesChecker(reporter, config),
2930
new CustomErrorsChecker(reporter),
31+
new OneContractPerFileChecker(reporter),
3032
]
3133
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
const BaseChecker = require('../base-checker')
2+
const { severityDescription } = require('../../doc/utils')
3+
4+
const DEFAULT_SEVERITY = 'warn'
5+
6+
const ruleId = 'one-contract-per-file'
7+
const meta = {
8+
type: 'best-practises',
9+
10+
docs: {
11+
description:
12+
'Enforces the use of ONE Contract per file see [here](https://docs.soliditylang.org/en/v0.8.21/style-guide.html#contract-and-library-names)',
13+
category: 'Best Practise Rules',
14+
options: [
15+
{
16+
description: severityDescription,
17+
default: DEFAULT_SEVERITY,
18+
},
19+
],
20+
},
21+
22+
isDefault: false,
23+
recommended: true,
24+
defaultSetup: DEFAULT_SEVERITY,
25+
26+
schema: null,
27+
}
28+
29+
class OneContractPerFileChecker extends BaseChecker {
30+
constructor(reporter) {
31+
super(reporter, ruleId, meta)
32+
}
33+
34+
SourceUnit(node) {
35+
const contractDefinitionCount = node.children.reduce((count, child) => {
36+
if (child.type === 'ContractDefinition') {
37+
return count + 1
38+
}
39+
return count
40+
}, 0)
41+
42+
if (contractDefinitionCount > 1) {
43+
this.error(
44+
node,
45+
`Found more than One contract per file. ${contractDefinitionCount} contracts found!`
46+
)
47+
}
48+
}
49+
}
50+
51+
module.exports = OneContractPerFileChecker

lib/rules/miscellaneous/quotes.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const meta = {
99
type: 'miscellaneous',
1010

1111
docs: {
12-
description: `Use double quotes for string literals. Values must be 'single' or 'double'.`,
12+
description: `Enforces the use of double or simple quotes as configured for string literals. Values must be 'single' or 'double'.`,
1313
category: 'Miscellaneous',
1414
options: [
1515
{
@@ -24,17 +24,38 @@ const meta = {
2424
examples: {
2525
good: [
2626
{
27-
description: 'String with double quotes',
27+
description: 'Configured with double quotes',
2828
code: require('../../../test/fixtures/miscellaneous/string-with-double-quotes'),
2929
},
30+
{
31+
description: 'Configured with single quotes',
32+
code: require('../../../test/fixtures/miscellaneous/string-with-single-quotes'),
33+
},
34+
{
35+
description: 'Configured with double quotes',
36+
code: 'string private constant STR = "You shall \'pass\' !";',
37+
},
38+
{
39+
description: 'Configured with single quotes',
40+
code: 'string private constant STR = \'You shall "pass" !\';',
41+
},
3042
],
3143
bad: [
3244
{
33-
description: 'String with single quotes',
45+
description: 'Configured with single quotes',
46+
code: require('../../../test/fixtures/miscellaneous/string-with-double-quotes'),
47+
},
48+
{
49+
description: 'Configured with double quotes',
3450
code: require('../../../test/fixtures/miscellaneous/string-with-single-quotes'),
3551
},
3652
],
3753
},
54+
notes: [
55+
{
56+
note: 'This rule allows to put a double quote inside single quote string and viceversa',
57+
},
58+
],
3859
},
3960

4061
isDefault: false,
@@ -64,7 +85,6 @@ class QuotesChecker extends BaseChecker {
6485
token.loc.start.line === node.loc.start.line &&
6586
token.loc.start.column === node.loc.start.column
6687
)
67-
6888
if (token && !this.alreadyVisited(token)) {
6989
this.addVisitedNode(token)
7090
this.validateQuotes(token)

0 commit comments

Comments
 (0)