Repository: source-academy/js-slang
Pull request #471 (2020-03-28 14:53:41)

Reimplementation of Scope Navigation T3
This PR removes T3's implementation of navigation to declaration and rebases the refactoring implementation on top of T2's navigation to declaration.

It removes unused code and updates the tests as well.

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

* **26** of **29**   **(89.66%)**  changed or added relevant lines in **3** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.1%**) to **88.872%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/finder.ts](https://coveralls.io/builds/29679803/source?filename=src%2Ffinder.ts#L108) | 13 | 14 | 92.86%
| [src/index.ts](https://coveralls.io/builds/29679803/source?filename=src%2Findex.ts#L183) | 10 | 12 | 83.33%
<!-- | **Total:** | **26** | **29** | **89.66%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/29679803/badge)](https://coveralls.io/builds/29679803) |
| :-- | --: |
| Change from base [Build 2355](https://coveralls.io/builds/29679356): |  0.1% |
| Covered Lines: | 2513 |
| Relevant Lines: | 2743 |

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


Patch:
diff --git a/src/finder.ts b/src/finder.ts
index bc568da6..ee561c6c 100644
--- a/src/finder.ts
+++ b/src/finder.ts
@@ -6,6 +6,7 @@ import {
   FunctionDeclaration,
   Identifier,
   Node,
+  SourceLocation,
   VariableDeclarator
 } from 'estree'
 import { Context } from './types'
@@ -89,21 +90,41 @@ export function findDeclarationNode(program: Node, identifier: Identifier): Node
   return undefined
 }
 
-function containsNode(nodeOuter: Node, nodeInner: Node) {
+function containsNode(nodeOuter: Node, nodeInner: Node): boolean {
   const outerLoc = nodeOuter.loc
   const innerLoc = nodeInner.loc
+
   return (
-    outerLoc &&
-    innerLoc &&
-    ((outerLoc.start.line === innerLoc.start.line &&
-      outerLoc.end.line === innerLoc.end.line &&
-      outerLoc.start.column <= innerLoc.start.column &&
-      outerLoc.end.column >= innerLoc.end.column) ||
-      (outerLoc.start.line < innerLoc.start.line && outerLoc.end.line >= innerLoc.end.line) ||
-      (outerLoc.start.line <= innerLoc.start.line && outerLoc.end.line > innerLoc.end.line))
+    outerLoc != null &&
+    innerLoc != null &&
+    isInLoc(innerLoc.start.line, innerLoc.start.column, outerLoc) &&
+    isInLoc(innerLoc.end.line, innerLoc.end.column, outerLoc)
   )
 }
 
+// This checks if a given (line, col) value is part of another node.
+export function isInLoc(line: number, col: number, location: SourceLocation): boolean {
+  if (location == null) {
+    return false
+  }
+
+  if (location.start.line < line && location.end.line > line) {
+    return true
+  } else if (location.start.line === line && location.end.line > line) {
+    return location.start.column <= col
+  } else if (location.start.line < line && location.end.line === line) {
+    return location.end.column >= col
+  } else if (location.start.line === line && location.end.line === line) {
+    if (location.start.column <= col && location.end.column >= col) {
+      return true
+    } else {
+      return false
+    }
+  } else {
+    return false
+  }
+}
+
 function findAncestors(root: Node, identifier: Identifier): Node[] | undefined {
   let foundAncestors: Node[] = []
   ancestor(root, {
diff --git a/src/index.ts b/src/index.ts
index 5a7c7abd..96949519 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -14,7 +14,7 @@ import { findDeclarationNode, findIdentifierNode } from './finder'
 import { evaluate } from './interpreter/interpreter'
 import { parse, parseAt } from './parser/parser'
 import { AsyncScheduler, PreemptiveScheduler } from './schedulers'
-import { getAllOccurrencesInScope, lookupDefinition, scopeVariables } from './scoped-vars'
+import { getAllOccurrencesInScopeHelper } from './scope-refactoring'
 import { areBreakpointsSet, setBreakpointAtLine } from './stdlib/inspector'
 import { getEvaluationSteps } from './stepper/stepper'
 import { sandboxedEval } from './transpiler/evalContainer'
@@ -173,6 +173,26 @@ export function findDeclaration(
   return declarationNode.loc
 }
 
+export function getAllOccurrencesInScope(
+  code: string,
+  context: Context,
+  loc: { line: number; column: number }
+): SourceLocation[] {
+  const program = parse(code, context, true)
+  if (!program) {
+    return []
+  }
+  const identifierNode = findIdentifierNode(program, context, loc)
+  if (!identifierNode) {
+    return []
+  }
+  const declarationNode = findDeclarationNode(program, identifierNode)
+  if (declarationNode == null || declarationNode.loc == null) {
+    return []
+  }
+  return getAllOccurrencesInScopeHelper(declarationNode.loc, program, identifierNode.name)
+}
+
 export async function runInContext(
   code: string,
   context: Context,
@@ -300,12 +320,4 @@ export function interrupt(context: Context) {
   context.errors.push(new InterruptedError(context.runtime.nodes[0]))
 }
 
-export {
-  createContext,
-  Context,
-  Result,
-  setBreakpointAtLine,
-  scopeVariables,
-  lookupDefinition,
-  getAllOccurrencesInScope
-}
+export { createContext, Context, Result, setBreakpointAtLine }
diff --git a/src/scoped-vars.ts b/src/scope-refactoring.ts
similarity index 78%
rename from src/scoped-vars.ts
rename to src/scope-refactoring.ts
index 1835f2a7..547886a8 100644
--- a/src/scoped-vars.ts
+++ b/src/scope-refactoring.ts
@@ -1,5 +1,6 @@
 import { simple } from 'acorn-walk/dist/walk'
 import * as es from 'estree'
+import { isInLoc } from './finder'
 import { BlockFrame, DefinitionNode } from './types'
 
 export function scopeVariables(
@@ -26,7 +27,6 @@ export function scopeVariables(
   const forStatements = getForStatements(program.body)
   const ifStatements = getIfStatements(program.body)
   const whileStatements = getWhileStatements(program.body)
-  const assignmentStatements = getAssignmentStatements(program.body)
   const variableStatements = definitionStatements.filter(statement =>
     isVariableDeclaration(statement)
   ) as es.VariableDeclaration[]
@@ -50,9 +50,6 @@ export function scopeVariables(
   const functionDefinitionNodes = functionDeclarations.map(declaration => declaration.definition)
   const functionBodyNodes = functionDeclarations.map(declaration => declaration.body)
 
-  const variableAssignmentNodes = assignmentStatements.map(statement =>
-    scopeAssignmentStatement(statement)
-  )
   const variableDefinitionNodes = variableStatements.map(statement =>
     scopeVariableDeclaration(statement)
   )
@@ -77,7 +74,6 @@ export function scopeVariables(
   block.children = [
     ...variableDefinitionNodes,
     ...functionDefinitionNodes,
-    ...variableAssignmentNodes,
     ...functionBodyNodes,
     ...arrowFunctionNodes,
     ...ifStatementNodes,
@@ -126,17 +122,6 @@ export function scopeFunctionDeclaration(
   return { definition, body }
 }
 
-function scopeAssignmentStatement(node: es.ExpressionStatement): DefinitionNode {
-  return {
-    name: ((node.expression as es.AssignmentExpression).left as es.Identifier).name,
-    type: 'DefinitionNode',
-    // assignmentStatements are usually found when assigning new values to let variables
-    // This node represents the latest value of the current variable
-    isDeclaration: false,
-    loc: ((node.expression as es.AssignmentExpression).left as es.Identifier).loc
-  }
-}
-
 function scopeArrowFunction(node: es.ArrowFunctionExpression): BlockFrame {
   const params = node.params.map(param => ({
     name: (param as es.Identifier).name,
@@ -200,72 +185,17 @@ function scopeForStatement(node: es.ForStatement): BlockFrame {
   return block
 }
 
-// Functions to lookup definition location of any variable
-export function lookupDefinition(
-  variableName: string,
-  line: number,
-  col: number,
-  node: BlockFrame,
-  currentDefinition?: DefinitionNode,
-  // This determines whether the function looks up where the variable is declared
-  // vs where it was assigned its latest value
-  lookupOriginalDeclaration: boolean = false
-): DefinitionNode | void {
-  if (!isInLoc(line, col, node.enclosingLoc as es.SourceLocation)) {
-    return undefined
-  }
-
-  const matches = (node.children.filter(child => !isBlockFrame(child)) as DefinitionNode[])
-    .filter(child => child.name === variableName)
-    // If the lookupOriginalDeclaration is true, we only search for the original declaration
-    // of the variable. Redefinitions of let variables are hence excluded
-    .filter(child => (lookupOriginalDeclaration ? child.isDeclaration : true))
-    // Only get definitions before the current cursor location
-    .filter(child =>
-      child.loc
-        ? child.loc.start.line < line ||
-          (child.loc.start.line === line && child.loc.start.column <= col)
-        : false
-    )
-  // Grab the latest definition
-  currentDefinition = matches.length > 0 ? matches[matches.length - 1] : currentDefinition
-  const blockToRecurse = node.children
-    .filter(isBlockFrame)
-    .filter(block => isInLoc(line, col, block.enclosingLoc as es.SourceLocation))
-
-  if (blockToRecurse.length > 1) {
-    throw new Error('Variable is part of more than one child block. This should never happen.')
-  }
-
-  return blockToRecurse.length === 1
-    ? lookupDefinition(
-        variableName,
-        line,
-        col,
-        blockToRecurse[0],
-        currentDefinition,
-        lookupOriginalDeclaration
-      )
-    : currentDefinition
-}
-
 // This function works by first searching for closest declaration of that variable in the parent scopes
 // Then, using the block where the node is found as the root, recurse on the children and get all
 // usages of the variable there
-export function getAllOccurrencesInScope(
-  target: string,
-  line: number,
-  col: number,
-  program: es.Program
+export function getAllOccurrencesInScopeHelper(
+  definitionLocation: es.SourceLocation,
+  program: es.Program,
+  target: string
 ): es.SourceLocation[] {
   const lookupTree = scopeVariables(program)
   // Find closest declaration of node.
-  const defNode = lookupDefinition(target, line, col, lookupTree, undefined, true)
-  if (defNode == null || defNode.loc == null) {
-    return []
-  }
-  const defLoc = defNode.loc
-  const block = getBlockFromLoc(defLoc, lookupTree)
+  const block = getBlockFromLoc(definitionLocation, lookupTree)
   const identifiers = getAllIdentifiers(program, target)
   // Recurse on teh children
   const nestedBlocks = block.children.filter(isBlockFrame)
@@ -368,16 +298,6 @@ function getDeclarationStatements(
   )
 }
 
-function getAssignmentStatements(
-  nodes: (es.Statement | es.ModuleDeclaration)[]
-): es.ExpressionStatement[] {
-  return nodes.filter(
-    statement =>
-      statement.type === 'ExpressionStatement' &&
-      statement.expression.type === 'AssignmentExpression'
-  ) as es.ExpressionStatement[]
-}
-
 function getIfStatements(nodes: (es.Statement | es.ModuleDeclaration)[]): es.IfStatement[] {
   return nodes.filter(statement => statement.type === 'IfStatement') as es.IfStatement[]
 }
@@ -429,29 +349,6 @@ function sortByLoc(x: DefinitionNode | BlockFrame, y: DefinitionNode | BlockFram
   }
 }
 
-// This checks if a given (line, col) value is part of another node.
-function isInLoc(line: number, col: number, location: es.SourceLocation): boolean {
-  if (location == null) {
-    return false
-  }
-
-  if (location.start.line < line && location.end.line > line) {
-    return true
-  } else if (location.start.line === line && location.end.line > line) {
-    return location.start.column <= col
-  } else if (location.start.line < line && location.end.line === line) {
-    return location.end.column >= col
-  } else if (location.start.line === line && location.end.line === line) {
-    if (location.start.column <= col && location.end.column >= col) {
-      return true
-    } else {
-      return false
-    }
-  } else {
-    return false
-  }
-}
-
 // This checks if a node is part of another node via its loc/enclosingLoc property.
 function isPartOf(curr: es.SourceLocation, enclosing: es.SourceLocation): boolean {
   return (


Test patch:
diff --git a/src/__tests__/__snapshots__/scoped-vars.ts.snap b/src/__tests__/__snapshots__/scope-refactoring.ts.snap
similarity index 63%
rename from src/__tests__/__snapshots__/scoped-vars.ts.snap
rename to src/__tests__/__snapshots__/scope-refactoring.ts.snap
index 5513661f..b8433323 100644
--- a/src/__tests__/__snapshots__/scoped-vars.ts.snap
+++ b/src/__tests__/__snapshots__/scope-refactoring.ts.snap
@@ -1,216 +1,5 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`Going to definitions of ordinary variable definitions 1`] = `
-Array [
-  Object {
-    "code": "let target = 1;
-target = 2;
-{
-const target = 3;
-target + 4;
-}",
-    "label": "Test 1",
-    "target": Target {
-      "col": 3,
-      "name": "target",
-      "row": 5,
-    },
-    "value": Object {
-      "isDeclaration": true,
-      "loc": SourceLocation {
-        "end": Position {
-          "column": 12,
-          "line": 4,
-        },
-        "start": Position {
-          "column": 6,
-          "line": 4,
-        },
-      },
-      "name": "target",
-      "type": "DefinitionNode",
-    },
-  },
-  Object {
-    "code": "let target = 1;
-target = 2;
-target + 99;",
-    "label": "Test 2",
-    "target": Target {
-      "col": 3,
-      "name": "target",
-      "row": 3,
-    },
-    "value": Object {
-      "isDeclaration": false,
-      "loc": SourceLocation {
-        "end": Position {
-          "column": 6,
-          "line": 2,
-        },
-        "start": Position {
-          "column": 0,
-          "line": 2,
-        },
-      },
-      "name": "target",
-      "type": "DefinitionNode",
-    },
-  },
-  Object {
-    "code": "let target = 1;
-target = 2;
-target + 99;",
-    "label": "Test 2",
-    "target": Target {
-      "col": 3,
-      "name": "MISSING",
-      "row": 3,
-    },
-    "value": undefined,
-  },
-]
-`;
-
-exports[`Going to definitions of with conditionals and loops 1`] = `
-Array [
-  Object {
-    "code": "let target = 1;
-if (target < 2) {
-target = 2 + 3;
-} else if (target > 2) {
-target = \\"nooooooo\\";
-} else {
-target = 99999;
-}",
-    "label": "Test 1",
-    "target": Target {
-      "col": 7,
-      "name": "target",
-      "row": 5,
-    },
-    "value": Object {
-      "isDeclaration": false,
-      "loc": SourceLocation {
-        "end": Position {
-          "column": 6,
-          "line": 5,
-        },
-        "start": Position {
-          "column": 0,
-          "line": 5,
-        },
-      },
-      "name": "target",
-      "type": "DefinitionNode",
-    },
-  },
-]
-`;
-
-exports[`Going to definitions of with function scopes 1`] = `
-Array [
-  Object {
-    "code": "let target = 1;
-target = 2;
-function test(target) {
-const x = 3;
-target + 4;
-function y() {
-target * 99;
-}
-}",
-    "label": "Test 1",
-    "target": Target {
-      "col": 7,
-      "name": "target",
-      "row": 7,
-    },
-    "value": Object {
-      "isDeclaration": true,
-      "loc": SourceLocation {
-        "end": Position {
-          "column": 20,
-          "line": 3,
-        },
-        "start": Position {
-          "column": 14,
-          "line": 3,
-        },
-      },
-      "name": "target",
-      "type": "DefinitionNode",
-    },
-  },
-  Object {
-    "code": "let target = 1;
-target = 2;
-function test(target) {
-const x = 3;
-target + 4;
-function y() {
-target * 99;
-}
-}",
-    "label": "Test 1",
-    "target": Target {
-      "col": 6,
-      "name": "target",
-      "row": 5,
-    },
-    "value": Object {
-      "isDeclaration": true,
-      "loc": SourceLocation {
-        "end": Position {
-          "column": 20,
-          "line": 3,
-        },
-        "start": Position {
-          "column": 14,
-          "line": 3,
-        },
-      },
-      "name": "target",
-      "type": "DefinitionNode",
-    },
-  },
-]
-`;
-
-exports[`Going to definitions with arrow function scopes 1`] = `
-Array [
-  Object {
-    "code": "let target = 1;
-function test(target) {
-const arrowFn = target => target + 1;
-const x = target * 777777;
-const nestedArrowFn = (y) => (target) => 1 + target;
-}",
-    "label": "Test 1",
-    "target": Target {
-      "col": 53,
-      "name": "target",
-      "row": 5,
-    },
-    "value": Object {
-      "isDeclaration": true,
-      "loc": SourceLocation {
-        "end": Position {
-          "column": 20,
-          "line": 2,
-        },
-        "start": Position {
-          "column": 14,
-          "line": 2,
-        },
-      },
-      "name": "target",
-      "type": "DefinitionNode",
-    },
-  },
-]
-`;
-
 exports[`Scoped based refactoring of ordinary variable definitions 1`] = `
 Array [
   Object {
@@ -222,9 +11,9 @@ target + 4;
 }",
     "label": "Test 1",
     "target": Target {
-      "col": 3,
+      "column": 3,
+      "line": 5,
       "name": "target",
-      "row": 5,
     },
     "value": Array [
       SourceLocation {
@@ -255,9 +44,9 @@ target = 2;
 target + 99;",
     "label": "Test 2",
     "target": Target {
-      "col": 3,
+      "column": 3,
+      "line": 3,
       "name": "target",
-      "row": 3,
     },
     "value": Array [
       SourceLocation {
@@ -292,18 +81,6 @@ target + 99;",
       },
     ],
   },
-  Object {
-    "code": "let target = 1;
-target = 2;
-target + 99;",
-    "label": "Test 2",
-    "target": Target {
-      "col": 3,
-      "name": "MISSING",
-      "row": 3,
-    },
-    "value": Array [],
-  },
 ]
 `;
 
@@ -318,29 +95,29 @@ const nestedArrowFn = (y) => (target) => 1 + target;
 }",
     "label": "Test 1",
     "target": Target {
-      "col": 53,
+      "column": 48,
+      "line": 5,
       "name": "target",
-      "row": 5,
     },
     "value": Array [
       SourceLocation {
         "end": Position {
-          "column": 20,
-          "line": 2,
+          "column": 36,
+          "line": 5,
         },
         "start": Position {
-          "column": 14,
-          "line": 2,
+          "column": 30,
+          "line": 5,
         },
       },
       SourceLocation {
         "end": Position {
-          "column": 16,
-          "line": 4,
+          "column": 51,
+          "line": 5,
         },
         "start": Position {
-          "column": 10,
-          "line": 4,
+          "column": 45,
+          "line": 5,
         },
       },
     ],
@@ -361,9 +138,9 @@ target = 99999;
 }",
     "label": "Test 1",
     "target": Target {
-      "col": 7,
+      "column": 2,
+      "line": 5,
       "name": "target",
-      "row": 5,
     },
     "value": Array [
       SourceLocation {
@@ -428,6 +205,111 @@ target = 99999;
       },
     ],
   },
+  Object {
+    "code": "let target = 2;
+for (let i = 0; i < 10; i++) {
+target += 99;
+}",
+    "label": "Test 2",
+    "target": Target {
+      "column": 5,
+      "line": 3,
+      "name": "target",
+    },
+    "value": Array [
+      SourceLocation {
+        "end": Position {
+          "column": 10,
+          "line": 1,
+        },
+        "start": Position {
+          "column": 4,
+          "line": 1,
+        },
+      },
+      SourceLocation {
+        "end": Position {
+          "column": 6,
+          "line": 3,
+        },
+        "start": Position {
+          "column": 0,
+          "line": 3,
+        },
+      },
+    ],
+  },
+  Object {
+    "code": "let target = 2;
+for (let i = 0; i < 10; i++) {
+target += 99;
+}",
+    "label": "Test 2",
+    "target": Target {
+      "column": 11,
+      "line": 2,
+      "name": "target",
+    },
+    "value": Array [],
+  },
+  Object {
+    "code": "let target = 2;
+for (let i = 0; i < 10; i++) {
+target += 99;
+}",
+    "label": "Test 2",
+    "target": Target {
+      "column": 2,
+      "line": 5,
+      "name": "i",
+    },
+    "value": Array [],
+  },
+  Object {
+    "code": "let target = 2;
+while (target > 0) {
+const x = \\"i am coronavirus\\";
+target -= 0.1;
+}",
+    "label": "Test 3",
+    "target": Target {
+      "column": 4,
+      "line": 4,
+      "name": "target",
+    },
+    "value": Array [
+      SourceLocation {
+        "end": Position {
+          "column": 10,
+          "line": 1,
+        },
+        "start": Position {
+          "column": 4,
+          "line": 1,
+        },
+      },
+      SourceLocation {
+        "end": Position {
+          "column": 13,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 7,
+          "line": 2,
+        },
+      },
+      SourceLocation {
+        "end": Position {
+          "column": 6,
+          "line": 4,
+        },
+        "start": Position {
+          "column": 0,
+          "line": 4,
+        },
+      },
+    ],
+  },
 ]
 `;
 
@@ -445,9 +327,9 @@ target * 99;
 }",
     "label": "Test 1",
     "target": Target {
-      "col": 7,
+      "column": 3,
+      "line": 7,
       "name": "target",
-      "row": 7,
     },
     "value": Array [
       SourceLocation {
@@ -494,39 +376,29 @@ target * 99;
 }",
     "label": "Test 1",
     "target": Target {
-      "col": 6,
+      "column": 6,
+      "line": 1,
       "name": "target",
-      "row": 5,
     },
     "value": Array [
       SourceLocation {
         "end": Position {
-          "column": 20,
-          "line": 3,
-        },
-        "start": Position {
-          "column": 14,
-          "line": 3,
-        },
-      },
-      SourceLocation {
-        "end": Position {
-          "column": 6,
-          "line": 5,
+          "column": 10,
+          "line": 1,
         },
         "start": Position {
-          "column": 0,
-          "line": 5,
+          "column": 4,
+          "line": 1,
         },
       },
       SourceLocation {
         "end": Position {
           "column": 6,
-          "line": 7,
+          "line": 2,
         },
         "start": Position {
           "column": 0,
-          "line": 7,
+          "line": 2,
         },
       },
     ],
diff --git a/src/__tests__/scope-refactoring.ts b/src/__tests__/scope-refactoring.ts
new file mode 100644
index 00000000..10120441
--- /dev/null
+++ b/src/__tests__/scope-refactoring.ts
@@ -0,0 +1,217 @@
+import { default as createContext } from '../createContext'
+import { getAllOccurrencesInScope } from '../index'
+/* tslint:disable:max-classes-per-file */
+
+const context = createContext(4)
+
+class Target {
+  public name: string
+  public line: number
+  public column: number
+
+  constructor(name: string, line: number, column: number) {
+    this.name = name
+    this.line = line
+    this.column = column
+  }
+}
+
+class CodeTest {
+  public label: string
+  public _code: string
+  public targets: Target[]
+  public get code(): string {
+    return this._code
+      .trim()
+      .split('\n')
+      .map(line => line.trim())
+      .join('\n')
+  }
+  public testAndAdd: (testFn: (code: string) => void) => void
+
+  constructor(label: string, code: string, targets: Target[]) {
+    this.label = label
+    this._code = code
+    this.targets = targets
+  }
+}
+
+// Makes a more readable debuggable result
+function result(testCase: CodeTest, target: Target, value: any) {
+  return {
+    label: testCase.label,
+    code: testCase.code,
+    target,
+    value
+  }
+}
+
+const variableDefinitionTests = [
+  new CodeTest(
+    'Test 1',
+    `
+      let target = 1;
+      target = 2;
+      {
+        const target = 3;
+        target + 4;
+      }
+    `,
+    [new Target('target', 5, 3)]
+  ),
+  new CodeTest(
+    'Test 2',
+    `
+      let target = 1;
+      target = 2;
+      target + 99;
+    `,
+    [new Target('target', 3, 3)]
+  )
+]
+
+const functionScopeTests = [
+  new CodeTest(
+    'Test 1',
+    `let target = 1;
+      target = 2;
+      function test(target) {
+        const x = 3;
+        target + 4;
+        function y() {
+          target * 99;
+        }
+      }
+    `,
+    [new Target('target', 7, 3), new Target('target', 1, 6)]
+  )
+]
+
+const arrowFunctionScopeTests = [
+  new CodeTest(
+    'Test 1',
+    `
+      let target = 1;
+      function test(target) {
+        const arrowFn = target => target + 1;
+        const x = target * 777777;
+        const nestedArrowFn = (y) => (target) => 1 + target;
+      }
+    `,
+    [new Target('target', 5, 48)]
+  )
+]
+
+const conditionalsLoopsTests = [
+  new CodeTest(
+    'Test 1',
+    `
+      let target = 1;
+      if (target < 2) {
+        target = 2 + 3;
+      } else if (target > 2) {
+        target = "nooooooo";
+      } else {
+        target = 99999;
+      }
+    `,
+    [new Target('target', 5, 2)]
+  ),
+  new CodeTest(
+    'Test 2',
+    `
+      let target = 2;
+      for (let i = 0; i < 10; i++) {
+        target += 99;
+      }
+    `,
+    [new Target('target', 3, 5), new Target('target', 2, 11), new Target('i', 5, 2)]
+  ),
+  new CodeTest(
+    'Test 3',
+    `
+      let target = 2;
+      while (target > 0) {
+        const x = "i am coronavirus";
+        target -= 0.1;
+      }
+    `,
+    [new Target('target', 4, 4)]
+  )
+]
+
+test('Scoped based refactoring of ordinary variable definitions', () => {
+  const actuals: any = []
+  variableDefinitionTests.forEach(testCase => {
+    testCase.targets.forEach(target => {
+      actuals.push(
+        result(
+          testCase,
+          target,
+          getAllOccurrencesInScope(testCase.code, context, {
+            line: target.line,
+            column: target.column
+          })
+        )
+      )
+    })
+  })
+  expect(actuals).toMatchSnapshot()
+})
+
+test('Scoped based refactoring with function scopes', () => {
+  const actuals: any = []
+  functionScopeTests.forEach(testCase => {
+    testCase.targets.forEach(target => {
+      actuals.push(
+        result(
+          testCase,
+          target,
+          getAllOccurrencesInScope(testCase.code, context, {
+            line: target.line,
+            column: target.column
+          })
+        )
+      )
+    })
+  })
+  expect(actuals).toMatchSnapshot()
+})
+
+test('Scoped based refactoring with arrow function scopes', () => {
+  const actuals: any = []
+  arrowFunctionScopeTests.forEach(testCase => {
+    testCase.targets.forEach(target => {
+      actuals.push(
+        result(
+          testCase,
+          target,
+          getAllOccurrencesInScope(testCase.code, context, {
+            line: target.line,
+            column: target.column
+          })
+        )
+      )
+    })
+  })
+  expect(actuals).toMatchSnapshot()
+})
+
+test('Scoped based refactoring with conditionals and loops', () => {
+  const actuals: any = []
+  conditionalsLoopsTests.forEach(testCase => {
+    testCase.targets.forEach(target => {
+      actuals.push(
+        result(
+          testCase,
+          target,
+          getAllOccurrencesInScope(testCase.code, context, {
+            line: target.line,
+            column: target.column
+          })
+        )
+      )
+    })
+  })
+  expect(actuals).toMatchSnapshot()
+})
diff --git a/src/__tests__/scoped-vars.ts b/src/__tests__/scoped-vars.ts
deleted file mode 100644
index 7080f930..00000000
--- a/src/__tests__/scoped-vars.ts
+++ /dev/null
@@ -1,306 +0,0 @@
-import * as es from 'estree'
-import { default as createContext } from '../createContext'
-import { looseParse, parse } from '../parser/parser'
-import { getAllOccurrencesInScope, lookupDefinition, scopeVariables } from '../scoped-vars'
-/* tslint:disable:max-classes-per-file */
-
-/**
- * Helper function to get the parsed estree program to test the function
- */
-function testParseHelper(
-  code: string,
-  // default to 4
-  chapter: number = 4,
-  useLooseParser: boolean = false
-): es.Program | undefined {
-  if (useLooseParser) {
-    return looseParse(code, createContext(chapter))
-  } else {
-    return parse(code, createContext(chapter))
-  }
-}
-
-class Target {
-  public name: string
-  public row: number
-  public col: number
-
-  constructor(name: string, row: number, col: number) {
-    this.name = name
-    this.row = row
-    this.col = col
-  }
-}
-
-class CodeTest {
-  public label: string
-  public _code: string
-  public targets: Target[]
-  public get code(): string {
-    return this._code
-      .trim()
-      .split('\n')
-      .map(line => line.trim())
-      .join('\n')
-  }
-  public testAndAdd: (testFn: (code: string) => void) => void
-
-  constructor(label: string, code: string, targets: Target[]) {
-    this.label = label
-    this._code = code
-    this.targets = targets
-  }
-}
-
-// Makes a more readable debuggable result
-function result(testCase: CodeTest, target: Target, value: any) {
-  return {
-    label: testCase.label,
-    code: testCase.code,
-    target,
-    value
-  }
-}
-
-const variableDefinitionTests = [
-  new CodeTest(
-    'Test 1',
-    `
-      let target = 1;
-      target = 2;
-      {
-        const target = 3;
-        target + 4;
-      }
-    `,
-    [new Target('target', 5, 3)]
-  ),
-  new CodeTest(
-    'Test 2',
-    `
-      let target = 1;
-      target = 2;
-      target + 99;
-    `,
-    [new Target('target', 3, 3), new Target('MISSING', 3, 3)]
-  )
-]
-
-const functionScopeTests = [
-  new CodeTest(
-    'Test 1',
-    `
-      let target = 1;
-      target = 2;
-      function test(target) {
-        const x = 3;
-        target + 4;
-        function y() {
-          target * 99;
-        }
-      }
-    `,
-    [new Target('target', 7, 7), new Target('target', 5, 6)]
-  )
-]
-
-const arrowFunctionScopeTests = [
-  new CodeTest(
-    'Test 1',
-    `
-      let target = 1;
-      function test(target) {
-        const arrowFn = target => target + 1;
-        const x = target * 777777;
-        const nestedArrowFn = (y) => (target) => 1 + target;
-      }
-    `,
-    [new Target('target', 5, 53)]
-  )
-]
-
-const conditionalsLoopsTests = [
-  new CodeTest(
-    'Test 1',
-    `
-      let target = 1;
-      if (target < 2) {
-        target = 2 + 3;
-      } else if (target > 2) {
-        target = "nooooooo";
-      } else {
-        target = 99999;
-      }
-    `,
-    [new Target('target', 5, 7)]
-  ),
-  new CodeTest(
-    'Test 2',
-    `
-      let target = 2;
-      for (let i = 0; i < 10; i++) {
-        target += 99;
-      }
-    `,
-    [new Target('target', 3, 5), new Target('target', 2, 13), new Target('i', 5, 2)]
-  ),
-  new CodeTest(
-    'Test 3',
-    `
-      let target = 2;
-      while (target > 0) {
-        const x = "i am coronavirus";
-        target -= 0.1;
-      }
-    `,
-    [new Target('target', 4, 4)]
-  )
-]
-
-test('Going to definitions of ordinary variable definitions', () => {
-  const actuals: any = []
-  variableDefinitionTests.forEach(testCase => {
-    const program = testParseHelper(testCase.code)
-    if (program === undefined) {
-      return
-    }
-    const scopedTree = scopeVariables(program)
-    testCase.targets.forEach(target => {
-      actuals.push(
-        result(testCase, target, lookupDefinition(target.name, target.row, target.col, scopedTree))
-      )
-    })
-  })
-  expect(actuals).toMatchSnapshot()
-})
-
-test('Scoped based refactoring of ordinary variable definitions', () => {
-  const actuals: any = []
-  variableDefinitionTests.forEach(testCase => {
-    const scopedTree = testParseHelper(testCase.code)
-    if (scopedTree === undefined) {
-      return
-    }
-    testCase.targets.forEach(target => {
-      actuals.push(
-        result(
-          testCase,
-          target,
-          getAllOccurrencesInScope(target.name, target.row, target.col, scopedTree)
-        )
-      )
-    })
-  })
-  expect(actuals).toMatchSnapshot()
-})
-
-test('Going to definitions of with function scopes', () => {
-  const actuals: any = []
-  functionScopeTests.forEach(testCase => {
-    const program = testParseHelper(testCase.code)
-    if (program === undefined) {
-      return
-    }
-    const scopedTree = scopeVariables(program)
-    testCase.targets.forEach(target => {
-      actuals.push(
-        result(testCase, target, lookupDefinition(target.name, target.row, target.col, scopedTree))
-      )
-    })
-  })
-  expect(actuals).toMatchSnapshot()
-})
-
-test('Scoped based refactoring with function scopes', () => {
-  const actuals: any = []
-  functionScopeTests.forEach(testCase => {
-    const scopedTree = testParseHelper(testCase.code)
-    if (scopedTree === undefined) {
-      return
-    }
-    testCase.targets.forEach(target => {
-      actuals.push(
-        result(
-          testCase,
-          target,
-          getAllOccurrencesInScope(target.name, target.row, target.col, scopedTree)
-        )
-      )
-    })
-  })
-  expect(actuals).toMatchSnapshot()
-})
-
-test('Going to definitions with arrow function scopes', () => {
-  const actuals: any = []
-  arrowFunctionScopeTests.forEach(testCase => {
-    const program = testParseHelper(testCase.code)
-    if (program === undefined) {
-      return
-    }
-    const scopedTree = scopeVariables(program)
-    testCase.targets.forEach(target => {
-      actuals.push(
-        result(testCase, target, lookupDefinition(target.name, target.row, target.col, scopedTree))
-      )
-    })
-  })
-  expect(actuals).toMatchSnapshot()
-})
-
-test('Scoped based refactoring with arrow function scopes', () => {
-  const actuals: any = []
-  arrowFunctionScopeTests.forEach(testCase => {
-    const scopedTree = testParseHelper(testCase.code)
-    if (scopedTree === undefined) {
-      return
-    }
-    testCase.targets.forEach(target => {
-      actuals.push(
-        result(
-          testCase,
-          target,
-          getAllOccurrencesInScope(target.name, target.row, target.col, scopedTree)
-        )
-      )
-    })
-  })
-  expect(actuals).toMatchSnapshot()
-})
-
-test('Going to definitions of with conditionals and loops', () => {
-  const actuals: any = []
-  conditionalsLoopsTests.forEach(testCase => {
-    const program = testParseHelper(testCase.code)
-    if (program === undefined) {
-      return
-    }
-    const scopedTree = scopeVariables(program)
-    testCase.targets.forEach(target => {
-      actuals.push(
-        result(testCase, target, lookupDefinition(target.name, target.row, target.col, scopedTree))
-      )
-    })
-  })
-  expect(actuals).toMatchSnapshot()
-})
-
-test('Scoped based refactoring with conditionals and loops', () => {
-  const actuals: any = []
-  conditionalsLoopsTests.forEach(testCase => {
-    const scopedTree = testParseHelper(testCase.code)
-    if (scopedTree === undefined) {
-      return
-    }
-    testCase.targets.forEach(target => {
-      actuals.push(
-        result(
-          testCase,
-          target,
-          getAllOccurrencesInScope(target.name, target.row, target.col, scopedTree)
-        )
-      )
-    })
-  })
-  expect(actuals).toMatchSnapshot()
-})