Repository: protofire/solhint
Pull request #450 (2023-07-14 16:38:48)

I296 fix: stdin command with formatter support
This PR allows for the stdin command to display report with the selected formatter 
Also counts the warning and display a message if max-warning has been reached




Patch:
diff --git a/README.md b/README.md
index 507c31e..6d8e70e 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,7 @@ Options:
 Commands:
 
   stdin [options]                         linting of source code data provided to STDIN
+  list-rules                              display covered rules of current .solhint.json
 ```
 ### Note
 The `--fix`  option currently works only on "avoid-throw" and "avoid-sha3" rules
diff --git a/lib/rules/best-practises/reason-string.js b/lib/rules/best-practises/reason-string.js
index f535b90..33b44a2 100644
--- a/lib/rules/best-practises/reason-string.js
+++ b/lib/rules/best-practises/reason-string.js
@@ -82,7 +82,12 @@ class ReasonStringChecker extends BaseChecker {
       // If has reason message and is too long, throw an error
       const reason = _.last(functionParameters).value
       if (reason.length > this.maxCharactersLong) {
-        this._errorMessageIsTooLong(node, functionName)
+        const infoMessage = reason.length
+          .toString()
+          .concat(' counted / ')
+          .concat(this.maxCharactersLong.toString())
+          .concat(' allowed')
+        this._errorMessageIsTooLong(node, functionName, infoMessage)
       }
     }
   }
@@ -103,8 +108,8 @@ class ReasonStringChecker extends BaseChecker {
     this.error(node, `Provide an error message for ${key}`)
   }
 
-  _errorMessageIsTooLong(node, key) {
-    this.error(node, `Error message for ${key} is too long`)
+  _errorMessageIsTooLong(node, key, infoMessage) {
+    this.error(node, `Error message for ${key} is too long: ${infoMessage}`)
   }
 }
 
diff --git a/solhint.js b/solhint.js
index 225aa94..f3e09d1 100644
--- a/solhint.js
+++ b/solhint.js
@@ -30,9 +30,11 @@ function init() {
     .action(execMainAction)
 
   program
-    .command('stdin')
-    .description('linting of source code data provided to STDIN')
+    .command('stdin', null, { noHelp: false })
     .option('--filename [file_name]', 'name of file received using STDIN')
+    .description(
+      'linting of source code data provided to STDIN\nEx: echo "contract Foo { function f1()external view returns(uint256) {} }" | solhint stdin\nEx: solhint stdin --filename "./pathToFilename" -f table'
+    )
     .action(processStdin)
 
   program
@@ -40,6 +42,11 @@ function init() {
     .description('create configuration file for solhint')
     .action(writeSampleConfigFile)
 
+  program
+    .command('list-rules', null, { noHelp: false })
+    .description('display covered rules of current .solhint.json')
+    .action(listRules)
+
   if (process.argv.length <= 2) {
     program.help()
   }
@@ -63,9 +70,6 @@ function execMainAction() {
 
   const reportLists = program.args.filter(_.isString).map(processPath)
   const reports = _.flatten(reportLists)
-  const warningsCount = reports.reduce((acc, i) => acc + i.warningCount, 0)
-  const warningsNumberExceeded =
-    program.opts().maxWarnings >= 0 && warningsCount > program.opts().maxWarnings
 
   if (program.opts().fix) {
     for (const report of reports) {
@@ -92,37 +96,31 @@ function execMainAction() {
     })
   }
 
-  if (printReports(reports, formatterFn)) {
-    if (
-      program.opts().maxWarnings &&
-      reports &&
-      reports.length > 0 &&
-      !reports[0].errorCount &&
-      warningsNumberExceeded
-    ) {
-      console.log(
-        'Solhint found more warnings than the maximum specified (maximum: %s, found: %s)',
-        program.opts().maxWarnings,
-        warningsCount
-      )
-      process.exit(1)
-    }
-  }
+  printReports(reports, formatterFn)
 
   exitWithCode(reports)
 }
 
 function processStdin(options) {
-  const STDIN_FILE = 0
+  const STDIN_FILE = options.filename || 0
   const stdinBuffer = fs.readFileSync(STDIN_FILE)
-
   const report = processStr(stdinBuffer.toString())
   report.file = options.filename || 'stdin'
-  const formatterFn = getFormatter()
 
-  printReports([report], formatterFn)
+  let formatterFn
+  try {
+    // to check if is a valid formatter before execute linter
+    formatterFn = getFormatter(program.opts().formatter)
+  } catch (ex) {
+    console.error(ex.message)
+    process.exit(1)
+  }
+
+  const reports = [report]
 
-  exitWithCode([report])
+  printReports(reports, formatterFn)
+
+  exitWithCode(reports)
 }
 
 function writeSampleConfigFile() {
@@ -165,7 +163,6 @@ const readIgnore = _.memoize(() => {
 
 const readConfig = _.memoize(() => {
   let config = {}
-
   try {
     config = loadConfig(program.opts().config)
   } catch (e) {
@@ -190,8 +187,41 @@ function processPath(path) {
   return linter.processPath(path, readConfig())
 }
 
+function areWarningsExceeded(reports) {
+  const warningsCount = reports.reduce((acc, i) => acc + i.warningCount, 0)
+  const warningsNumberExceeded =
+    program.opts().maxWarnings >= 0 && warningsCount > program.opts().maxWarnings
+
+  return { warningsNumberExceeded, warningsCount }
+}
+
 function printReports(reports, formatter) {
+  const warnings = areWarningsExceeded(reports)
+
   console.log(formatter(reports))
+
+  if (
+    program.opts().maxWarnings &&
+    reports &&
+    reports.length > 0 &&
+    warnings.warningsNumberExceeded
+  ) {
+    if (!reports[0].errorCount) {
+      console.log(
+        'Solhint found more warnings than the maximum specified (maximum: %s, found: %s)',
+        program.opts().maxWarnings,
+        warnings.warningsCount
+      )
+    } else {
+      console.log(
+        'Error/s found on rules! [max-warnings] rule ignored. Fixing errors enables max-warnings',
+        program.opts().maxWarnings,
+        warnings.warningsCount
+      )
+    }
+    process.exit(1)
+  }
+
   return reports
 }
 
@@ -207,6 +237,40 @@ function getFormatter(formatter) {
   }
 }
 
+function listRules() {
+  if (process.argv.length !== 3) {
+    console.log('Error!! no additional parameters after list-rules command')
+    process.exit(0)
+  }
+
+  const configPath = '.solhint.json'
+  if (!fs.existsSync(configPath)) {
+    console.log('Error!! Configuration does not exists')
+    process.exit(0)
+  } else {
+    const config = readConfig()
+    console.log('\nConfiguration File: \n', config)
+
+    const reportLists = linter.processPath(configPath, config)
+    const rulesObject = reportLists[0].config
+
+    console.log('\nRules: \n')
+    const orderedRules = Object.keys(rulesObject)
+      .sort()
+      .reduce((obj, key) => {
+        obj[key] = rulesObject[key]
+        return obj
+      }, {})
+
+    // eslint-disable-next-line func-names
+    Object.keys(orderedRules).forEach(function (key) {
+      console.log('- ', key, ': ', orderedRules[key])
+    })
+  }
+
+  process.exit(0)
+}
+
 function exitWithCode(reports) {
   const errorsCount = reports.reduce((acc, i) => acc + i.errorCount, 0)
 


Test patch:
diff --git a/test/rules/best-practises/reason-string.js b/test/rules/best-practises/reason-string.js
index c7f0cfd..30546d7 100644
--- a/test/rules/best-practises/reason-string.js
+++ b/test/rules/best-practises/reason-string.js
@@ -101,4 +101,25 @@ describe('Linter - reason-string', () => {
     assertNoWarnings(report)
     assertNoErrors(report)
   })
+
+  it('should raise reason string maxLength error with added data', () => {
+    const qtyChars = 'Roles: account already has role'.length
+    const maxLength = 5
+
+    const code = funcWith(`require(!has(role, account), "Roles: account already has role");
+        role.bearer[account] = true;role.bearer[account] = true;
+    `)
+
+    const report = linter.processStr(code, {
+      rules: { 'reason-string': ['warn', { maxLength: 5 }] },
+    })
+
+    assert.ok(report.warningCount > 0)
+    assertWarnsCount(report, 1)
+
+    assert.equal(
+      report.reports[0].message,
+      `Error message for require is too long: ${qtyChars} counted / ${maxLength} allowed`
+    )
+  })
 })