Repository: protofire/solhint (js)

Issue:
Proper error output for parse errors
Currently parse errors cause an exception which results in `solhint` aborting and printing the Javascript stack trace. This makes it difficult to extract parse errors from the output for IDE integration for example. Ideally parse errors would show like any other hint error.

I’m happy to provide a patch.

Pull request description:
Report parse errors instead of throwing an exception
Report parse errors instead of throwing an exception. Fixes #245

Patch:
diff --git a/lib/index.js b/lib/index.js
index 6abcbe5..17a9b87 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -13,21 +13,34 @@ function parseInput(inputStr) {
     // first we try to parse the string as we normally do
     return parser.parse(inputStr, { loc: true, range: true })
   } catch (e) {
-    try {
-      // using 'loc' may throw when inputStr is empty or only has comments
-      return parser.parse(inputStr, {})
-    } catch (error) {
-      // if the parser fails in both scenarios (with and without 'loc')
-      // we throw the error, as we may be facing an invalid Solidity file
-      throw new Error(error)
-    }
+    // using 'loc' may throw when inputStr is empty or only has comments
+    return parser.parse(inputStr, {})
   }
 }
 
 function processStr(inputStr, config = {}, fileName = '') {
   config = applyExtends(config)
 
-  const ast = parseInput(inputStr)
+  let ast
+  try {
+    ast = parseInput(inputStr)
+  } catch (e) {
+    if (e instanceof parser.ParserError) {
+      const reporter = new Reporter([], config)
+      for (const error of e.errors) {
+        reporter.addReport(
+          error.line,
+          error.column,
+          Reporter.SEVERITY.ERROR,
+          `Parse error: ${error.message}`
+        )
+      }
+      return reporter
+    } else {
+      throw e
+    }
+  }
+
   const tokens = parser.tokenize(inputStr, { loc: true })
   const reporter = new Reporter(tokens, config)
   const listener = new TreeListener(checkers(reporter, config, inputStr, tokens, fileName))


Test patch:
diff --git a/test/parse-error.js b/test/parse-error.js
new file mode 100644
index 0000000..8e13c3d
--- /dev/null
+++ b/test/parse-error.js
@@ -0,0 +1,27 @@
+const assert = require('assert')
+const { assertErrorCount } = require('./common/asserts')
+const linter = require('./../lib/index')
+
+describe('Parse error', () => {
+  it('should report parse errors', () => {
+    const report = linter.processStr('contract Foo {', {})
+
+    assertErrorCount(report, 1)
+    const error = report.reports[0]
+    assert.equal(error.line, 1)
+    assert.equal(error.column, 14)
+    assert.ok(error.message.startsWith("Parse error: mismatched input '<EOF>'"))
+  })
+
+  it('should report multiple parse errors', () => {
+    const report = linter.processStr('contract Foo { function constructor() }\n contract Bar {', {})
+
+    assertErrorCount(report, 3)
+    const messages = report.reports.map(error => error.message)
+    assert.ok(
+      messages[0].startsWith("Parse error: no viable alternative at input 'functionconstructor'")
+    )
+    assert.ok(messages[1].startsWith("Parse error: mismatched input '}'"))
+    assert.ok(messages[2].startsWith("Parse error: mismatched input '<EOF>'"))
+  })
+})