diff --git a/conf/rulesets/solhint-all.js b/conf/rulesets/solhint-all.js index 20824769..2344912c 100644 --- a/conf/rulesets/solhint-all.js +++ b/conf/rulesets/solhint-all.js @@ -8,6 +8,7 @@ module.exports = Object.freeze({ 'code-complexity': ['warn', 7], 'explicit-types': ['warn', 'explicit'], 'function-max-lines': ['warn', 50], + 'interface-starts-with-i': 'warning', 'max-line-length': ['error', 120], 'max-states-count': ['warn', 15], 'no-console': 'error', diff --git a/docs/rules.md b/docs/rules.md index 153509b9..f1ed4375 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -24,6 +24,30 @@ title: "Rule Index of Solhint" | [constructor-syntax](./rules/best-practises/constructor-syntax.md) | Constructors should use the new constructor keyword. | | | +## Style Guide Rules + +| Rule Id | Error | Recommended | Deprecated | +| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | +| [interface-starts-with-i](./rules/naming/interface-starts-with-i.md) | Solidity Interfaces names should start with an `I` | | | +| [const-name-snakecase](./rules/naming/const-name-snakecase.md) | Constant name must be in capitalized SNAKE_CASE. (Does not check IMMUTABLES, use immutable-vars-naming) | $~~~~~~~~$✔️ | | +| [contract-name-camelcase](./rules/naming/contract-name-camelcase.md) | Contract, Structs and Enums should be in CamelCase. | $~~~~~~~~$✔️ | | +| [event-name-camelcase](./rules/naming/event-name-camelcase.md) | Event name must be in CamelCase. | $~~~~~~~~$✔️ | | +| [foundry-test-functions](./rules/naming/foundry-test-functions.md) | Enforce naming convention on functions for Foundry test cases | | | +| [func-name-mixedcase](./rules/naming/func-name-mixedcase.md) | Function name must be in mixedCase. | $~~~~~~~~$✔️ | | +| [func-named-parameters](./rules/naming/func-named-parameters.md) | Enforce named parameters for function calls with 4 or more arguments. This rule may have some false positives | | | +| [func-param-name-mixedcase](./rules/naming/func-param-name-mixedcase.md) | Function param name must be in mixedCase. | | | +| [immutable-vars-naming](./rules/naming/immutable-vars-naming.md) | Check Immutable variables. Capitalized SNAKE_CASE or mixedCase depending on configuration. | $~~~~~~~~$✔️ | | +| [modifier-name-mixedcase](./rules/naming/modifier-name-mixedcase.md) | Modifier name must be in mixedCase. | | | +| [named-parameters-mapping](./rules/naming/named-parameters-mapping.md) | Solidity v0.8.18 introduced named parameters on the mappings definition. | | | +| [private-vars-leading-underscore](./rules/naming/private-vars-leading-underscore.md) | Non-external functions and state variables should start with a single underscore. Others, shouldn't | | | +| [use-forbidden-name](./rules/naming/use-forbidden-name.md) | Avoid to use letters 'I', 'l', 'O' as identifiers. | $~~~~~~~~$✔️ | | +| [var-name-mixedcase](./rules/naming/var-name-mixedcase.md) | Variable name must be in mixedCase. (Does not check IMMUTABLES, use immutable-vars-naming) | $~~~~~~~~$✔️ | | +| [func-order](./rules/order/func-order.md) | Function order is incorrect. | | $~~~~~~~$✔️ | +| [imports-on-top](./rules/order/imports-on-top.md) | Import statements must be on top. | $~~~~~~~~$✔️ | | +| [ordering](./rules/order/ordering.md) | Check order of elements in file and inside each contract, according to the style guide. | | | +| [visibility-modifier-order](./rules/order/visibility-modifier-order.md) | Visibility modifier must be first in list of modifiers. | $~~~~~~~~$✔️ | | + + ## Gas Consumption Rules | Rule Id | Error | Recommended | Deprecated | @@ -48,29 +72,6 @@ title: "Rule Index of Solhint" | [quotes](./rules/miscellaneous/quotes.md) | Enforces the use of double or simple quotes as configured for string literals. Values must be 'single' or 'double'. | $~~~~~~~~$✔️ | | -## Style Guide Rules - -| Rule Id | Error | Recommended | Deprecated | -| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | -| [const-name-snakecase](./rules/naming/const-name-snakecase.md) | Constant name must be in capitalized SNAKE_CASE. (Does not check IMMUTABLES, use immutable-vars-naming) | $~~~~~~~~$✔️ | | -| [contract-name-camelcase](./rules/naming/contract-name-camelcase.md) | Contract, Structs and Enums should be in CamelCase. | $~~~~~~~~$✔️ | | -| [event-name-camelcase](./rules/naming/event-name-camelcase.md) | Event name must be in CamelCase. | $~~~~~~~~$✔️ | | -| [foundry-test-functions](./rules/naming/foundry-test-functions.md) | Enforce naming convention on functions for Foundry test cases | | | -| [func-name-mixedcase](./rules/naming/func-name-mixedcase.md) | Function name must be in mixedCase. | $~~~~~~~~$✔️ | | -| [func-named-parameters](./rules/naming/func-named-parameters.md) | Enforce named parameters for function calls with 4 or more arguments. This rule may have some false positives | | | -| [func-param-name-mixedcase](./rules/naming/func-param-name-mixedcase.md) | Function param name must be in mixedCase. | | | -| [immutable-vars-naming](./rules/naming/immutable-vars-naming.md) | Check Immutable variables. Capitalized SNAKE_CASE or mixedCase depending on configuration. | $~~~~~~~~$✔️ | | -| [modifier-name-mixedcase](./rules/naming/modifier-name-mixedcase.md) | Modifier name must be in mixedCase. | | | -| [named-parameters-mapping](./rules/naming/named-parameters-mapping.md) | Solidity v0.8.18 introduced named parameters on the mappings definition. | | | -| [private-vars-leading-underscore](./rules/naming/private-vars-leading-underscore.md) | Non-external functions and state variables should start with a single underscore. Others, shouldn't | | | -| [use-forbidden-name](./rules/naming/use-forbidden-name.md) | Avoid to use letters 'I', 'l', 'O' as identifiers. | $~~~~~~~~$✔️ | | -| [var-name-mixedcase](./rules/naming/var-name-mixedcase.md) | Variable name must be in mixedCase. (Does not check IMMUTABLES, use immutable-vars-naming) | $~~~~~~~~$✔️ | | -| [func-order](./rules/order/func-order.md) | Function order is incorrect. | | $~~~~~~~$✔️ | -| [imports-on-top](./rules/order/imports-on-top.md) | Import statements must be on top. | $~~~~~~~~$✔️ | | -| [ordering](./rules/order/ordering.md) | Check order of elements in file and inside each contract, according to the style guide. | | | -| [visibility-modifier-order](./rules/order/visibility-modifier-order.md) | Visibility modifier must be first in list of modifiers. | $~~~~~~~~$✔️ | | - - ## Security Rules | Rule Id | Error | Recommended | Deprecated | diff --git a/docs/rules/naming/interface-starts-with-i.md b/docs/rules/naming/interface-starts-with-i.md new file mode 100644 index 00000000..89a468bc --- /dev/null +++ b/docs/rules/naming/interface-starts-with-i.md @@ -0,0 +1,50 @@ +--- +warning: "This is a dynamically generated file. Do not edit manually." +layout: "default" +title: "interface-starts-with-i | Solhint" +--- + +# interface-starts-with-i +![Category Badge](https://img.shields.io/badge/-Style%20Guide%20Rules-informational) +![Default Severity Badge warning](https://img.shields.io/badge/Default%20Severity-warning-undefined) + +## Description +Solidity Interfaces names should start with an `I` + +## Options +This rule accepts a string option of rule severity. Must be one of "error", "warn", "off". Default to warning. + +### Example Config +```json +{ + "rules": { + "interface-starts-with-i": "warning" + } +} +``` + + +## Examples +### 👍 Examples of **correct** code for this rule + +#### Interface name starts with I + +```solidity +interface IFoo { function foo () external; } +``` + +### 👎 Examples of **incorrect** code for this rule + +#### Interface name doesnt start with I + +```solidity +interface Foo { function foo () external; } +``` + +## Version +This rule is introduced in the latest version. + +## Resources +- [Rule source](https://github.com/protofire/solhint/tree/master/lib/rules/best-practises/interface-starts-with-i.js) +- [Document source](https://github.com/protofire/solhint/tree/master/docs/rules/best-practises/interface-starts-with-i.md) +- [Test cases](https://github.com/protofire/solhint/tree/master/test/rules/best-practises/interface-starts-with-i.js) diff --git a/lib/rules/best-practises/index.js b/lib/rules/best-practises/index.js index 15059e83..c7d2f090 100644 --- a/lib/rules/best-practises/index.js +++ b/lib/rules/best-practises/index.js @@ -11,6 +11,7 @@ const NoGlobalImportsChecker = require('./no-global-import') const NoUnusedImportsChecker = require('./no-unused-import') const ExplicitTypesChecker = require('./explicit-types') const OneContractPerFileChecker = require('./one-contract-per-file') +const InterfaceStartsWithIChecker = require('./interface-starts-with-i') module.exports = function checkers(reporter, config, inputSrc, tokens) { return [ @@ -27,5 +28,6 @@ module.exports = function checkers(reporter, config, inputSrc, tokens) { new NoUnusedImportsChecker(reporter, tokens), new ExplicitTypesChecker(reporter, config), new OneContractPerFileChecker(reporter), + new InterfaceStartsWithIChecker(reporter), ] } diff --git a/lib/rules/best-practises/interface-starts-with-i.js b/lib/rules/best-practises/interface-starts-with-i.js new file mode 100644 index 00000000..9c7fafe3 --- /dev/null +++ b/lib/rules/best-practises/interface-starts-with-i.js @@ -0,0 +1,45 @@ +const BaseChecker = require('../base-checker') + +const ruleId = 'interface-starts-with-i' +const meta = { + type: 'naming', + docs: { + description: 'Solidity Interfaces names should start with an `I`', + category: 'Style Guide Rules', + examples: { + good: [ + { + description: 'Interface name starts with I', + code: `interface IFoo { function foo () external; }`, + }, + ], + bad: [ + { + description: 'Interface name doesnt start with I', + code: `interface Foo { function foo () external; }`, + }, + ], + }, + }, + isDefault: false, + recommended: false, + defaultSetup: 'warning', + schema: [], +} + +class InterfaceStartsWithIChecker extends BaseChecker { + constructor(reporter) { + super(reporter, ruleId, meta) + } + + ContractDefinition(node) { + if (node.kind !== 'interface') return + const interfaceName = node.name + + if (!interfaceName.startsWith('I')) { + this.error(node, `Interface name '${interfaceName}' must start with "I"`) + } + } +} + +module.exports = InterfaceStartsWithIChecker diff --git a/test/rules/best-practises/interface-starts-with-i.js b/test/rules/best-practises/interface-starts-with-i.js new file mode 100644 index 00000000..7f30d789 --- /dev/null +++ b/test/rules/best-practises/interface-starts-with-i.js @@ -0,0 +1,23 @@ +const assert = require('assert') +const { processStr } = require('../../../lib/index') + +const config = { + rules: { 'interface-starts-with-i': 'error' }, +} + +describe('Linter - interface-starts-with-i', () => { + it('should raise error for interface not starting with I', () => { + const code = 'interface Foo {}' + const report = processStr(code, config) + + assert.equal(report.errorCount, 1) + assert.ok(report.messages[0].message === `Interface name 'Foo' must start with "I"`) + }) + + it('should not raise error for interface starting with I', () => { + const code = 'interface IFoo {}' + + const report = processStr(code, config) + assert.equal(report.errorCount, 0) + }) +})