Repository: protofire/solhint
Pull request #469 (2023-08-04 16:56:11)

Pre Release last changes


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d63bff5..acdfb9b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-## [3.4.2] - 2023-08-04
+## [3.5] - 2023-08-04
 ### Updated
 - Support `ignoreConstructors` option for `no-empty-blocks` [#418](https://github.com/protofire/solhint/pull/418)
 - Bump json5 from 2.1.3 to 2.2.3 [#376](https://github.com/protofire/solhint/pull/376)
@@ -9,10 +9,16 @@
 - Information about maxCharacters allowed on `reason-string` rule [#446](https://github.com/protofire/solhint/pull/446)
 - E2E tests for `max-warnings` [#455](https://github.com/protofire/solhint/pull/455)
 - Replaced blacklist and whitelist words [#459](https://github.com/protofire/solhint/pull/459)
+- Removed runtime dependencies on load-rules [#462](https://github.com/protofire/solhint/pull/462)
+- Allowed $ symbol as part of naming [#465](https://github.com/protofire/solhint/issues/465)
+- Disabled `no-empty-blocks` rule for receive() function [#466](https://github.com/protofire/solhint/pull/466)
+
+  
 ### Added
 - New Rule: No unused imports [#417](https://github.com/protofire/solhint/pull/417)
 - New Rule: To treat immutable as constants [#458](https://github.com/protofire/solhint/pull/458)
 - New Rule: Explicit-types. To forbid/enforce full type or alias for variables declaration [#467](https://github.com/protofire/solhint/pull/467)
+- New Rule: Naming of Function parameters. Enforce arguments naming [#468](https://github.com/protofire/solhint/pull/468)
 - JSON formatter support [#440](https://github.com/protofire/solhint/pull/440)
 - Rules List with `list-rules` command [#449](https://github.com/protofire/solhint/pull/449)
 - E2E tests for formatters and new `Compact formatter` [#457](https://github.com/protofire/solhint/pull/457)
@@ -23,6 +29,7 @@
 - Formatter option fixed for `stdin` command [#450](https://github.com/protofire/solhint/pull/450)
 
 
+
 <br><br>
 ## [3.4.1] - 2023-03-06
 ### Updated
diff --git a/docs/rules.md b/docs/rules.md
index 1a7d2c2..24d799a 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -16,7 +16,7 @@ title:       "Rule Index of Solhint"
 | [no-console](./rules/best-practises/no-console.md)                 | No console.log/logInt/logBytesX/logString/etc & No hardhat and forge-std console.sol import statements.               | $~~~~~~~~$✔️ |            |
 | [no-empty-blocks](./rules/best-practises/no-empty-blocks.md)       | Code block has zero statements inside. Exceptions apply.                                                              | $~~~~~~~~$✔️ |            |
 | [no-global-import](./rules/best-practises/no-global-import.md)     | Import statement includes an entire file instead of selected symbols.                                                 | $~~~~~~~~$✔️ |            |
-| [no-unused-import](./rules/best-practises/no-unused-import.md)     | Imported name is not used.                                                                                            | $~~~~~~~~$✔️ |            |
+| [no-unused-import](./rules/best-practises/no-unused-import.md)     | Imported object name is not being used by the contract.                                                               | $~~~~~~~~$✔️ |            |
 | [no-unused-vars](./rules/best-practises/no-unused-vars.md)         | Variable "name" is unused.                                                                                            | $~~~~~~~~$✔️ |            |
 | [payable-fallback](./rules/best-practises/payable-fallback.md)     | When fallback is not payable you will not be able to receive ethers.                                                  | $~~~~~~~~$✔️ |            |
 | [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. | $~~~~~~~~$✔️ |            |
diff --git a/docs/rules/best-practises/no-unused-import.md b/docs/rules/best-practises/no-unused-import.md
index 5643b9f..8d13e89 100644
--- a/docs/rules/best-practises/no-unused-import.md
+++ b/docs/rules/best-practises/no-unused-import.md
@@ -12,7 +12,7 @@ title:       "no-unused-import | Solhint"
 
 
 ## Description
-Imported name is not used.
+Imported object name is not being used by the contract.
 
 ## Options
 This rule accepts a string option of rule severity. Must be one of "error", "warn", "off". Default to warn.
@@ -28,7 +28,27 @@ This rule accepts a string option of rule severity. Must be one of "error", "war
 
 
 ## Examples
-This rule does not have examples.
+### 👍 Examples of **correct** code for this rule
+
+#### Imported object is being used
+
+```solidity
+
+            import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+            contract MyToken is ERC20 {}
+          
+```
+
+### 👎 Examples of **incorrect** code for this rule
+
+#### Imported object is not being used
+
+```solidity
+
+          import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+            contract B {}
+          
+```
 
 ## Version
 This rule is introduced in the latest version.
diff --git a/lib/rules/best-practises/no-unused-import.js b/lib/rules/best-practises/no-unused-import.js
index 78ba6f7..993ae14 100644
--- a/lib/rules/best-practises/no-unused-import.js
+++ b/lib/rules/best-practises/no-unused-import.js
@@ -6,8 +6,28 @@ const meta = {
   type: 'best-practises',
 
   docs: {
-    description: 'Imported name is not used.',
+    description: 'Imported object name is not being used by the contract.',
     category: 'Best Practise Rules',
+    examples: {
+      good: [
+        {
+          description: 'Imported object is being used',
+          code: `
+            import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+            contract MyToken is ERC20 {}
+          `,
+        },
+      ],
+      bad: [
+        {
+          description: 'Imported object is not being used',
+          code: `
+          import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+            contract B {}
+          `,
+        },
+      ],
+    },
   },
 
   isDefault: false,
@@ -17,6 +37,11 @@ const meta = {
   schema: null,
 }
 
+// meant to skip Identifiers that are part of the ImportDirective that defines a name
+function isImportIdentifier(node) {
+  return !node.parent || node.parent.type === 'ImportDirective'
+}
+
 class NoUnusedImportsChecker extends BaseChecker {
   constructor(reporter) {
     super(reporter, ruleId, meta)
@@ -35,10 +60,6 @@ class NoUnusedImportsChecker extends BaseChecker {
     }
   }
 
-  MemberAccess(node) {
-    this.registerUsage(node.expression.name)
-  }
-
   ArrayTypeName(node) {
     this.registerUsage(node.baseTypeName.namePath)
   }
@@ -47,8 +68,19 @@ class NoUnusedImportsChecker extends BaseChecker {
     this.registerUsage(node.typeName.namePath)
   }
 
-  FunctionCall(node) {
-    this.registerUsage(node.expression.name)
+  Identifier(node) {
+    if (!isImportIdentifier(node)) {
+      this.registerUsage(node.name)
+    }
+  }
+
+  FunctionDefinition(node) {
+    if (node.override) {
+      node.override.forEach((it) => this.registerUsage(it.namePath))
+    }
+    if (node.isConstructor) {
+      node.modifiers.forEach((it) => this.registerUsage(it.name))
+    }
   }
 
   ImportDirective(node) {
@@ -77,6 +109,7 @@ class NoUnusedImportsChecker extends BaseChecker {
 
   UsingForDeclaration(node) {
     this.registerUsage(node.libraryName)
+    node.functions.forEach((it) => this.registerUsage(it))
   }
 
   'SourceUnit:exit'() {
diff --git a/package-lock.json b/package-lock.json
index 6673ae2..2a94ced 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5155,9 +5155,9 @@
       }
     },
     "node_modules/word-wrap": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
-      "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
       "dev": true,
       "engines": {
         "node": ">=0.10.0"
@@ -9059,9 +9059,9 @@
       }
     },
     "word-wrap": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
-      "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
       "dev": true
     },
     "workerpool": {
diff --git a/package.json b/package.json
index 24ac1cf..378baaf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "solhint",
-  "version": "3.4.2",
+  "version": "3.5",
   "description": "Solidity Code Linter",
   "main": "lib/index.js",
   "keywords": [


Test patch:
diff --git a/test/rules/best-practises/no-unused-import.js b/test/rules/best-practises/no-unused-import.js
index ba5c535..49a2c49 100644
--- a/test/rules/best-practises/no-unused-import.js
+++ b/test/rules/best-practises/no-unused-import.js
@@ -150,6 +150,84 @@ describe('Linter - no-unused-import', () => {
       description: 'imported type is used in a function parameter declaration',
       code: `import {A} from './A.sol'; contract B { function (A.thing statevar) public {} }`,
     },
+    {
+      description: 'imported function is attached to a type',
+      code: `import {add} from './A.sol'; type Int is int; using {add} for Int global;`,
+    },
+    {
+      description: 'imported function is used as a user-defined operator',
+      code: `import {add} from './A.sol'; type Int is int; using {add as +} for Int global;`,
+    },
+    {
+      description: 'field of imported name is attached to a type',
+      code: `
+          import {Int, Math} from "./A.sol"; using {Math.add} for Int;
+          contract C {
+              Int public foo;
+          }
+      `,
+    },
+    {
+      description: 'Name is used in an override',
+      code: `
+        pragma solidity >=0.8.19;
+        import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+        import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
+        contract MyContract {
+            /// @inheritdoc ERC721
+            function tokenURI(uint256 streamId) public view override(IERC721Metadata, ERC721) returns (string memory uri) {
+                uri = "example.com";
+            }
+        }
+      `,
+    },
+    {
+      description: 'Type is used in a constructor initializator',
+      code: `
+        pragma solidity >=0.8.19;
+        import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+        contract MyContract {
+            constructor() ERC721("Sablier V2 Lockup Dynamic NFT", "SAB-V2-LOCKUP-DYN") { }
+        }
+      `,
+    },
+    {
+      description: 'Import is used as value in a function parameter',
+      code: `
+        pragma solidity >=0.8.19 <0.9.0;
+        import { UD60x18, ZERO } from "@prb/math/UD60x18.sol";
+        contract SetProtocolFee_Integration_Fuzz_Test {
+            function testFuzz_SetProtocolFee(UD60x18 newProtocolFee) external {
+                newProtocolFee = _bound(newProtocolFee, 1, MAX_FEE);
+                vm.expectEmit({ emitter: address(comptroller) });
+                emit SetProtocolFee({ admin: users.admin, asset: dai, oldProtocolFee: ZERO, newProtocolFee: newProtocolFee });
+            }
+        }
+      `,
+    },
+    {
+      description: 'Import is used as value in a binary expression',
+      code: `
+        import { ZERO } from "@prb/math/UD60x18.sol";
+        contract Foo {
+            function returnFifteen() public returns (uint){
+              return ZERO + 15;
+            }
+        }
+      `,
+    },
+    {
+      description: 'Import is used as value in an assignment',
+      code: `
+        import { ZERO } from "@prb/math/UD60x18.sol";
+        contract Foo {
+            uint256 public howMuch;
+            constructor () {
+              howMuch = ZERO;
+            }
+        }
+      `,
+    },
   ].forEach(({ description, code }) => {
     it(`should not raise when ${description}`, () => {
       const report = linter.processStr(code, {