Repository: source-academy/js-slang
Pull request #176 (2019-02-21 11:49:12)

added toggle for verbose error messages
actual verbose error messages not included as part of this pull request

coveralls: ## Pull Request Test Coverage Report for [Build 677](https://coveralls.io/builds/21764062)

* **21** of **23**   **(91.3%)**  changed or added relevant lines in **3** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**1.9%**) to **93.085%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/index.ts](https://coveralls.io/builds/21764062/source?filename=src%2Findex.ts#L33) | 15 | 17 | 88.24%
<!-- | **Total:** | **21** | **23** | **91.3%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/21764062/badge)](https://coveralls.io/builds/21764062) |
| :-- | --: |
| Change from base [Build 676](https://coveralls.io/builds/21761434): |  1.9% |
| Covered Lines: | 1282 |
| Relevant Lines: | 1368 |

---
##### 💛  - [Coveralls](https://coveralls.io)

thomastanck: You'll have to sync master after #174 is merged and update tests. There is a refactor to the test API.
thomastanck: Check on `theDirective`

> On 25 Feb 2019, at 22:35, Leow Wen Bin <notifications@github.com> wrote:
> 
> @LeowWB commented on this pull request.
> 
> In src/index.ts:
> 
> > @@ -35,10 +49,26 @@ export function runInContext(
>    context: Context,
>    options: Partial<IOptions> = {}
>  ): Promise<Result> {
> +  function getFirstLine(theProgram: Program) {
> +    if (theProgram.body[0] && theProgram.body[0].type === 'ExpressionStatement') {
> +      const firstLineOfProgram = theProgram.body[0] as ExpressionStatement
> +      const theDirective = (firstLineOfProgram as Directive).directive
> +      if (theDirective) {
> Sorry, could you rephrase? You'd check for undefined on which name?
> 
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub, or mute the thread.

openorclose: Is there a reason verbose mode is not another option to be passed into runInContext? Currently native already works this way.
LeowWB: > Is there a reason verbose mode is not another option to be passed into runInContext? Currently native already works this way.

The main reason is that the verbosity of error messages has little to do with how the program is being run, but rather the way the output is displayed to the user. A verbose error mode doesn't result in any change to the number of errors, or any other aspect of what the interpreter does in the process of running the program. 
martin-henz: I think we should keep such annotations out of the student programs. The reason: Project NGED is working on a collaborative editor. One user may want to have verbose error messages, while another one may want to have terse messages. I can foresee those two going back and forth.

Second reason: We want to keep the language js-slang clean. It should be a strict subset of JavaScript.

It's best to add an option to runInContext, as @openorclose suggests.

In order to test this in the frontend, you may want to look at this frontend branch, implementing an auto-run feature: https://github.com/source-academy/cadet-frontend/tree/auto-run-feature

We would for now add a similar button to the frontend. We can later think about user preference settings for such things.
thomastanck: The decision was made to mimic the `"use strict";` directive quite some time ago, but we need to revisit that decision now that NATIVE is merged in. Perhaps we should merge this in first since I believe the other PR has changes that depends on this one. After both of these are merged, it shouldn't be hard to make a final PR switching to the option method.
martin-henz: Sure. No problem to merge the current solution

"enable verbose";

The directive
"use strict"; 
itself is a bit of a trick. Expression statements like this are consistent with JavaScript syntax (and don't have any effect).

As Thomas suggests, let's re-visit this later.
martin-henz: Looks like we need to resolve a conflict in the test cases; both branches added test cases in the same location.

Patch:
diff --git a/src/createContext.ts b/src/createContext.ts
index 320e3276..8601a490 100644
--- a/src/createContext.ts
+++ b/src/createContext.ts
@@ -1,3 +1,5 @@
+// Variable determining chapter of Source is contained in this file.
+
 import { GLOBAL, GLOBAL_KEY_TO_ACCESS_NATIVE_STORAGE } from './constants'
 import { stringify } from './interop'
 import * as list from './stdlib/list'
diff --git a/src/index.ts b/src/index.ts
index f0ac522a..4f892245 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,4 +1,4 @@
-// import { generate } from 'astring'
+import { ExpressionStatement, Program } from 'estree'
 import { RawSourceMap, SourceMapConsumer } from 'source-map'
 import { UNKNOWN_LOCATION } from './constants'
 // import { UNKNOWN_LOCATION } from './constants'
@@ -8,7 +8,7 @@ import { ExceptionError, InterruptedError, RuntimeSourceError } from './interpre
 import { parse } from './parser'
 import { AsyncScheduler, PreemptiveScheduler } from './schedulers'
 import { transpile } from './transpiler'
-import { Context, Error, Finished, Result, Scheduler, SourceError } from './types'
+import { Context, Directive, Error, Finished, Result, Scheduler, SourceError } from './types'
 import { sandboxedEval } from './utils/evalContainer'
 
 export interface IOptions {
@@ -23,13 +23,25 @@ const DEFAULT_OPTIONS: IOptions = {
   isNativeRunnable: false
 }
 
+// deals with parsing error objects and converting them to strings (for repl at least)
+
+let verboseErrors = false
 const resolvedErrorPromise = Promise.resolve({ status: 'error' } as Result)
 
-export function parseError(errors: SourceError[]): string {
+export function parseError(errors: SourceError[], verbose: boolean = verboseErrors): string {
   const errorMessagesArr = errors.map(error => {
     const line = error.location ? error.location.start.line : '<unknown>'
+    const column = error.location ? error.location.start.column : '<unknown>'
     const explanation = error.explain()
-    return `Line ${line}: ${explanation}`
+    const elaboration = error.elaborate()
+
+    if (verbose) {
+      // TODO currently elaboration is just tagged on to a new line after the error message itself. find a better
+      // way to display it.
+      return `Line ${line}, Column ${column}: ${explanation}\n${elaboration}\n`
+    } else {
+      return `Line ${line}: ${explanation}`
+    }
   })
   return errorMessagesArr.join('\n')
 }
@@ -39,10 +51,22 @@ export function runInContext(
   context: Context,
   options: Partial<IOptions> = {}
 ): Promise<Result> {
+  function getFirstLine(theProgram: Program) {
+    if (theProgram.body[0] && theProgram.body[0].type === 'ExpressionStatement') {
+      const firstLineOfProgram = theProgram.body[0] as ExpressionStatement
+      const theDirective = (firstLineOfProgram as Directive).directive
+      if (theDirective !== undefined) {
+        return theDirective
+      }
+    }
+
+    return undefined
+  }
   const theOptions: IOptions = { ...DEFAULT_OPTIONS, ...options }
   context.errors = []
   const program = parse(code, context)
   if (program) {
+    verboseErrors = getFirstLine(program) === 'enable verbose'
     if (theOptions.isNativeRunnable) {
       let transpiled
       let sourceMapJson: RawSourceMap | undefined
diff --git a/src/interpreter-errors.ts b/src/interpreter-errors.ts
index f8842ff9..0cad02dc 100644
--- a/src/interpreter-errors.ts
+++ b/src/interpreter-errors.ts
@@ -1,4 +1,4 @@
-/* tslint:disable:max-classes-per-file */
+/* tslint:disable: max-classes-per-file */
 import { baseGenerator, generate } from 'astring'
 import * as es from 'estree'
 
@@ -107,7 +107,9 @@ export class UndefinedVariable extends RuntimeSourceError {
   }
 
   public elaborate() {
-    return 'TODO'
+    return `Before you can read the value of ${
+      this.name
+    }, you need to declare it as a variable or a constant. You can do this using the let or const keywords.`
   }
 }
 
@@ -117,11 +119,17 @@ export class UnassignedVariable extends RuntimeSourceError {
   }
 
   public explain() {
-    return `Name ${this.name} not yet assigned`
+    return `Name ${this.name} declared later in current scope but not yet assigned`
   }
 
   public elaborate() {
-    return 'TODO'
+    return `If you're trying to access the value of ${
+      this.name
+    } from an outer scope, please rename the inner ${
+      this.name
+    }. An easy way to avoid this issue in future would be to avoid declaring any variables or constants with the name ${
+      this.name
+    } in the same scope.`
   }
 }
 
diff --git a/src/repl/repl.ts b/src/repl/repl.ts
index 95fbeea5..5ffe25a6 100644
--- a/src/repl/repl.ts
+++ b/src/repl/repl.ts
@@ -1,20 +1,23 @@
-import repl = require('repl')
+import repl = require('repl') // 'repl' here refers to the module named 'repl' in index.d.ts
 import { createContext, parseError, runInContext } from '../index'
 
 function startRepl(chapter = 1) {
   // use defaults for everything
   const context = createContext(chapter)
-  repl.start({
-    eval: (cmd, unusedContext, unusedFilename, callback) => {
-      runInContext(cmd, context, { scheduler: 'preemptive' }).then(obj => {
-        if (obj.status === 'finished') {
-          callback(null, obj.value)
-        } else {
-          callback(new Error(parseError(context.errors)), undefined)
-        }
-      })
+  repl.start(
+    // the object being passed as argument fits the interface ReplOptions in the repl module.
+    {
+      eval: (cmd, unusedContext, unusedFilename, callback) => {
+        runInContext(cmd, context, { scheduler: 'preemptive' }).then(obj => {
+          if (obj.status === 'finished') {
+            callback(null, obj.value)
+          } else {
+            callback(new Error(parseError(context.errors)), undefined)
+          }
+        })
+      }
     }
-  })
+  )
 }
 
 function main() {
diff --git a/src/rules/noDeclareMutable.ts b/src/rules/noDeclareMutable.ts
index f864c81a..949b00f1 100644
--- a/src/rules/noDeclareMutable.ts
+++ b/src/rules/noDeclareMutable.ts
@@ -1,3 +1,4 @@
+import * as astr from 'astring'
 import * as es from 'estree'
 
 import { ErrorSeverity, ErrorType, Rule, SourceError } from '../types'
@@ -21,7 +22,10 @@ export class NoDeclareMutableError implements SourceError {
   }
 
   public elaborate() {
-    return this.explain()
+    const name = (this.node.declarations[0].id as es.Identifier).name
+    const value = astr.generate(this.node.declarations[0].init)
+
+    return `Use keyword "const" instead, to declare a constant:\n\tconst ${name} = ${value};`
   }
 }
 
diff --git a/src/types.ts b/src/types.ts
index 51655f14..12003329 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -3,7 +3,6 @@
 	error-related classes).
 */
 
-/* tslint:disable:interface-functionName max-classes-per-file */
 /* tslint:disable:max-classes-per-file */
 
 import { SourceLocation } from 'acorn'
@@ -125,3 +124,13 @@ export type Result = Suspended | Finished | Error
 export interface Scheduler {
   run(it: IterableIterator<Value>, context: Context): Promise<Result>
 }
+
+/*
+	Although the ESTree specifications supposedly provide a Directive interface, the index file does not seem to export it.
+	As such this interface was created here to fulfil the same purpose.
+ */
+export interface Directive extends es.ExpressionStatement {
+  type: 'ExpressionStatement'
+  expression: es.Literal
+  directive: string
+}


Test patch:
diff --git a/src/__tests__/__snapshots__/block-scoping.ts.snap b/src/__tests__/__snapshots__/block-scoping.ts.snap
index 8f21a906..55abd8c1 100644
--- a/src/__tests__/__snapshots__/block-scoping.ts.snap
+++ b/src/__tests__/__snapshots__/block-scoping.ts.snap
@@ -63,7 +63,7 @@ f();",
       "type": "Runtime",
     },
   ],
-  "parsedErrors": "Line 3: Name a not yet assigned",
+  "parsedErrors": "Line 3: Name a declared later in current scope but not yet assigned",
   "result": undefined,
   "resultStatus": "error",
   "visualiseListResult": Array [],
@@ -96,7 +96,7 @@ Object {
       "type": "Runtime",
     },
   ],
-  "parsedErrors": "Line 3: Name a not yet assigned",
+  "parsedErrors": "Line 3: Name a declared later in current scope but not yet assigned",
   "result": undefined,
   "resultStatus": "error",
   "visualiseListResult": Array [],
@@ -129,7 +129,7 @@ v;",
       "type": "Runtime",
     },
   ],
-  "parsedErrors": "Line 1: Name f not yet assigned",
+  "parsedErrors": "Line 1: Name f declared later in current scope but not yet assigned",
   "result": undefined,
   "resultStatus": "error",
   "visualiseListResult": Array [],
diff --git a/src/__tests__/__snapshots__/interpreter-errors.ts.snap b/src/__tests__/__snapshots__/interpreter-errors.ts.snap
index 135b9ff1..0287dd1a 100644
--- a/src/__tests__/__snapshots__/interpreter-errors.ts.snap
+++ b/src/__tests__/__snapshots__/interpreter-errors.ts.snap
@@ -75,6 +75,70 @@ Object {
 }
 `;
 
+exports[`Error when assigning to builtin - verbose: expectParsedError 1`] = `
+Object {
+  "alertResult": Array [],
+  "code": "\\"enable verbose\\";
+map = 5;",
+  "displayResult": Array [],
+  "errors": Array [
+    ConstAssignment {
+      "location": SourceLocation {
+        "end": Position {
+          "column": 7,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 0,
+          "line": 2,
+        },
+      },
+      "name": "map",
+      "severity": "Error",
+      "type": "Runtime",
+    },
+  ],
+  "parsedErrors": "Line 2, Column 0: Cannot assign new value to constant map
+TODO
+",
+  "result": undefined,
+  "resultStatus": "error",
+  "visualiseListResult": Array [],
+}
+`;
+
+exports[`Error when assigning to builtin - verbose: expectParsedError 2`] = `
+Object {
+  "alertResult": Array [],
+  "code": "\\"enable verbose\\";
+undefined = 5;",
+  "displayResult": Array [],
+  "errors": Array [
+    ConstAssignment {
+      "location": SourceLocation {
+        "end": Position {
+          "column": 13,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 0,
+          "line": 2,
+        },
+      },
+      "name": "undefined",
+      "severity": "Error",
+      "type": "Runtime",
+    },
+  ],
+  "parsedErrors": "Line 2, Column 0: Cannot assign new value to constant undefined
+TODO
+",
+  "result": undefined,
+  "resultStatus": "error",
+  "visualiseListResult": Array [],
+}
+`;
+
 exports[`Error when assigning to builtin: expectParsedError 1`] = `
 Object {
   "alertResult": Array [],
@@ -293,6 +357,38 @@ f(1, 2);",
 }
 `;
 
+exports[`Error when calling non function value "string" - verbose: expectParsedError 1`] = `
+Object {
+  "alertResult": Array [],
+  "code": "\\"enable verbose\\";
+  'string'();",
+  "displayResult": Array [],
+  "errors": Array [
+    CallingNonFunctionValue {
+      "callee": "string",
+      "location": SourceLocation {
+        "end": Position {
+          "column": 12,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 2,
+          "line": 2,
+        },
+      },
+      "severity": "Error",
+      "type": "Runtime",
+    },
+  ],
+  "parsedErrors": "Line 2, Column 2: Calling non-function value \\"string\\"
+TODO
+",
+  "result": undefined,
+  "resultStatus": "error",
+  "visualiseListResult": Array [],
+}
+`;
+
 exports[`Error when calling non function value "string": expectParsedError 1`] = `
 Object {
   "alertResult": Array [],
@@ -322,6 +418,38 @@ Object {
 }
 `;
 
+exports[`Error when calling non function value 0 - verbose: expectParsedError 1`] = `
+Object {
+  "alertResult": Array [],
+  "code": "\\"enable verbose\\";
+  0();",
+  "displayResult": Array [],
+  "errors": Array [
+    CallingNonFunctionValue {
+      "callee": 0,
+      "location": SourceLocation {
+        "end": Position {
+          "column": 5,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 2,
+          "line": 2,
+        },
+      },
+      "severity": "Error",
+      "type": "Runtime",
+    },
+  ],
+  "parsedErrors": "Line 2, Column 2: Calling non-function value 0
+TODO
+",
+  "result": undefined,
+  "resultStatus": "error",
+  "visualiseListResult": Array [],
+}
+`;
+
 exports[`Error when calling non function value 0: expectParsedError 1`] = `
 Object {
   "alertResult": Array [],
@@ -351,6 +479,40 @@ Object {
 }
 `;
 
+exports[`Error when calling non function value array - verbose: expectParsedError 1`] = `
+Object {
+  "alertResult": Array [],
+  "code": "\\"enable verbose\\";
+[1]();",
+  "displayResult": Array [],
+  "errors": Array [
+    CallingNonFunctionValue {
+      "callee": Array [
+        1,
+      ],
+      "location": SourceLocation {
+        "end": Position {
+          "column": 5,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 0,
+          "line": 2,
+        },
+      },
+      "severity": "Error",
+      "type": "Runtime",
+    },
+  ],
+  "parsedErrors": "Line 2, Column 0: Calling non-function value [1]
+TODO
+",
+  "result": undefined,
+  "resultStatus": "error",
+  "visualiseListResult": Array [],
+}
+`;
+
 exports[`Error when calling non function value array: expectParsedError 1`] = `
 Object {
   "alertResult": Array [],
@@ -382,6 +544,44 @@ Object {
 }
 `;
 
+exports[`Error when calling non function value null - verbose: expectParsedError 1`] = `
+Object {
+  "alertResult": Array [],
+  "code": "\\"enable verbose\\";
+  null();",
+  "displayResult": Array [],
+  "errors": Array [
+    NoNullError {
+      "node": Node {
+        "end": 24,
+        "loc": SourceLocation {
+          "end": Position {
+            "column": 6,
+            "line": 2,
+          },
+          "start": Position {
+            "column": 2,
+            "line": 2,
+          },
+        },
+        "raw": "null",
+        "start": 20,
+        "type": "Literal",
+        "value": null,
+      },
+      "severity": "Error",
+      "type": "Syntax",
+    },
+  ],
+  "parsedErrors": "Line 2, Column 2: null literals are not allowed
+null literals are not allowed
+",
+  "result": undefined,
+  "resultStatus": "error",
+  "visualiseListResult": Array [],
+}
+`;
+
 exports[`Error when calling non function value null: expectParsedError 1`] = `
 Object {
   "alertResult": Array [],
@@ -410,7 +610,43 @@ Object {
       "type": "Syntax",
     },
   ],
-  "parsedErrors": "Line 1: null literals are not allowed",
+  "parsedErrors": "Line 1, Column 0: null literals are not allowed
+null literals are not allowed
+",
+  "result": undefined,
+  "resultStatus": "error",
+  "visualiseListResult": Array [],
+}
+`;
+
+exports[`Error when calling non function value object - verbose: expectParsedError 1`] = `
+Object {
+  "alertResult": Array [],
+  "code": "\\"enable verbose\\";
+({a: 1})();",
+  "displayResult": Array [],
+  "errors": Array [
+    CallingNonFunctionValue {
+      "callee": Object {
+        "a": 1,
+      },
+      "location": SourceLocation {
+        "end": Position {
+          "column": 10,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 0,
+          "line": 2,
+        },
+      },
+      "severity": "Error",
+      "type": "Runtime",
+    },
+  ],
+  "parsedErrors": "Line 2, Column 0: Calling non-function value {\\"a\\": 1}
+TODO
+",
   "result": undefined,
   "resultStatus": "error",
   "visualiseListResult": Array [],
@@ -448,6 +684,38 @@ Object {
 }
 `;
 
+exports[`Error when calling non function value true - verbose: expectParsedError 1`] = `
+Object {
+  "alertResult": Array [],
+  "code": "\\"enable verbose\\";
+  true();",
+  "displayResult": Array [],
+  "errors": Array [
+    CallingNonFunctionValue {
+      "callee": true,
+      "location": SourceLocation {
+        "end": Position {
+          "column": 8,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 2,
+          "line": 2,
+        },
+      },
+      "severity": "Error",
+      "type": "Runtime",
+    },
+  ],
+  "parsedErrors": "Line 2, Column 2: Calling non-function value true
+TODO
+",
+  "result": undefined,
+  "resultStatus": "error",
+  "visualiseListResult": Array [],
+}
+`;
+
 exports[`Error when calling non function value true: expectParsedError 1`] = `
 Object {
   "alertResult": Array [],
@@ -477,6 +745,38 @@ Object {
 }
 `;
 
+exports[`Error when calling non function value undefined - verbose: expectParsedError 1`] = `
+Object {
+  "alertResult": Array [],
+  "code": "\\"enable verbose\\";
+  undefined();",
+  "displayResult": Array [],
+  "errors": Array [
+    CallingNonFunctionValue {
+      "callee": undefined,
+      "location": SourceLocation {
+        "end": Position {
+          "column": 13,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 2,
+          "line": 2,
+        },
+      },
+      "severity": "Error",
+      "type": "Runtime",
+    },
+  ],
+  "parsedErrors": "Line 2, Column 2: Calling non-function value undefined
+TODO
+",
+  "result": undefined,
+  "resultStatus": "error",
+  "visualiseListResult": Array [],
+}
+`;
+
 exports[`Error when calling non function value undefined: expectParsedError 1`] = `
 Object {
   "alertResult": Array [],
@@ -1083,6 +1383,38 @@ Object {
 }
 `;
 
+exports[`Undefined variable error is thrown - verbose: expectParsedError 1`] = `
+Object {
+  "alertResult": Array [],
+  "code": "\\"enable verbose\\";
+im_undefined;",
+  "displayResult": Array [],
+  "errors": Array [
+    UndefinedVariable {
+      "location": SourceLocation {
+        "end": Position {
+          "column": 12,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 0,
+          "line": 2,
+        },
+      },
+      "name": "im_undefined",
+      "severity": "Error",
+      "type": "Runtime",
+    },
+  ],
+  "parsedErrors": "Line 2, Column 0: Name im_undefined not declared
+Before you can read the value of im_undefined, you need to declare it as a variable or a constant. You can do this using the let or const keywords.
+",
+  "result": undefined,
+  "resultStatus": "error",
+  "visualiseListResult": Array [],
+}
+`;
+
 exports[`Undefined variable error is thrown: expectParsedError 1`] = `
 Object {
   "alertResult": Array [],
diff --git a/src/__tests__/block-scoping.ts b/src/__tests__/block-scoping.ts
index 10ee2d0e..f1d35401 100644
--- a/src/__tests__/block-scoping.ts
+++ b/src/__tests__/block-scoping.ts
@@ -130,7 +130,9 @@ test('No hoisting of functions. Only the name is hoisted like let and const', ()
         return 1;
       }
       v;
-    `).toMatchInlineSnapshot(`"Line 1: Name f not yet assigned"`)
+    `).toMatchInlineSnapshot(
+    `"Line 1: Name f declared later in current scope but not yet assigned"`
+  )
 }, 30000)
 
 test('Error when accessing temporal dead zone', () => {
@@ -141,7 +143,9 @@ test('Error when accessing temporal dead zone', () => {
       const a = 5;
     }
     f();
-    `).toMatchInlineSnapshot(`"Line 3: Name a not yet assigned"`)
+    `).toMatchInlineSnapshot(
+    `"Line 3: Name a declared later in current scope but not yet assigned"`
+  )
 }, 30000)
 
 // tslint:disable-next-line:max-line-length
@@ -152,7 +156,9 @@ test('In a block, every going-to-be-defined variable in the block cannot be acce
         a + a;
         const a = 10;
       }
-    `).toMatchInlineSnapshot(`"Line 3: Name a not yet assigned"`)
+    `).toMatchInlineSnapshot(
+    `"Line 3: Name a declared later in current scope but not yet assigned"`
+  )
 }, 30000)
 
 test('Shadowed variables may not be assigned to until declared in the current scope', () => {
diff --git a/src/__tests__/interpreter-errors.ts b/src/__tests__/interpreter-errors.ts
index 519712ed..6304a153 100644
--- a/src/__tests__/interpreter-errors.ts
+++ b/src/__tests__/interpreter-errors.ts
@@ -1,34 +1,92 @@
 import { parseError, runInContext } from '../index'
 import { mockContext } from '../mocks/context'
 import {
+  expectDifferentParsedErrors,
   expectParsedError,
   expectParsedErrorNoSnapshot,
   expectResult,
   stripIndent
 } from '../utils/testing'
 
+const undefinedVariable = stripIndent`
+im_undefined;
+`
+const undefinedVariableVerbose = stripIndent`
+"enable verbose";
+im_undefined;
+`
+
 test('Undefined variable error is thrown', () => {
-  return expectParsedError(stripIndent`
-    im_undefined;
-  `).toMatchInlineSnapshot(`"Line 1: Name im_undefined not declared"`)
+  return expectParsedError(undefinedVariable).toMatchInlineSnapshot(
+    `"Line 1: Name im_undefined not declared"`
+  )
+})
+
+test('Undefined variable error is thrown - verbose', () => {
+  return expectParsedError(undefinedVariableVerbose).toMatchInlineSnapshot(`
+"Line 2, Column 0: Name im_undefined not declared
+Before you can read the value of im_undefined, you need to declare it as a variable or a constant. \
+You can do this using the let or const keywords.
+"
+`)
+})
+
+test('Undefined variable error message differs from verbose version', () => {
+  return expectDifferentParsedErrors(undefinedVariable, undefinedVariableVerbose).toBe(undefined)
 })
 
+const assignToBuiltin = stripIndent`
+map = 5;
+`
+
+const assignToBuiltinVerbose = stripIndent`
+  "enable verbose";
+  map = 5;
+`
+
 test('Error when assigning to builtin', () => {
-  return expectParsedError(
-    stripIndent`
-    map = 5;
-  `,
-    { chapter: 3 }
-  ).toMatchInlineSnapshot(`"Line 1: Cannot assign new value to constant map"`)
+  return expectParsedError(assignToBuiltin, { chapter: 3 }).toMatchInlineSnapshot(
+    `"Line 1: Cannot assign new value to constant map"`
+  )
 })
 
+test('Error when assigning to builtin - verbose', () => {
+  return expectParsedError(assignToBuiltinVerbose, { chapter: 3 }).toMatchInlineSnapshot(`
+"Line 2, Column 0: Cannot assign new value to constant map
+TODO
+"
+`)
+})
+
+test('Assigning to builtin error message differs from verbose version', () => {
+  return expectDifferentParsedErrors(assignToBuiltin, assignToBuiltinVerbose).toBe(undefined)
+})
+
+const assignToBuiltin1 = stripIndent`
+undefined = 5;
+`
+
+const assignToBuiltinVerbose1 = stripIndent`
+  "enable verbose";
+  undefined = 5;
+`
+
 test('Error when assigning to builtin', () => {
-  return expectParsedError(
-    stripIndent`
-    undefined = 5;
-  `,
-    { chapter: 3 }
-  ).toMatchInlineSnapshot(`"Line 1: Cannot assign new value to constant undefined"`)
+  return expectParsedError(assignToBuiltin1, { chapter: 3 }).toMatchInlineSnapshot(
+    `"Line 1: Cannot assign new value to constant undefined"`
+  )
+})
+
+test('Error when assigning to builtin - verbose', () => {
+  return expectParsedError(assignToBuiltinVerbose1, { chapter: 3 }).toMatchInlineSnapshot(`
+"Line 2, Column 0: Cannot assign new value to constant undefined
+TODO
+"
+`)
+})
+
+test('Assigning to builtin error message differs from verbose version', () => {
+  return expectDifferentParsedErrors(assignToBuiltin1, assignToBuiltinVerbose1).toBe(undefined)
 })
 
 // NOTE: Obsoleted due to strict types on member access
@@ -163,65 +221,218 @@ test('Infinite recursion of mutually recursive functions', () => {
   )
 })
 
+const callingNonFunctionValueUndefined = stripIndent`
+undefined();
+`
+
+const callingNonFunctionValueUndefinedVerbose = stripIndent`
+"enable verbose";
+  undefined();
+`
 // should not be different when error passing is fixed
 test('Error when calling non function value undefined', () => {
-  return expectParsedError(
-    stripIndent`
-    undefined();
-  `,
-    { native: true }
-  ).toMatchInlineSnapshot(`"Line 1: Calling non-function value undefined"`)
+  return expectParsedError(callingNonFunctionValueUndefined, {
+    native: true
+  }).toMatchInlineSnapshot('"Line 1: Calling non-function value undefined"')
+})
+
+test('Error when calling non function value undefined - verbose', () => {
+  return expectParsedError(callingNonFunctionValueUndefinedVerbose).toMatchInlineSnapshot(`
+"Line 2, Column 2: Calling non-function value undefined
+TODO
+"
+`)
+})
+
+test('Calling non function value undefined error message differs from verbose version', () => {
+  return expectDifferentParsedErrors(
+    callingNonFunctionValueUndefined,
+    callingNonFunctionValueUndefinedVerbose
+  ).toBe(undefined)
 })
 
+const callingNonFunctionValueNull = stripIndent`
+null();
+`
+
+const callingNonFunctionValueNullVerbose = stripIndent`
+"enable verbose";
+  null();
+`
+
 test('Error when calling non function value null', () => {
-  return expectParsedError(stripIndent`
-    null();
-  `).toMatchInlineSnapshot(`"Line 1: null literals are not allowed"`)
+  return expectParsedError(callingNonFunctionValueNull).toMatchInlineSnapshot(`
+"Line 1, Column 0: null literals are not allowed
+null literals are not allowed
+"
+`)
 })
 
+test('Error when calling non function value null - verbose', () => {
+  return expectParsedError(callingNonFunctionValueNullVerbose).toMatchInlineSnapshot(`
+"Line 2, Column 2: null literals are not allowed
+null literals are not allowed
+"
+`)
+})
+
+test('Calling non function value null error message differs from verbose version', () => {
+  return expectDifferentParsedErrors(
+    callingNonFunctionValueNull,
+    callingNonFunctionValueNullVerbose
+  ).toBe(undefined)
+})
+
+const callingNonFunctionValueTrue = stripIndent`
+true();
+`
+const callingNonFunctionValueTrueVerbose = stripIndent`
+"enable verbose";
+  true();
+`
+
 test('Error when calling non function value true', () => {
-  return expectParsedError(
-    stripIndent`
-    true();
-  `,
-    { native: true }
-  ).toMatchInlineSnapshot(`"Line 1: Calling non-function value true"`)
+  return expectParsedError(callingNonFunctionValueTrue, { native: true }).toMatchInlineSnapshot(
+    `"Line 1: Calling non-function value true"`
+  )
+})
+
+test('Error when calling non function value true - verbose', () => {
+  return expectParsedError(callingNonFunctionValueTrueVerbose).toMatchInlineSnapshot(`
+"Line 2, Column 2: Calling non-function value true
+TODO
+"
+`)
 })
 
+test('Calling non function value true error message differs from verbose version', () => {
+  return expectDifferentParsedErrors(
+    callingNonFunctionValueTrue,
+    callingNonFunctionValueTrueVerbose
+  ).toBe(undefined)
+})
+
+const callingNonFunctionValue0 = stripIndent`
+0();
+`
+
+const callingNonFunctionValue0Verbose = stripIndent`
+"enable verbose";
+  0();
+`
+
 test('Error when calling non function value 0', () => {
-  return expectParsedError(
-    stripIndent`
-    0();
-  `,
-    { native: true }
-  ).toMatchInlineSnapshot(`"Line 1: Calling non-function value 0"`)
+  return expectParsedError(callingNonFunctionValue0, { native: true }).toMatchInlineSnapshot(
+    `"Line 1: Calling non-function value 0"`
+  )
 })
 
+test('Error when calling non function value 0 - verbose', () => {
+  return expectParsedError(callingNonFunctionValue0Verbose).toMatchInlineSnapshot(`
+"Line 2, Column 2: Calling non-function value 0
+TODO
+"
+`)
+})
+
+test('Calling non function value 0 error message differs from verbose version', () => {
+  return expectDifferentParsedErrors(
+    callingNonFunctionValue0,
+    callingNonFunctionValue0Verbose
+  ).toBe(undefined)
+})
+
+const callingNonFunctionValueString = stripIndent`
+'string'();
+`
+
+const callingNonFunctionValueStringVerbose = stripIndent`
+"enable verbose";
+  'string'();
+`
+
 test('Error when calling non function value "string"', () => {
-  return expectParsedError(
-    stripIndent`
-    'string'();
-  `,
-    { native: true }
-  ).toMatchInlineSnapshot(`"Line 1: Calling non-function value \\"string\\""`)
+  return expectParsedError(callingNonFunctionValueString, { native: true }).toMatchInlineSnapshot(
+    `"Line 1: Calling non-function value \\"string\\""`
+  )
 })
 
+test('Error when calling non function value "string" - verbose', () => {
+  return expectParsedError(callingNonFunctionValueStringVerbose).toMatchInlineSnapshot(`
+"Line 2, Column 2: Calling non-function value \\"string\\"
+TODO
+"
+`)
+})
+
+test('Calling non function value string error message differs from verbose version', () => {
+  return expectDifferentParsedErrors(
+    callingNonFunctionValueString,
+    callingNonFunctionValueStringVerbose
+  ).toBe(undefined)
+})
+
+const callingNonFunctionValueArray = stripIndent`
+[1]();
+`
+
+const callingNonFunctionValueArrayVerbose = stripIndent`
+"enable verbose";
+[1]();
+`
+
 test('Error when calling non function value array', () => {
-  return expectParsedError(
-    stripIndent`
-    [1]();
-  `,
-    { chapter: 3, native: true }
-  ).toMatchInlineSnapshot(`"Line 1: Calling non-function value [1]"`)
+  return expectParsedError(callingNonFunctionValueArray, {
+    chapter: 3,
+    native: true
+  }).toMatchInlineSnapshot(`"Line 1: Calling non-function value [1]"`)
+})
+
+test('Error when calling non function value array - verbose', () => {
+  return expectParsedError(callingNonFunctionValueArrayVerbose, { chapter: 3 })
+    .toMatchInlineSnapshot(`
+"Line 2, Column 0: Calling non-function value [1]
+TODO
+"
+`)
+})
+
+test('Calling non function value array error message differs from verbose version', () => {
+  return expectDifferentParsedErrors(
+    callingNonFunctionValueArray,
+    callingNonFunctionValueArrayVerbose
+  ).toBe(undefined)
 })
 
+const callingNonFunctionValueObject = stripIndent`
+({a: 1})();
+`
+
+const callingNonFunctionValueObjectVerbose = stripIndent`
+"enable verbose";
+({a: 1})();
+`
+
 test('Error when calling non function value object', () => {
-  return expectParsedError(
-    stripIndent`
-    ({a: 1})();
-  `,
-    { chapter: 100 }
-  ).toMatchInlineSnapshot(`"Line 1: Calling non-function value {\\"a\\": 1}"`)
+  return expectParsedError(callingNonFunctionValueObject, { chapter: 100 }).toMatchInlineSnapshot(
+    `"Line 1: Calling non-function value {\\"a\\": 1}"`
+  )
+})
+
+test('Error when calling non function value object - verbose', () => {
+  return expectParsedError(callingNonFunctionValueObjectVerbose, { chapter: 100 })
+    .toMatchInlineSnapshot(`
+"Line 2, Column 0: Calling non-function value {\\"a\\": 1}
+TODO
+"
+`)
+})
+
+test('Calling non function value object error message differs from verbose version', () => {
+  return expectDifferentParsedErrors(
+    callingNonFunctionValueObject,
+    callingNonFunctionValueObjectVerbose
+  ).toBe(undefined)
 })
 
 test('Error when calling function with too few arguments', () => {
diff --git a/src/utils/testing.ts b/src/utils/testing.ts
index 1eb9b3e7..77302435 100644
--- a/src/utils/testing.ts
+++ b/src/utils/testing.ts
@@ -237,6 +237,22 @@ export function expectParsedError(code: string, options: TestOptions = {}) {
   ).resolves
 }
 
+export function expectDifferentParsedErrors(
+  code1: string,
+  code2: string,
+  options: TestOptions = {}
+) {
+  return expect(
+    testFailure(code1, options).then(error1 => {
+      expect(
+        testFailure(code2, options).then(error2 => {
+          return expect(error1).not.toEqual(error2)
+        })
+      )
+    })
+  ).resolves
+}
+
 export function expectWarning(code: string, options: TestOptions = {}) {
   return expect(
     testSuccessWithErrors(code, options)