From 5840e730f6c9ca0dda02b09805dc1f976c571725 Mon Sep 17 00:00:00 2001 From: dbale-altoros Date: Thu, 16 Jan 2025 17:14:05 -0300 Subject: [PATCH 1/4] feature: duplicated import rule --- conf/rulesets/solhint-all.js | 1 + docs/rules.md | 1 + .../rules/miscellaneous/duplicated-imports.md | 41 +++ lib/rules/miscellaneous/duplicated-imports.js | 251 ++++++++++++++++ lib/rules/miscellaneous/index.js | 2 + solhint.js | 24 +- .../miscellaneous/duplicated-imports-data.js | 275 ++++++++++++++++++ .../rules/miscellaneous/duplicated-imports.js | 35 +++ 8 files changed, 629 insertions(+), 1 deletion(-) create mode 100644 docs/rules/miscellaneous/duplicated-imports.md create mode 100644 lib/rules/miscellaneous/duplicated-imports.js create mode 100644 test/fixtures/miscellaneous/duplicated-imports-data.js create mode 100644 test/rules/miscellaneous/duplicated-imports.js diff --git a/conf/rulesets/solhint-all.js b/conf/rulesets/solhint-all.js index 730cdd17..3b964d3f 100644 --- a/conf/rulesets/solhint-all.js +++ b/conf/rulesets/solhint-all.js @@ -36,6 +36,7 @@ module.exports = Object.freeze({ 'gas-strict-inequalities': 'warn', 'gas-struct-packing': 'warn', 'comprehensive-interface': 'warn', + 'duplicated-imports': 'warn', quotes: ['error', 'double'], 'const-name-snakecase': 'warn', 'contract-name-capwords': 'warn', diff --git a/docs/rules.md b/docs/rules.md index 1ebe8de5..603dba4c 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -28,6 +28,7 @@ title: "Rule Index of Solhint" | Rule Id | Error | Recommended | Deprecated | | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | | [interface-starts-with-i](./rules/naming/interface-starts-with-i.md) | Solidity Interfaces names should start with an `I` | | | +| [duplicated-imports](./rules/miscellaneous/duplicated-imports.md) | Check if an import is done twice in the same file and there is no alias | | | | [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-capwords](./rules/naming/contract-name-capwords.md) | Contract, Structs and Enums should be in CapWords. | $~~~~~~~~$✔️ | | | [event-name-capwords](./rules/naming/event-name-capwords.md) | Event name must be in CapWords. | $~~~~~~~~$✔️ | | diff --git a/docs/rules/miscellaneous/duplicated-imports.md b/docs/rules/miscellaneous/duplicated-imports.md new file mode 100644 index 00000000..6088c1a6 --- /dev/null +++ b/docs/rules/miscellaneous/duplicated-imports.md @@ -0,0 +1,41 @@ +--- +warning: "This is a dynamically generated file. Do not edit manually." +layout: "default" +title: "duplicated-imports | Solhint" +--- + +# duplicated-imports +![Category Badge](https://img.shields.io/badge/-Style%20Guide%20Rules-informational) +![Default Severity Badge warn](https://img.shields.io/badge/Default%20Severity-warn-yellow) + +## Description +Check if an import is done twice in the same file and there is no alias + +## Options +This rule accepts a string option of rule severity. Must be one of "error", "warn", "off". Defaults to warn. + +### Example Config +```json +{ + "rules": { + "duplicated-imports": "warn" + } +} +``` + +### Notes +- Rule reports "(inline) duplicated" if the same object is imported more than once in the same import statement +- Rule reports "(globalSamePath) duplicated" if the same object is imported on another import statement from same location +- Rule reports "(globalDiffPath) duplicated" if the same object is imported on another import statement, from other location, but no alias +- Rule does NOT support this kind of import "import * as Alias from "./filename.sol" + +## Examples +This rule does not have examples. + +## Version +This rule is introduced in the latest version. + +## Resources +- [Rule source](https://github.com/protofire/solhint/blob/master/lib/rules/miscellaneous/duplicated-imports.js) +- [Document source](https://github.com/protofire/solhint/blob/master/docs/rules/miscellaneous/duplicated-imports.md) +- [Test cases](https://github.com/protofire/solhint/blob/master/test/rules/miscellaneous/duplicated-imports.js) diff --git a/lib/rules/miscellaneous/duplicated-imports.js b/lib/rules/miscellaneous/duplicated-imports.js new file mode 100644 index 00000000..567f9d9c --- /dev/null +++ b/lib/rules/miscellaneous/duplicated-imports.js @@ -0,0 +1,251 @@ +const path = require('path') +const BaseChecker = require('../base-checker') +const { severityDescription } = require('../../doc/utils') + +const DEFAULT_SEVERITY = 'warn' + +const ruleId = 'duplicated-imports' +const meta = { + type: 'miscellaneous', + + docs: { + description: `Check if an import is done twice in the same file and there is no alias`, + category: 'Style Guide Rules', + options: [ + { + description: severityDescription, + default: DEFAULT_SEVERITY, + }, + ], + notes: [ + { + note: 'Rule reports "(inline) duplicated" if the same object is imported more than once in the same import statement', + }, + { + note: 'Rule reports "(globalSamePath) duplicated" if the same object is imported on another import statement from same location', + }, + { + note: 'Rule reports "(globalDiffPath) duplicated" if the same object is imported on another import statement, from other location, but no alias', + }, + { + note: 'Rule does NOT support this kind of import "import * as Alias from "./filename.sol"', + }, + ], + }, + + isDefault: false, + recommended: false, + defaultSetup: 'warn', + fixable: true, + schema: null, +} + +class DuplicatedImportsChecker extends BaseChecker { + constructor(reporter) { + super(reporter, ruleId, meta) + + this.imports = [] + } + + ImportDirective(node) { + const normalizedPath = this.normalizePath(node.path) + + const importStatement = { + path: '', + objectNames: [], + } + + importStatement.path = normalizedPath + importStatement.objectNames = node.symbolAliases + ? node.symbolAliases + : this.getObjectName(normalizedPath) + + this.imports.push(importStatement) + } + + 'SourceUnit:exit'(node) { + const duplicates = this.findDuplicates(this.imports) + + for (let i = 0; i < duplicates.length; i++) { + this.error(node, `Duplicated Import (${duplicates[i].type}) ${duplicates[i].name}`) + } + } + + getObjectName(normalizedPath) { + // get file name + const fileNameWithExtension = path.basename(normalizedPath) + // Remove extension + const objectName = fileNameWithExtension.replace('.sol', '') + return [[objectName, null]] + } + + normalizePath(path) { + if (path.startsWith('../')) { + return `./${path}` + } + return path + } + + findInlineDuplicates(data) { + const inlineDuplicates = [] + + data.forEach((entry) => { + const path = entry.path + // To track object names + const objectNamesSet = new Set() + + entry.objectNames.forEach(([objectName]) => { + // If object name already been found , it is a duplicated + if (objectNamesSet.has(objectName)) { + inlineDuplicates.push({ + name: objectName, + type: 'inline', + paths: [path], + }) + } else { + // If it is not found before, we add it + objectNamesSet.add(objectName) + } + }) + }) + + return inlineDuplicates + } + + finGlobalDuplicatesSamePath(data) { + const duplicates = [] + + // Loop through data + data.forEach((entry) => { + const path = entry.path + + // Object to track object names on each path + const objectNamesMap = {} + + // Loop through each objectName of current object + entry.objectNames.forEach(([objectName]) => { + if (!objectNamesMap[objectName]) { + objectNamesMap[objectName] = [] + } + objectNamesMap[objectName].push(path) + }) + + // Compare this object with the rest to detect duplicates + data.forEach((otherEntry) => { + if (otherEntry !== entry) { + otherEntry.objectNames.forEach(([objectName]) => { + if ( + objectNamesMap[objectName] && + objectNamesMap[objectName].includes(otherEntry.path) + ) { + // Add path only if it is not present + const existingDuplicate = duplicates.find( + (duplicate) => + duplicate.name === objectName && + duplicate.type === 'global' && + duplicate.paths.includes(entry.path) + ) + + if (!existingDuplicate) { + duplicates.push({ + name: objectName, + type: 'globalSamePath', + paths: [entry.path], // Just add path once, it is always the same + }) + } + } + }) + } + }) + }) + + return duplicates + } + + finGlobalDuplicatesDiffPathNoAlias(data) { + const duplicates = [] + + // Loop through data + data.forEach((entry) => { + // Object to track names on each path + entry.objectNames.forEach(([objectName, alias]) => { + // Only compare if there is no alias + if (!alias) { + // Go through rest of objects to search for duplicates + data.forEach((otherEntry) => { + if (otherEntry !== entry) { + otherEntry.objectNames.forEach(([otherObjectName, otherAlias]) => { + // If object name is the same, has no alias and different path + if ( + objectName === otherObjectName && + !otherAlias && + entry.path !== otherEntry.path + ) { + // Check if the name is already in the duplicated array + const existingDuplicate = duplicates.find( + (duplicate) => + duplicate.name === objectName && + duplicate.type === 'global' && + duplicate.paths.includes(entry.path) + ) + + // Add new object if doesn't exist + if (!existingDuplicate) { + duplicates.push({ + name: objectName, + type: 'globalDiffPath', + paths: [entry.path, otherEntry.path], + }) + } + + // Add path if already exists + if (existingDuplicate && !existingDuplicate.paths.includes(otherEntry.path)) { + existingDuplicate.paths.push(otherEntry.path) + } + } + }) + } + }) + } + }) + }) + + return duplicates + } + + removeDuplicatedObjects(data) { + const uniqueData = data.filter((value, index, self) => { + // Order path arrays to be compared later + const sortedPaths = value.paths.slice().sort() + + return ( + index === + self.findIndex( + (t) => + t.name === value.name && + t.type === value.type && + // Compare ordered arrays of paths + JSON.stringify(t.paths.slice().sort()) === JSON.stringify(sortedPaths) + ) + ) + }) + + return uniqueData + } + + findDuplicates(data) { + /// @TODO THIS LOGIC CAN BE IMPROVED - Not done due lack of time + + const duplicates1 = this.findInlineDuplicates(data) + + const duplicates2 = this.finGlobalDuplicatesSamePath(data) + + const duplicates3 = this.finGlobalDuplicatesDiffPathNoAlias(data) + + const duplicates = this.removeDuplicatedObjects(duplicates1.concat(duplicates2, duplicates3)) + + return duplicates + } +} + +module.exports = DuplicatedImportsChecker diff --git a/lib/rules/miscellaneous/index.js b/lib/rules/miscellaneous/index.js index 680f6913..0edeb854 100644 --- a/lib/rules/miscellaneous/index.js +++ b/lib/rules/miscellaneous/index.js @@ -1,9 +1,11 @@ const QuotesChecker = require('./quotes') const ComprehensiveInterfaceChecker = require('./comprehensive-interface') +const DuplicatedImportsChecker = require('./duplicated-imports') module.exports = function checkers(reporter, config, tokens) { return [ new QuotesChecker(reporter, config, tokens), new ComprehensiveInterfaceChecker(reporter, config, tokens), + new DuplicatedImportsChecker(reporter), ] } diff --git a/solhint.js b/solhint.js index 6c172666..37a6508c 100755 --- a/solhint.js +++ b/solhint.js @@ -4,6 +4,7 @@ const _ = require('lodash') const fs = require('fs') const process = require('process') const readline = require('readline') +const chalk = require('chalk') const linter = require('./lib/index') const { loadConfig } = require('./lib/config/config-file') @@ -306,7 +307,28 @@ function printReports(reports, formatter) { } const fullReport = formatter(reports) + (finalMessage || '') - if (!program.opts().quiet) console.log(fullReport) + + if (!program.opts().quiet) { + console.log(fullReport) + + console.log( + chalk.italic.bgYellow.black.bold( + ' -------------------------------------------------------------------------- ' + ) + ) + + console.log( + chalk.italic.bgYellow.black.bold( + ' ===> Join SOLHINT Community at: https://discord.com/invite/4TYGq3zpjs <=== ' + ) + ) + + console.log( + chalk.italic.bgYellow.black.bold( + ' -------------------------------------------------------------------------- \n' + ) + ) + } if (program.opts().save) { writeStringToFile(fullReport) diff --git a/test/fixtures/miscellaneous/duplicated-imports-data.js b/test/fixtures/miscellaneous/duplicated-imports-data.js new file mode 100644 index 00000000..605d438b --- /dev/null +++ b/test/fixtures/miscellaneous/duplicated-imports-data.js @@ -0,0 +1,275 @@ +const { multiLine } = require('../../common/contract-builder') + +const noDuplicates = [ + { + name: 'No Duplicates1', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {Afool} from './Afool.sol';", + "import {Afool2} from './Afool2.sol';", + 'contract Test { }' + ), + }, + { + name: 'No Duplicates2', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {Afool as Bfool} from './Afool.sol';", + 'contract Test { }' + ), + }, + { + name: 'No Duplicates4', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {Afool, Cfool} from './Afool.sol';", + "import {Cfool as Dfool} from './Cfool.sol';", + 'contract Test { }' + ), + }, + { + name: 'No Duplicates6', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import './SimpleLibrary.sol';", + "import {SimpleLibrary as lib} from './SimpleLibrary2.sol';", + 'contract Test { }' + ), + }, + { + name: 'No Duplicates7', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {SimpleLibrary} from './SimpleLibrary.sol';", + 'contract Test { }' + ), + }, + { + name: 'No Duplicates8', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {LibraryA} from './LibraryA.sol';", + "import {LibraryB} from './LibraryB.sol';", + 'contract Test { }' + ), + }, +] + +const duplicates = [ + { + name: 'Duplicates1', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {Afool, Afool as Bfool} from './Afool.sol';", + 'contract Test { }' + ), + qtyDuplicates: 1, + message: ['(inline) Afool'], // Inline duplication + }, + { + name: 'Duplicates2', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {Afool} from './Afool.sol';", + "import {Afool} from './Afool.sol';", + 'contract Test { }' + ), + qtyDuplicates: 1, + message: ['(globalSamePath) Afool'], // Global duplication within the same path + }, + { + name: 'Duplicates3', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {Afool} from './Afool.sol';", + "import {Afool as FoolAlias} from './Afool.sol';", + 'contract Test { }' + ), + qtyDuplicates: 1, + message: ['(globalSamePath) Afool'], // Global duplication with alias + }, + { + name: 'Duplicates4', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import './SimpleLibrary.sol';", + "import {SimpleLibrary} from './folder/SimpleLibrary.sol';", + 'contract Test { }' + ), + qtyDuplicates: 1, + message: ['(globalDiffPath) SimpleLibrary'], // Import with and without name specification + }, + { + name: 'Duplicates5', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {IXTokenFactory} from '../../token/interfaces/IXTokenFactory.sol';", + "import {IXTokenFactory} from '../../token/interfaces/IXTokenFactory2.sol';", + 'contract Test { }' + ), + qtyDuplicates: 1, + message: ['(globalDiffPath) IXTokenFactory'], // Global duplication + }, + { + name: 'Duplicates6', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {IXTokenFactory} from '../../token/interfaces/IXTokenFactory.sol';", + "import {IXTokenFactory, IXTokenFactory as AliasFactory} from '../../token/interfaces/IXTokenFactory.sol';", + 'contract Test { }' + ), + qtyDuplicates: 2, + message: ['(inline) IXTokenFactory', '(globalSamePath) IXTokenFactory'], // Mixed inline and global duplication + }, + { + name: 'Duplicates7', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {SharedLib} from './LibraryA.sol';", + "import {SharedLib} from './LibraryB.sol';", + 'contract Test { }' + ), + qtyDuplicates: 1, + message: ['(globalDiffPath) SharedLib'], // Same object name from different libraries + }, + { + name: 'Duplicates8', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {Token, Token as Tkn} from './LibraryA.sol';", + "import {Token} from './LibraryB.sol';", + 'contract Test { }' + ), + qtyDuplicates: 2, + message: ['(inline) Token', '(globalDiffPath) Token'], // Mixed inline and global duplication + }, + { + name: 'Duplicates9', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import './LibraryA.sol';", + "import {LibraryA} from './LibraryB.sol';", + 'contract Test { }' + ), + qtyDuplicates: 1, + message: ['(globalDiffPath) LibraryA'], // Import with and without name specification different libraries + }, + { + name: 'Duplicates10', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import './LibraryA.sol';", + "import {LibraryA} from './LibraryA.sol';", + 'contract Test { }' + ), + qtyDuplicates: 1, + message: ['(globalSamePath) LibraryA'], // Import with and without name specification same libraries + }, + { + name: 'Duplicates11', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {SharedLib} from '../LibraryA.sol';", + "import {SharedLib} from './nested/LibraryA.sol';", + 'contract Test { }' + ), + qtyDuplicates: 1, + message: ['(globalDiffPath) SharedLib'], // Same object imported from different nested paths + }, + { + name: 'Duplicates12', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import '@openzeppelin/contracts/token/ERC20/ERC20.sol';", + "import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol';", + 'contract Test { }' + ), + qtyDuplicates: 1, + message: ['(globalSamePath) ERC20'], // Standard library duplicate + }, + { + name: 'Duplicates13', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import '../token/interfaces/IXTokenWrapper.sol';", + "import {IXTokenWrapper} from '../token/interfaces/IXTokenWrapper.sol';", + 'contract Test { }' + ), + qtyDuplicates: 1, + message: ['(globalSamePath) IXTokenWrapper'], // Import with and without name specification + }, + { + name: 'Duplicates14', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {ReentrancyGuardUpgradeable} from '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol';", + "import {ReentrancyGuardUpgradeable as Rguard} from '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol';", + 'contract Test { }' + ), + qtyDuplicates: 1, + message: ['(globalSamePath) ReentrancyGuardUpgradeable'], // Reentrancy guard duplication with alias + }, + { + name: 'Duplicates15', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import './LibraryA.sol';", + "import './LibraryA.sol';", + 'contract Test { }' + ), + qtyDuplicates: 1, + message: ['(globalSamePath) LibraryA'], // Same path imported multiple times + }, + { + name: 'Duplicates16', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {Afool, Afool as Fool1, Afool as Fool2, Afool as Fool3} from './Afool.sol';", + "import {Afool} from './Afool.sol';", + 'contract Test { }' + ), + qtyDuplicates: 2, + message: ['(inline) Afool', '(globalSamePath) Afool'], // Global duplication within the same path + }, + { + name: 'Duplicates17', + code: multiLine( + '// SPDX-License-Identifier: Apache-2.0', + 'pragma solidity ^0.8.0;', + "import {IXTokenFactory} from '../../token/interfaces/IXTokenFactory2.sol';", + "import {IXTokenFactory, IXTokenFactory as AliasFactory} from '../../token/interfaces/IXTokenFactory.sol';", + "import '../../token/interfaces/IXTokenFactory.sol';", + 'contract Test { }' + ), + qtyDuplicates: 3, + message: [ + '(inline) IXTokenFactory', + '(globalSamePath) IXTokenFactory', + '(globalDiffPath) IXTokenFactory', + ], // Mixed inline and global duplication + }, +] + +module.exports = { noDuplicates, duplicates } diff --git a/test/rules/miscellaneous/duplicated-imports.js b/test/rules/miscellaneous/duplicated-imports.js new file mode 100644 index 00000000..ca8c9c93 --- /dev/null +++ b/test/rules/miscellaneous/duplicated-imports.js @@ -0,0 +1,35 @@ +const assert = require('assert') +const linter = require('../../../lib/index') +const { assertNoErrors, assertErrorCount } = require('../../common/asserts') +const TEST_CASES = require('../../fixtures/miscellaneous/duplicated-imports-data') + +describe('Linter - duplicated-imports', () => { + for (const importCase of TEST_CASES.duplicates) { + it(`Should report ${importCase.qtyDuplicates} error/s for ${importCase.name}`, () => { + const code = importCase.code + + const report = linter.processStr(code, { + rules: { 'duplicated-imports': 'error' }, + }) + + // assert.equal(report.errorCount, ) + assertErrorCount(report, importCase.qtyDuplicates) + + for (let i = 0; i < report.reports.length; i++) { + assert.ok(report.reports[i].message.includes(importCase.message[i])) + } + }) + } + + for (const importCase of TEST_CASES.noDuplicates) { + it(`Should NOT report errors for ${importCase.name}`, () => { + const code = importCase.code + + const report = linter.processStr(code, { + rules: { 'duplicated-imports': 'error' }, + }) + + assertNoErrors(report) + }) + } +}) From 53ca00f4e9c11cb2ccc57b29cbd19b9bbf3ca882 Mon Sep 17 00:00:00 2001 From: dbale-altoros Date: Thu, 16 Jan 2025 17:21:00 -0300 Subject: [PATCH 2/4] feature: duplicated import rule --- solhint.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/solhint.js b/solhint.js index 37a6508c..b34609f7 100755 --- a/solhint.js +++ b/solhint.js @@ -310,24 +310,25 @@ function printReports(reports, formatter) { if (!program.opts().quiet) { console.log(fullReport) - - console.log( - chalk.italic.bgYellow.black.bold( - ' -------------------------------------------------------------------------- ' + if (fullReport) { + console.log( + chalk.italic.bgYellow.black.bold( + ' -------------------------------------------------------------------------- ' + ) ) - ) - console.log( - chalk.italic.bgYellow.black.bold( - ' ===> Join SOLHINT Community at: https://discord.com/invite/4TYGq3zpjs <=== ' + console.log( + chalk.italic.bgYellow.black.bold( + ' ===> Join SOLHINT Community at: https://discord.com/invite/4TYGq3zpjs <=== ' + ) ) - ) - console.log( - chalk.italic.bgYellow.black.bold( - ' -------------------------------------------------------------------------- \n' + console.log( + chalk.italic.bgYellow.black.bold( + ' -------------------------------------------------------------------------- \n' + ) ) - ) + } } if (program.opts().save) { From 0e9c062371b414f10def320338aa241669e777bc Mon Sep 17 00:00:00 2001 From: dbale-altoros Date: Thu, 16 Jan 2025 18:02:22 -0300 Subject: [PATCH 3/4] feature: duplicated import rule --- solhint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solhint.js b/solhint.js index b34609f7..01587e17 100755 --- a/solhint.js +++ b/solhint.js @@ -310,7 +310,7 @@ function printReports(reports, formatter) { if (!program.opts().quiet) { console.log(fullReport) - if (fullReport) { + if (fullReport && !program.opts().formatter) { console.log( chalk.italic.bgYellow.black.bold( ' -------------------------------------------------------------------------- ' From fa1d56c481bc28224696df9aaaf32b2e4213711e Mon Sep 17 00:00:00 2001 From: dbale-altoros Date: Thu, 16 Jan 2025 18:06:04 -0300 Subject: [PATCH 4/4] feature: duplicated import rule --- e2e/autofix-test.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/e2e/autofix-test.js b/e2e/autofix-test.js index f57ae859..2c800223 100644 --- a/e2e/autofix-test.js +++ b/e2e/autofix-test.js @@ -145,7 +145,7 @@ describe('e2e', function () { const reportLines = stdout.split('\n') const finalLine = '5 problems (5 errors, 0 warnings)' - expect(reportLines[reportLines.length - 3]).to.contain(finalLine) + expect(reportLines[reportLines.length - 7]).to.contain(finalLine) result = compareTextFiles(currentFile, afterFixFile) expect(result).to.be.true @@ -194,7 +194,7 @@ describe('e2e', function () { it('should get the right report (2)', () => { const reportLines = stdout.split('\n') const finalLine = '27 problems (27 errors, 0 warnings)' - expect(reportLines[reportLines.length - 3]).to.contain(finalLine) + expect(reportLines[reportLines.length - 7]).to.contain(finalLine) }) }) @@ -240,7 +240,7 @@ describe('e2e', function () { it('should get the right report (3)', () => { const reportLines = stdout.split('\n') const finalLine = '9 problems (9 errors, 0 warnings)' - expect(reportLines[reportLines.length - 3]).to.contain(finalLine) + expect(reportLines[reportLines.length - 7]).to.contain(finalLine) }) }) @@ -286,7 +286,7 @@ describe('e2e', function () { it('should get the right report (4)', () => { const reportLines = stdout.split('\n') const finalLine = '19 problems (19 errors, 0 warnings)' - expect(reportLines[reportLines.length - 3]).to.contain(finalLine) + expect(reportLines[reportLines.length - 7]).to.contain(finalLine) }) }) @@ -332,7 +332,7 @@ describe('e2e', function () { it('should get the right report (5)', () => { const reportLines = stdout.split('\n') const finalLine = '11 problems (11 errors, 0 warnings)' - expect(reportLines[reportLines.length - 3]).to.contain(finalLine) + expect(reportLines[reportLines.length - 7]).to.contain(finalLine) }) }) @@ -379,7 +379,7 @@ describe('e2e', function () { it('should get the right report (6)', () => { const reportLines = stdout.split('\n') const finalLine = '8 problems (8 errors, 0 warnings)' - expect(reportLines[reportLines.length - 3]).to.contain(finalLine) + expect(reportLines[reportLines.length - 7]).to.contain(finalLine) }) }) @@ -419,7 +419,7 @@ describe('e2e', function () { it('should get the right report (6)', () => { const reportLines = stdout.split('\n') const finalLine = '8 problems (8 errors, 0 warnings)' - expect(reportLines[reportLines.length - 3]).to.contain(finalLine) + expect(reportLines[reportLines.length - 7]).to.contain(finalLine) }) }) @@ -466,7 +466,7 @@ describe('e2e', function () { it('should get the right report (7)', () => { const reportLines = stdout.split('\n') const finalLine = '3 problems (3 errors, 0 warnings)' - expect(reportLines[reportLines.length - 3]).to.contain(finalLine) + expect(reportLines[reportLines.length - 7]).to.contain(finalLine) }) }) @@ -512,7 +512,7 @@ describe('e2e', function () { it('should get the right report (8)', () => { const reportLines = stdout.split('\n') const finalLine = '5 problems (5 errors, 0 warnings)' - expect(reportLines[reportLines.length - 3]).to.contain(finalLine) + expect(reportLines[reportLines.length - 7]).to.contain(finalLine) }) }) @@ -558,7 +558,7 @@ describe('e2e', function () { it('should get the right report (9)', () => { const reportLines = stdout.split('\n') const finalLine = '6 problems (6 errors, 0 warnings)' - expect(reportLines[reportLines.length - 3]).to.contain(finalLine) + expect(reportLines[reportLines.length - 7]).to.contain(finalLine) }) }) @@ -604,7 +604,7 @@ describe('e2e', function () { it('should get the right report (10)', () => { const reportLines = stdout.split('\n') const finalLine = '18 problems (18 errors, 0 warnings)' - expect(reportLines[reportLines.length - 3]).to.contain(finalLine) + expect(reportLines[reportLines.length - 7]).to.contain(finalLine) }) }) @@ -649,7 +649,7 @@ describe('e2e', function () { // it('should get the right report (11)', () => { // const reportLines = stdout.split('\n') // const finalLine = '12 problems (12 errors, 0 warnings)' - // expect(reportLines[reportLines.length - 3]).to.contain(finalLine) + // expect(reportLines[reportLines.length - 7]).to.contain(finalLine) // }) // })