solhint
Version:
Solidity Code Linter
331 lines (301 loc) • 10.2 kB
JavaScript
const linter = require('../../../lib/index')
const {
assertNoErrors,
assertErrorMessage,
assertErrorCount,
assertWarnsCount,
} = require('../../common/asserts')
describe('Linter - no-unused-import', () => {
it('should raise when imported name is not used', () => {
const code = `import {A} from './A.sol';`
const report = linter.processStr(code, {
rules: { 'no-unused-import': 'error' },
})
assertErrorCount(report, 1)
assertErrorMessage(report, 'imported name A is not used')
})
it('should raise when name created in import "path" as name is not used', () => {
const code = `import './A.sol' as A;`
const report = linter.processStr(code, {
rules: { 'no-unused-import': 'error' },
})
assertErrorCount(report, 1)
assertErrorMessage(report, 'imported name A is not used')
})
it('should not crash on, but also not recognize, malformed inheritdoc statements', () => {
const code = `
import {A} from './A.sol';
// inheritdoc A
// @inheritdoc A
/// inheritdoc A
/// @ inherit A
/// @ inheritdoc A
/// @inheritdoc somethingelse
/// @inheritdoc
/// @inheritdoc
`
const report = linter.processStr(code, {
rules: { 'no-unused-import': 'error' },
})
assertErrorCount(report, 1)
assertErrorMessage(report, 'imported name A is not used')
})
it('should raise error when using solhint:recommended', () => {
const code = `pragma solidity ^0.8.24; import {A} from "./A.sol";`
const report = linter.processStr(code, {
extends: 'solhint:recommended',
rules: { 'import-path-check': 'off' },
})
assertWarnsCount(report, 1)
assertErrorMessage(report, 'imported name A is not used')
})
it('should report correct name when unused import is aliased', () => {
const code = `import {A as B} from './A.sol';`
const report = linter.processStr(code, {
rules: { 'no-unused-import': 'error' },
})
assertErrorCount(report, 1)
assertErrorMessage(report, 'imported name B is not used')
})
it('should raise when some of the imported names are not used', () => {
const code = `import {A, B} from './A.sol'; contract C is A {}`
const report = linter.processStr(code, {
rules: { 'no-unused-import': 'error' },
})
assertErrorCount(report, 1)
assertErrorMessage(report, 'imported name B is not used')
})
it('should not raise when contract name is used as a type for a memory variable', () => {
const code = `
import {ERC20} from './ERC20.sol';
contract A {
function fun () public {
ERC20 funToken = address(0);
}
}`
const report = linter.processStr(code, {
rules: { 'no-unused-import': 'error' },
})
assertNoErrors(report)
})
;[
{
description: 'a field of an imported name is used',
code: `import {Vm} from './Vm.sol';
contract A {
function fun () public {
Vm.cheat('code');
}
}`,
},
{
description: 'imported name is used in new statement',
code: `import {OtherContract} from './Contract.sol';
contract Factory {
function deploy () public returns (address){
return new OtherContract();
}
}`,
},
{
description: 'imported name is used in array definition',
code: `import {B} from './Contract.sol';
contract A {
function fun () public {
B[] memory someArray;
}
}`,
},
{
description: 'member of imported name is used in array definition',
code: `import {B} from './Contract.sol';
contract A {
function fun () public {
B.field[] memory someArray;
}
}`,
},
{
description: 'imported name is used in type cast',
code: `import {ImportedType} from './Contract.sol';
contract A {
function fun () public {
ImportedType(address(0));
}
}`,
},
{
description: 'imported name is used as a custom error',
code: `import {SpecialError} from './Contract.sol';
contract A {
function fun () public {
revert SpecialError();
}
}`,
},
{
description: 'contract name is used for inheritance',
code: `import {A} from './A.sol'; contract B is A {}`,
},
{
description: 'aliased contract name is used',
code: `import {A as B} from './A.sol'; contract C is B {}`,
},
{
description: 'library name is used in a using ... for statement',
code: `import {A} from './A.sol'; contract B { using A for uint256; }`,
},
{
description: 'contract name is used in a state variable declaration',
code: ` import {A} from './A.sol'; contract B { A public statevar; }`,
},
{
description: 'imported subtype is used in a state variable declaration',
code: `import {A} from './A.sol'; contract B { A.thing public statevar; }`,
},
{
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 {
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;
}
}
`,
},
{
description: 'Import is used in the left side of a using for statement',
code: `
pragma solidity >=0.8.19;
import {EnumerableSetUD60x18, EnumerableSet} from "./libraries/EnumerableSetUD60x18.sol";
contract SolhintTest {
using EnumerableSetUD60x18 for EnumerableSet.Bytes32Set;
}
`,
},
{
description: 'Import is used as the type value of a mapping',
code: `
pragma solidity >=0.8.19;
import {UD60x18} from "@prb/math/UD60x18.sol";
contract SolhintTest {
mapping(address user => UD60x18 amount) internal balance;
}
`,
},
{
description: 'Import is used as the type value of a nested mapping',
code: `
pragma solidity >=0.8.19;
import {SD59x18} from "@prb/math/SD59x18.sol";
contract SolhintTest {
mapping(address user => mapping(address relayer => SD59x18 amount)) internal balance;
}
`,
},
{
description: 'Import is used in /// @inheritdoc',
code: `
import { IPRBProxyPlugin } from "@prb/proxy/interfaces/IPRBProxyPlugin.sol";
contract SablierV2ProxyPlugin {
/// @inheritdoc IPRBProxyPlugin
function getMethods() external pure returns (bytes4[] memory methods) {
methods = new bytes4[](1);
methods[0] = this.onStreamCanceled.selector;
}
}
`,
},
{
description: 'Import is used in /** @inheritdoc',
code: `
import { IPRBProxyPlugin } from "@prb/proxy/interfaces/IPRBProxyPlugin.sol";
contract SablierV2ProxyPlugin {
/** @inheritdoc IPRBProxyPlugin */
function getMethods() external pure returns (bytes4[] memory methods) {
methods = new bytes4[](1);
methods[0] = this.onStreamCanceled.selector;
}
}
`,
},
].forEach(({ description, code }) => {
it(`should not raise when ${description}`, () => {
const report = linter.processStr(code, {
rules: { 'no-unused-import': 'error' },
})
assertNoErrors(report)
})
})
})