Skip to content

Commit cd981f6

Browse files
lhemerlyfrangioAmxx
authored
Add custom linting rules (#4132)
Co-authored-by: Francisco Giordano <[email protected]> Co-authored-by: Hadrien Croubois <[email protected]>
1 parent 2a4396c commit cd981f6

File tree

6 files changed

+127
-15
lines changed

6 files changed

+127
-15
lines changed

.solhint.json

Lines changed: 0 additions & 15 deletions
This file was deleted.

package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
"rimraf": "^3.0.2",
8585
"semver": "^7.3.5",
8686
"solhint": "^3.3.6",
87+
"solhint-plugin-openzeppelin": "file:scripts/solhint-custom",
8788
"solidity-ast": "^0.4.25",
8889
"solidity-coverage": "^0.8.0",
8990
"solidity-docgen": "^0.6.0-beta.29",

scripts/solhint-custom/index.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
const path = require('path');
2+
const minimatch = require('minimatch');
3+
4+
// Files matching these patterns will be ignored unless a rule has `static global = true`
5+
const ignore = ['contracts/mocks/**/*', 'test/**/*'];
6+
7+
class Base {
8+
constructor(reporter, config, source, fileName) {
9+
this.reporter = reporter;
10+
this.ignored = this.constructor.global || ignore.some(p => minimatch(path.normalize(fileName), p));
11+
this.ruleId = this.constructor.ruleId;
12+
if (this.ruleId === undefined) {
13+
throw Error('missing ruleId static property');
14+
}
15+
}
16+
17+
error(node, message) {
18+
if (!this.ignored) {
19+
this.reporter.error(node, this.ruleId, message);
20+
}
21+
}
22+
}
23+
24+
module.exports = [
25+
class extends Base {
26+
static ruleId = 'interface-names';
27+
28+
ContractDefinition(node) {
29+
if (node.kind === 'interface' && !/^I[A-Z]/.test(node.name)) {
30+
this.error(node, 'Interface names should have a capital I prefix');
31+
}
32+
}
33+
},
34+
35+
class extends Base {
36+
static ruleId = 'private-variables';
37+
38+
VariableDeclaration(node) {
39+
const constantOrImmutable = node.isDeclaredConst || node.isImmutable;
40+
if (node.isStateVar && !constantOrImmutable && node.visibility !== 'private') {
41+
this.error(node, 'State variables must be private');
42+
}
43+
}
44+
},
45+
46+
class extends Base {
47+
static ruleId = 'leading-underscore';
48+
49+
VariableDeclaration(node) {
50+
if (node.isDeclaredConst) {
51+
if (/^_/.test(node.name)) {
52+
// TODO: re-enable and fix
53+
// this.error(node, 'Constant variables should not have leading underscore');
54+
}
55+
} else if (node.visibility === 'private' && !/^_/.test(node.name)) {
56+
this.error(node, 'Non-constant private variables must have leading underscore');
57+
}
58+
}
59+
60+
FunctionDefinition(node) {
61+
if (node.visibility === 'private' || (node.visibility === 'internal' && node.parent.kind !== 'library')) {
62+
if (!/^_/.test(node.name)) {
63+
this.error(node, 'Private and internal functions must have leading underscore');
64+
}
65+
}
66+
if (node.visibility === 'internal' && node.parent.kind === 'library') {
67+
if (/^_/.test(node.name)) {
68+
this.error(node, 'Library internal functions should not have leading underscore');
69+
}
70+
}
71+
}
72+
},
73+
74+
// TODO: re-enable and fix
75+
// class extends Base {
76+
// static ruleId = 'no-external-virtual';
77+
//
78+
// FunctionDefinition(node) {
79+
// if (node.visibility == 'external' && node.isVirtual) {
80+
// this.error(node, 'Functions should not be external and virtual');
81+
// }
82+
// }
83+
// },
84+
];
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "solhint-plugin-openzeppelin",
3+
"private": true
4+
}

solhint.config.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const customRules = require('./scripts/solhint-custom');
2+
3+
const rules = [
4+
'no-unused-vars',
5+
'const-name-snakecase',
6+
'contract-name-camelcase',
7+
'event-name-camelcase',
8+
'func-name-mixedcase',
9+
'func-param-name-mixedcase',
10+
'modifier-name-mixedcase',
11+
'var-name-mixedcase',
12+
'imports-on-top',
13+
'no-global-import',
14+
...customRules.map(r => `openzeppelin/${r.ruleId}`),
15+
];
16+
17+
module.exports = {
18+
plugins: ['openzeppelin'],
19+
rules: Object.fromEntries(rules.map(r => [r, 'error'])),
20+
};

0 commit comments

Comments
 (0)