Repository: protofire/solhint
Pull request #557 (2024-03-08 19:34:06)

New Rule: Interface starts with i
this rule was developed by [defi wonderland's](https://defi.sucks/) plugin for solhint



Patch:
diff --git a/conf/rulesets/solhint-all.js b/conf/rulesets/solhint-all.js
index 2082476..2344912 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 153509b..f1ed437 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 0000000..89a468b
--- /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 15059e8..c7d2f09 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 0000000..9c7fafe
--- /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


Test patch:
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 0000000..7f30d78
--- /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)
+  })
+})