Repository: source-academy/js-slang
Pull request #480 (2020-03-31 14:02:34)

Fix T3 Scope bug and add more tests and code level documentation
This PR fixes a bug with refactoring when it comes to function names.

It also adds code-level documentation and more tests for the util functions

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

* **10** of **10**   **(100.0%)**  changed or added relevant lines in **1** file are covered.
* **1** unchanged line in **1** file lost coverage.
* Overall coverage increased (+**0.06%**) to **89.375%**

---


|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/index.ts](https://coveralls.io/builds/29741500/source?filename=src%2Findex.ts#L189) | 1 | 84.86% |
<!-- | **Total:** | **1** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/29741500/badge)](https://coveralls.io/builds/29741500) |
| :-- | --: |
| Change from base [Build 2399](https://coveralls.io/builds/29701728): |  0.06% |
| Covered Lines: | 4009 |
| Relevant Lines: | 4348 |

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


Patch:
diff --git a/src/scope-refactoring.ts b/src/scope-refactoring.ts
index 547886a8..b5fb776e 100644
--- a/src/scope-refactoring.ts
+++ b/src/scope-refactoring.ts
@@ -3,6 +3,25 @@ import * as es from 'estree'
 import { isInLoc } from './finder'
 import { BlockFrame, DefinitionNode } from './types'
 
+/**
+ * This file parses the original AST Tree into another tree with a similar structure
+ * This new scope tree is far simplified and contains only two types of nodes
+ * The first is called a DefinitionNode, which represents any form of const, let or function definition
+ * The second is a BlockFrame. For normal blocks, it is everything within the curly braces
+ * However, for conditionals, loops and functions and the like, it includes the related stuff outside of the curly braces
+ * For example, for the arguments in the function definition, it will be part of the function's BlockFrame and not the parents.
+ * BlockFrames can be seen as a rough approximation of scope and is largely based of estree's BlockStatement.
+ */
+
+/**
+ * scopeVariables help to transform the AST tree from acorn into the scope tree.
+ * It returns the new root of the scope tree.
+ * Although it is a long function, the work done is fairly simple.
+ * First, we get the estree nodes that are in the current es.BlockStatement/es.Program
+ * We transform the current Program/BlockStatement into a BlockFrame and all its relevant children into
+ * DefinitionNodes where applicable.
+ * Then recursively call this on the child BlockStatements to generate the tree.
+ */
 export function scopeVariables(
   program: es.Program | es.BlockStatement,
   enclosingLoc?: es.SourceLocation | null
@@ -90,13 +109,20 @@ export function scopeVariableDeclaration(node: es.VariableDeclaration): Definiti
   return {
     name: (node.declarations[0].id as es.Identifier).name,
     type: 'DefinitionNode',
-    isDeclaration: true,
     // Assume that only one variable can be declared per line
     loc: node.declarations[0].id.loc
   }
 }
 
-export function scopeFunctionDeclaration(
+/**
+ * Scoping fuction declarations is a bit unlike the way we deal with the rest of the block scopes.
+ * Normally, if there are any definitions locally scoped to the block, we put those DefinitionNodes
+ * within the block frame. However, for fucntion definition names (not parameters), we do not put them
+ * inside the BlockFrame of the function, as function definition names should be visible in the parent scope
+ * Thus, despite the node's loc property being within the BlockFrame's enclosingLoc, ]
+ * we treat it as if it is not in there.
+ */
+function scopeFunctionDeclaration(
   node: es.FunctionDeclaration
 ): {
   definition: DefinitionNode
@@ -105,13 +131,11 @@ export function scopeFunctionDeclaration(
   const definition = {
     name: (node.id as es.Identifier).name,
     type: 'DefinitionNode',
-    isDeclaration: true,
     loc: (node.id as es.Identifier).loc
   }
   const parameters = node.params.map((param: es.Identifier) => ({
     name: param.name,
     type: 'DefinitionNode',
-    isDeclaration: true,
     loc: param.loc
   }))
   // node.loc refers to the loc of the entire function definition, not just its body
@@ -126,7 +150,6 @@ function scopeArrowFunction(node: es.ArrowFunctionExpression): BlockFrame {
   const params = node.params.map(param => ({
     name: (param as es.Identifier).name,
     type: 'DefinitionNode',
-    isDeclaration: true,
     loc: param.loc
   }))
   // arrowFunctionBodies may not contain curly braces on the RHS of the arrow
@@ -174,7 +197,6 @@ function scopeForStatement(node: es.ForStatement): BlockFrame {
     ? (node.init as es.VariableDeclaration).declarations.map((dec: es.VariableDeclarator) => ({
         type: 'DefinitionNode',
         name: (dec.id as es.Identifier).name,
-        isDeclaration: true,
         loc: (dec.id as es.Identifier).loc
       }))
     : []
@@ -185,9 +207,14 @@ function scopeForStatement(node: es.ForStatement): BlockFrame {
   return block
 }
 
-// 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
+/**
+ * Gets all instances of a variable being used in the child scope.
+ * Given the definition location on the variable, get the BlockFrame it resides in.
+ * Then, run depth first search from that BlockFrame.
+ * This DFS terminates when there are no child blocks within that block,
+ * or if the variable name has been redeclared. This means that is of a different scope
+ * and the function will terminate there.
+ */
 export function getAllOccurrencesInScopeHelper(
   definitionLocation: es.SourceLocation,
   program: es.Program,
@@ -197,7 +224,7 @@ export function getAllOccurrencesInScopeHelper(
   // Find closest declaration of node.
   const block = getBlockFromLoc(definitionLocation, lookupTree)
   const identifiers = getAllIdentifiers(program, target)
-  // Recurse on teh children
+  // Recurse on the children
   const nestedBlocks = block.children.filter(isBlockFrame)
   const occurences = getNodeLocsInCurrentBlockFrame(
     identifiers,
@@ -221,7 +248,6 @@ function getAllOccurencesInChildScopes(
   const definitionNodes = block.children
     .filter(isDefinitionNode)
     .filter(node => node.name === target)
-    .filter(node => node.isDeclaration)
   if (definitionNodes.length !== 0) {
     return []
   }
@@ -240,24 +266,39 @@ function getAllOccurencesInChildScopes(
 }
 
 // Gets the enclosing block of a node.
-function getBlockFromLoc(loc: es.SourceLocation, block: BlockFrame): BlockFrame {
+export function getBlockFromLoc(loc: es.SourceLocation, block: BlockFrame): BlockFrame {
+  let parent: BlockFrame | null = null
   let childBlocks = block.children.filter(isBlockFrame)
   let isPartOfChildBlock = childBlocks.some(node =>
     isPartOf(loc, node.enclosingLoc as es.SourceLocation)
   )
   while (isPartOfChildBlock) {
     // A block containing the loc must necessarily exist by the earlier check
+    parent = block
     block = childBlocks.filter(node => isPartOf(loc, node.enclosingLoc as es.SourceLocation))[0]
     childBlocks = block.children.filter(isBlockFrame)
     isPartOfChildBlock = childBlocks.some(node =>
       isPartOf(loc, node.enclosingLoc as es.SourceLocation)
     )
   }
+  // We check if the parent block contains the target loc.
+  // This deals with the edge case of function definitions, as it is within the enclosing loc of the function
+  // But has scope outside of it, as its definition belongs to the outer scope
+  if (
+    parent != null &&
+    parent.children
+      .filter(isDefinitionNode)
+      .filter(node => notEmpty(node.loc))
+      .filter(node => areLocsEqual(node.loc as es.SourceLocation, loc)).length !== 0
+  ) {
+    return parent
+  }
   return block
 }
 
 // Adapted from src/transpiler.ts L345
-function getAllIdentifiers(program: es.Program, target: string): es.Identifier[] {
+// acorn-walk does not return all identifiers, so this is a workaround
+export function getAllIdentifiers(program: es.Program, target: string): es.Identifier[] {
   const identifiers: es.Identifier[] = []
 
   simple(program, {
@@ -357,8 +398,9 @@ function isPartOf(curr: es.SourceLocation, enclosing: es.SourceLocation): boolea
   )
 }
 
-// Returns all nodes that are not within any nested blocks
-function getNodeLocsInCurrentBlockFrame<E extends es.Node>(
+// Returns all nodes that are not part of any child BlockFrame.
+// ie direct children of the current BlockFrame
+export function getNodeLocsInCurrentBlockFrame<E extends es.Node>(
   nodes: E[],
   currentLoc: es.SourceLocation,
   blocks: BlockFrame[]
@@ -377,7 +419,12 @@ function getNodeLocsInCurrentBlockFrame<E extends es.Node>(
   )
 }
 
-function getBlockFramesInCurrentBlockFrame(
+/**
+ * Returns all BlockFrames that are a direct child of the parent BlockFrame
+ * This helps us weed out the nested BlockFrames, which would affect the accuracy of the
+ * refactoring.
+ */
+export function getBlockFramesInCurrentBlockFrame(
   nodes: BlockFrame[],
   currentLoc: es.SourceLocation,
   blocks: BlockFrame[]
diff --git a/src/types.ts b/src/types.ts
index 9a627b0f..371d9ab7 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -123,7 +123,6 @@ export interface BlockFrame {
 export interface DefinitionNode {
   name: string
   type: string
-  isDeclaration: boolean
   loc?: es.SourceLocation | null
 }
 


Test patch:
diff --git a/src/__tests__/__snapshots__/scope-refactoring.ts.snap b/src/__tests__/__snapshots__/scope-refactoring.ts.snap
index b8433323..a8fb8cfc 100644
--- a/src/__tests__/__snapshots__/scope-refactoring.ts.snap
+++ b/src/__tests__/__snapshots__/scope-refactoring.ts.snap
@@ -246,24 +246,42 @@ target += 99;
 }",
     "label": "Test 2",
     "target": Target {
-      "column": 11,
+      "column": 10,
       "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 [],
+    "value": Array [
+      SourceLocation {
+        "end": Position {
+          "column": 10,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 9,
+          "line": 2,
+        },
+      },
+      SourceLocation {
+        "end": Position {
+          "column": 17,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 16,
+          "line": 2,
+        },
+      },
+      SourceLocation {
+        "end": Position {
+          "column": 25,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 24,
+          "line": 2,
+        },
+      },
+    ],
   },
   Object {
     "code": "let target = 2;
@@ -310,6 +328,74 @@ target -= 0.1;
       },
     ],
   },
+  Object {
+    "code": "let i = \\"dontgetconfusedwith the next one pls\\";
+for (let i = 0; i < 10; i++) {
+target += 99;
+}",
+    "label": "Test 4",
+    "target": Target {
+      "column": 10,
+      "line": 2,
+      "name": "i",
+    },
+    "value": Array [
+      SourceLocation {
+        "end": Position {
+          "column": 10,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 9,
+          "line": 2,
+        },
+      },
+      SourceLocation {
+        "end": Position {
+          "column": 17,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 16,
+          "line": 2,
+        },
+      },
+      SourceLocation {
+        "end": Position {
+          "column": 25,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 24,
+          "line": 2,
+        },
+      },
+    ],
+  },
+  Object {
+    "code": "let i = \\"dontgetconfusedwith the next one pls\\";
+for (let i = 0; i < 10; i++) {
+target += 99;
+}",
+    "label": "Test 4",
+    "target": Target {
+      "column": 5,
+      "line": 1,
+      "name": "i",
+    },
+    "value": Array [
+      SourceLocation {
+        "end": Position {
+          "column": 5,
+          "line": 1,
+        },
+        "start": Position {
+          "column": 4,
+          "line": 1,
+        },
+      },
+    ],
+  },
 ]
 `;
 
@@ -403,5 +489,831 @@ target * 99;
       },
     ],
   },
+  Object {
+    "code": "function parse_and_eval(1) {
+eval_top_level(1);
+}
+function repl(str) {
+const res = parse_and_eval(23);
+}",
+    "label": "Test 1",
+    "target": Target {
+      "column": 13,
+      "line": 1,
+      "name": "parse_and_eval",
+    },
+    "value": Array [
+      SourceLocation {
+        "end": Position {
+          "column": 23,
+          "line": 1,
+        },
+        "start": Position {
+          "column": 9,
+          "line": 1,
+        },
+      },
+      SourceLocation {
+        "end": Position {
+          "column": 26,
+          "line": 5,
+        },
+        "start": Position {
+          "column": 12,
+          "line": 5,
+        },
+      },
+    ],
+  },
 ]
 `;
+
+exports[`getBlockFromLoc with arrow function name 1`] = `
+Object {
+  "children": Array [
+    Object {
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 12,
+          "line": 8,
+        },
+        "start": Position {
+          "column": 8,
+          "line": 8,
+        },
+      },
+      "name": "lmao",
+      "type": "DefinitionNode",
+    },
+    Object {
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 15,
+          "line": 9,
+        },
+        "start": Position {
+          "column": 11,
+          "line": 9,
+        },
+      },
+      "name": "abba",
+      "type": "DefinitionNode",
+    },
+    Object {
+      "children": Array [
+        Object {
+          "loc": SourceLocation {
+            "end": Position {
+              "column": 18,
+              "line": 10,
+            },
+            "start": Position {
+              "column": 10,
+              "line": 10,
+            },
+          },
+          "name": "bestSong",
+          "type": "DefinitionNode",
+        },
+      ],
+      "enclosingLoc": SourceLocation {
+        "end": Position {
+          "column": 3,
+          "line": 12,
+        },
+        "start": Position {
+          "column": 2,
+          "line": 9,
+        },
+      },
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 3,
+          "line": 12,
+        },
+        "start": Position {
+          "column": 18,
+          "line": 9,
+        },
+      },
+      "type": "BlockFrame",
+    },
+  ],
+  "enclosingLoc": SourceLocation {
+    "end": Position {
+      "column": 1,
+      "line": 14,
+    },
+    "start": Position {
+      "column": 0,
+      "line": 7,
+    },
+  },
+  "loc": SourceLocation {
+    "end": Position {
+      "column": 1,
+      "line": 14,
+    },
+    "start": Position {
+      "column": 0,
+      "line": 7,
+    },
+  },
+  "type": "BlockFrame",
+}
+`;
+
+exports[`getBlockFromLoc with function definition name 1`] = `
+Object {
+  "children": Array [
+    Object {
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 12,
+          "line": 8,
+        },
+        "start": Position {
+          "column": 8,
+          "line": 8,
+        },
+      },
+      "name": "lmao",
+      "type": "DefinitionNode",
+    },
+    Object {
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 15,
+          "line": 9,
+        },
+        "start": Position {
+          "column": 11,
+          "line": 9,
+        },
+      },
+      "name": "abba",
+      "type": "DefinitionNode",
+    },
+    Object {
+      "children": Array [
+        Object {
+          "loc": SourceLocation {
+            "end": Position {
+              "column": 18,
+              "line": 10,
+            },
+            "start": Position {
+              "column": 10,
+              "line": 10,
+            },
+          },
+          "name": "bestSong",
+          "type": "DefinitionNode",
+        },
+      ],
+      "enclosingLoc": SourceLocation {
+        "end": Position {
+          "column": 3,
+          "line": 12,
+        },
+        "start": Position {
+          "column": 2,
+          "line": 9,
+        },
+      },
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 3,
+          "line": 12,
+        },
+        "start": Position {
+          "column": 18,
+          "line": 9,
+        },
+      },
+      "type": "BlockFrame",
+    },
+  ],
+  "enclosingLoc": SourceLocation {
+    "end": Position {
+      "column": 1,
+      "line": 14,
+    },
+    "start": Position {
+      "column": 0,
+      "line": 7,
+    },
+  },
+  "loc": SourceLocation {
+    "end": Position {
+      "column": 1,
+      "line": 14,
+    },
+    "start": Position {
+      "column": 0,
+      "line": 7,
+    },
+  },
+  "type": "BlockFrame",
+}
+`;
+
+exports[`getBlockFromLoc with normal variable name 1`] = `
+Object {
+  "children": Array [
+    Object {
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 18,
+          "line": 7,
+        },
+        "start": Position {
+          "column": 10,
+          "line": 7,
+        },
+      },
+      "name": "bestSong",
+      "type": "DefinitionNode",
+    },
+  ],
+  "enclosingLoc": SourceLocation {
+    "end": Position {
+      "column": 3,
+      "line": 9,
+    },
+    "start": Position {
+      "column": 2,
+      "line": 6,
+    },
+  },
+  "loc": SourceLocation {
+    "end": Position {
+      "column": 3,
+      "line": 9,
+    },
+    "start": Position {
+      "column": 18,
+      "line": 6,
+    },
+  },
+  "type": "BlockFrame",
+}
+`;
+
+exports[`scopeVariables should return an accurate scope tree 1`] = `
+Object {
+  "children": Array [
+    Object {
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 16,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 10,
+          "line": 2,
+        },
+      },
+      "name": "anakin",
+      "type": "DefinitionNode",
+    },
+    Object {
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 16,
+          "line": 3,
+        },
+        "start": Position {
+          "column": 10,
+          "line": 3,
+        },
+      },
+      "name": "obiwan",
+      "type": "DefinitionNode",
+    },
+    Object {
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 26,
+          "line": 4,
+        },
+        "start": Position {
+          "column": 13,
+          "line": 4,
+        },
+      },
+      "name": "disneyTrilogy",
+      "type": "DefinitionNode",
+    },
+    Object {
+      "children": Array [
+        Object {
+          "loc": SourceLocation {
+            "end": Position {
+              "column": 31,
+              "line": 5,
+            },
+            "start": Position {
+              "column": 12,
+              "line": 5,
+            },
+          },
+          "name": "isWorseThanPrequels",
+          "type": "DefinitionNode",
+        },
+      ],
+      "enclosingLoc": SourceLocation {
+        "end": Position {
+          "column": 5,
+          "line": 7,
+        },
+        "start": Position {
+          "column": 4,
+          "line": 4,
+        },
+      },
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 5,
+          "line": 7,
+        },
+        "start": Position {
+          "column": 29,
+          "line": 4,
+        },
+      },
+      "type": "BlockFrame",
+    },
+    Object {
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 35,
+          "line": 8,
+        },
+        "start": Position {
+          "column": 13,
+          "line": 8,
+        },
+      },
+      "name": "functionmcfunctionface",
+      "type": "DefinitionNode",
+    },
+    Object {
+      "children": Array [
+        Object {
+          "loc": SourceLocation {
+            "end": Position {
+              "column": 13,
+              "line": 9,
+            },
+            "start": Position {
+              "column": 12,
+              "line": 9,
+            },
+          },
+          "name": "x",
+          "type": "DefinitionNode",
+        },
+        Object {
+          "children": Array [
+            Object {
+              "loc": SourceLocation {
+                "end": Position {
+                  "column": 23,
+                  "line": 9,
+                },
+                "start": Position {
+                  "column": 17,
+                  "line": 9,
+                },
+              },
+              "name": "corona",
+              "type": "DefinitionNode",
+            },
+            Object {
+              "children": Array [
+                Object {
+                  "loc": SourceLocation {
+                    "end": Position {
+                      "column": 34,
+                      "line": 9,
+                    },
+                    "start": Position {
+                      "column": 29,
+                      "line": 9,
+                    },
+                  },
+                  "name": "virus",
+                  "type": "DefinitionNode",
+                },
+              ],
+              "enclosingLoc": SourceLocation {
+                "end": Position {
+                  "column": 53,
+                  "line": 9,
+                },
+                "start": Position {
+                  "column": 28,
+                  "line": 9,
+                },
+              },
+              "loc": SourceLocation {
+                "end": Position {
+                  "column": 53,
+                  "line": 9,
+                },
+                "start": Position {
+                  "column": 39,
+                  "line": 9,
+                },
+              },
+              "type": "BlockFrame",
+            },
+          ],
+          "enclosingLoc": SourceLocation {
+            "end": Position {
+              "column": 53,
+              "line": 9,
+            },
+            "start": Position {
+              "column": 16,
+              "line": 9,
+            },
+          },
+          "loc": SourceLocation {
+            "end": Position {
+              "column": 53,
+              "line": 9,
+            },
+            "start": Position {
+              "column": 28,
+              "line": 9,
+            },
+          },
+          "type": "BlockFrame",
+        },
+        Object {
+          "loc": SourceLocation {
+            "end": Position {
+              "column": 16,
+              "line": 10,
+            },
+            "start": Position {
+              "column": 10,
+              "line": 10,
+            },
+          },
+          "name": "hahaha",
+          "type": "DefinitionNode",
+        },
+      ],
+      "enclosingLoc": SourceLocation {
+        "end": Position {
+          "column": 5,
+          "line": 12,
+        },
+        "start": Position {
+          "column": 4,
+          "line": 8,
+        },
+      },
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 5,
+          "line": 12,
+        },
+        "start": Position {
+          "column": 38,
+          "line": 8,
+        },
+      },
+      "type": "BlockFrame",
+    },
+  ],
+  "enclosingLoc": SourceLocation {
+    "end": Position {
+      "column": 2,
+      "line": 13,
+    },
+    "start": Position {
+      "column": 0,
+      "line": 1,
+    },
+  },
+  "loc": SourceLocation {
+    "end": Position {
+      "column": 2,
+      "line": 13,
+    },
+    "start": Position {
+      "column": 0,
+      "line": 1,
+    },
+  },
+  "type": "BlockFrame",
+}
+`;
+
+exports[`scopeVariables should return an accurate scope tree with normal block scopes 1`] = `
+Object {
+  "children": Array [
+    Object {
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 16,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 10,
+          "line": 2,
+        },
+      },
+      "name": "anakin",
+      "type": "DefinitionNode",
+    },
+    Object {
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 16,
+          "line": 3,
+        },
+        "start": Position {
+          "column": 10,
+          "line": 3,
+        },
+      },
+      "name": "obiwan",
+      "type": "DefinitionNode",
+    },
+    Object {
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 26,
+          "line": 4,
+        },
+        "start": Position {
+          "column": 13,
+          "line": 4,
+        },
+      },
+      "name": "disneyTrilogy",
+      "type": "DefinitionNode",
+    },
+    Object {
+      "children": Array [
+        Object {
+          "loc": SourceLocation {
+            "end": Position {
+              "column": 31,
+              "line": 5,
+            },
+            "start": Position {
+              "column": 12,
+              "line": 5,
+            },
+          },
+          "name": "isWorseThanPrequels",
+          "type": "DefinitionNode",
+        },
+      ],
+      "enclosingLoc": SourceLocation {
+        "end": Position {
+          "column": 5,
+          "line": 7,
+        },
+        "start": Position {
+          "column": 4,
+          "line": 4,
+        },
+      },
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 5,
+          "line": 7,
+        },
+        "start": Position {
+          "column": 29,
+          "line": 4,
+        },
+      },
+      "type": "BlockFrame",
+    },
+    Object {
+      "children": Array [
+        Object {
+          "children": Array [
+            Object {
+              "loc": SourceLocation {
+                "end": Position {
+                  "column": 16,
+                  "line": 9,
+                },
+                "start": Position {
+                  "column": 15,
+                  "line": 9,
+                },
+              },
+              "name": "i",
+              "type": "DefinitionNode",
+            },
+            Object {
+              "loc": SourceLocation {
+                "end": Position {
+                  "column": 15,
+                  "line": 10,
+                },
+                "start": Position {
+                  "column": 14,
+                  "line": 10,
+                },
+              },
+              "name": "x",
+              "type": "DefinitionNode",
+            },
+          ],
+          "enclosingLoc": SourceLocation {
+            "end": Position {
+              "column": 7,
+              "line": 11,
+            },
+            "start": Position {
+              "column": 6,
+              "line": 9,
+            },
+          },
+          "loc": SourceLocation {
+            "end": Position {
+              "column": 7,
+              "line": 11,
+            },
+            "start": Position {
+              "column": 35,
+              "line": 9,
+            },
+          },
+          "type": "BlockFrame",
+        },
+        Object {
+          "children": Array [
+            Object {
+              "loc": SourceLocation {
+                "end": Position {
+                  "column": 15,
+                  "line": 13,
+                },
+                "start": Position {
+                  "column": 14,
+                  "line": 13,
+                },
+              },
+              "name": "y",
+              "type": "DefinitionNode",
+            },
+          ],
+          "enclosingLoc": SourceLocation {
+            "end": Position {
+              "column": 7,
+              "line": 14,
+            },
+            "start": Position {
+              "column": 16,
+              "line": 12,
+            },
+          },
+          "loc": SourceLocation {
+            "end": Position {
+              "column": 7,
+              "line": 14,
+            },
+            "start": Position {
+              "column": 16,
+              "line": 12,
+            },
+          },
+          "type": "BlockFrame",
+        },
+        Object {
+          "children": Array [
+            Object {
+              "loc": SourceLocation {
+                "end": Position {
+                  "column": 15,
+                  "line": 15,
+                },
+                "start": Position {
+                  "column": 14,
+                  "line": 15,
+                },
+              },
+              "name": "t",
+              "type": "DefinitionNode",
+            },
+          ],
+          "enclosingLoc": SourceLocation {
+            "end": Position {
+              "column": 7,
+              "line": 16,
+            },
+            "start": Position {
+              "column": 24,
+              "line": 14,
+            },
+          },
+          "loc": SourceLocation {
+            "end": Position {
+              "column": 7,
+              "line": 16,
+            },
+            "start": Position {
+              "column": 24,
+              "line": 14,
+            },
+          },
+          "type": "BlockFrame",
+        },
+        Object {
+          "children": Array [
+            Object {
+              "loc": SourceLocation {
+                "end": Position {
+                  "column": 23,
+                  "line": 17,
+                },
+                "start": Position {
+                  "column": 14,
+                  "line": 17,
+                },
+              },
+              "name": "jfksdfjgk",
+              "type": "DefinitionNode",
+            },
+          ],
+          "enclosingLoc": SourceLocation {
+            "end": Position {
+              "column": 7,
+              "line": 18,
+            },
+            "start": Position {
+              "column": 13,
+              "line": 16,
+            },
+          },
+          "loc": SourceLocation {
+            "end": Position {
+              "column": 7,
+              "line": 18,
+            },
+            "start": Position {
+              "column": 13,
+              "line": 16,
+            },
+          },
+          "type": "BlockFrame",
+        },
+      ],
+      "enclosingLoc": SourceLocation {
+        "end": Position {
+          "column": 5,
+          "line": 19,
+        },
+        "start": Position {
+          "column": 4,
+          "line": 8,
+        },
+      },
+      "loc": SourceLocation {
+        "end": Position {
+          "column": 5,
+          "line": 19,
+        },
+        "start": Position {
+          "column": 4,
+          "line": 8,
+        },
+      },
+      "type": "BlockFrame",
+    },
+  ],
+  "enclosingLoc": SourceLocation {
+    "end": Position {
+      "column": 2,
+      "line": 20,
+    },
+    "start": Position {
+      "column": 0,
+      "line": 1,
+    },
+  },
+  "loc": SourceLocation {
+    "end": Position {
+      "column": 2,
+      "line": 20,
+    },
+    "start": Position {
+      "column": 0,
+      "line": 1,
+    },
+  },
+  "type": "BlockFrame",
+}
+`;
diff --git a/src/__tests__/scope-refactoring.ts b/src/__tests__/scope-refactoring.ts
index 10120441..11d71206 100644
--- a/src/__tests__/scope-refactoring.ts
+++ b/src/__tests__/scope-refactoring.ts
@@ -1,5 +1,15 @@
+import { Program } from 'estree'
 import { default as createContext } from '../createContext'
 import { getAllOccurrencesInScope } from '../index'
+import { BlockFrame } from '../types'
+import {
+  scopeVariables,
+  getBlockFromLoc,
+  getAllIdentifiers,
+  getNodeLocsInCurrentBlockFrame,
+  getBlockFramesInCurrentBlockFrame
+} from '../scope-refactoring'
+import { parse } from '../parser/parser'
 /* tslint:disable:max-classes-per-file */
 
 const context = createContext(4)
@@ -84,6 +94,16 @@ const functionScopeTests = [
       }
     `,
     [new Target('target', 7, 3), new Target('target', 1, 6)]
+  ),
+  new CodeTest(
+    'Test 1',
+    `function parse_and_eval(1) {
+      eval_top_level(1);
+    }
+    function repl(str) {
+      const res = parse_and_eval(23);
+    }`,
+    [new Target('parse_and_eval', 1, 13)]
   )
 ]
 
@@ -125,7 +145,7 @@ const conditionalsLoopsTests = [
         target += 99;
       }
     `,
-    [new Target('target', 3, 5), new Target('target', 2, 11), new Target('i', 5, 2)]
+    [new Target('target', 3, 5), new Target('i', 2, 10)]
   ),
   new CodeTest(
     'Test 3',
@@ -137,6 +157,16 @@ const conditionalsLoopsTests = [
       }
     `,
     [new Target('target', 4, 4)]
+  ),
+  new CodeTest(
+    'Test 4',
+    `
+    let i = "dontgetconfusedwith the next one pls";
+      for (let i = 0; i < 10; i++) {
+        target += 99;
+      }
+    `,
+    [new Target('i', 2, 10), new Target('i', 1, 5)]
   )
 ]
 
@@ -215,3 +245,234 @@ test('Scoped based refactoring with conditionals and loops', () => {
   })
   expect(actuals).toMatchSnapshot()
 })
+
+test('scopeVariables should return an accurate scope tree', () => {
+  const program = `
+    const anakin = "chancellor palpatine is evil";
+    const obiwan = "from my point of view the jedi are evil";
+    function disneyTrilogy() {
+      const isWorseThanPrequels = true;
+      return isWorseThanPrequels;
+    }
+    function functionmcfunctionface() {
+      const x = (corona) => (virus) => corona + virus;
+      let hahaha = "be";
+      return hahaha;
+    }
+  `
+  expect(scopeVariables(parse(program, context, true) as Program)).toMatchSnapshot()
+})
+
+test('scopeVariables should return an accurate scope tree with normal block scopes', () => {
+  const program = `
+    const anakin = 'chancellor palpatine is evil';
+    const obiwan = 'from my point of view the jedi are evil';
+    function disneyTrilogy() {
+      const isWorseThanPrequels = true;
+      return isWorseThanPrequels;
+    }
+    {
+      for (let i = 0; i < 10; i++) {
+        const x = 'nooooooooo';
+      }
+      if (true) {
+        const y = 10;
+      } else if (false) {
+        const t = 10;
+      } else {
+        const jfksdfjgk = 'S/U the semester!';
+      }
+    }
+  `
+  expect(scopeVariables(parse(program, context, true) as Program)).toMatchSnapshot()
+})
+
+test('getBlockFromLoc with normal variable name', () => {
+  const program = `const y = true;
+const dancingqueen = 'you can dance, you can jive';
+const nextline = 'having the time of your life';
+{
+  const lmao = dancingqueen;
+  function abba() {
+    const bestSong = 'the winnner takes it all';
+    return dancingqueen;
+  }
+}`
+  const scopedTree = scopeVariables(parse(program, context, true) as Program)
+  const loc = { start: { line: 8, column: 12 }, end: { line: 8, column: 23 } }
+  expect(getBlockFromLoc(loc, scopedTree)).toMatchSnapshot()
+})
+
+test('getBlockFromLoc with function definition name', () => {
+  const program = `const y = true;
+const dancingqueen = 'you can dance, you can jive';
+const nextline = 'having the time of your life';
+function picklerick() {
+  return 1;
+}
+{
+  const lmao = dancingqueen;
+  function abba() {
+    const bestSong = 'the winnner takes it all';
+    return dancingqueen;
+  }
+  picklerick();
+}`
+  const scopedTree = scopeVariables(parse(program, context, true) as Program)
+  const loc = { start: { line: 13, column: 3 }, end: { line: 13, column: 12 } }
+  expect(getBlockFromLoc(loc, scopedTree)).toMatchSnapshot()
+})
+
+test('getBlockFromLoc with arrow function name', () => {
+  const program = `const y = true;
+const dancingqueen = 'you can dance, you can jive';
+const nextline = 'having the time of your life';
+const picklerick() = x => {
+  return 1;
+};
+{
+  const lmao = dancingqueen;
+  function abba() {
+    const bestSong = 'the winnner takes it all';
+    return dancingqueen;
+  }
+  picklerick();
+}`
+  const scopedTree = scopeVariables(parse(program, context, true) as Program)
+  const loc = { start: { line: 13, column: 3 }, end: { line: 13, column: 12 } }
+  expect(getBlockFromLoc(loc, scopedTree)).toMatchSnapshot()
+})
+
+test('getAllIdentifiers should get all indentifiers regardless of scope', () => {
+  const program = `const y = true;
+const dancingqueen = 'you can dance, you can jive';
+const nextline = 'having the time of your life';
+const picklerick() = x => {
+  return 1;
+};
+{
+  const lmao = dancingqueen;
+  function abba() {
+    const bestSong = 'the winnner takes it all';
+    const virus = 'OwO';
+    return dancingqueen;
+  }
+  picklerick();
+}
+for (let i = 0; i < 10; i++) {
+  while (1 > 0) {
+    // do something cool
+  }
+  function wowThisIsANestedFunction(x) {
+    const virus = 'imameme';
+    const coronavirus = 'memememe' + virus;
+    return x + 'wowwowowowowowowowowow uwu';
+  }
+  wowThisIsANestedFunction('UwU');
+}
+if (true) {
+  const x = 2;
+} else if (false) {
+  const x = corona => virus => coronavirus;
+} else {
+  return 'idontliketests';
+}
+`
+  expect(getAllIdentifiers(parse(program, context, true) as Program, 'virus').length).toBe(4)
+})
+
+test('getNodeLocsInCurrentBlockFrame should return all nodes in the current block frame', () => {
+  const program = `const y = true;
+const dancingqueen = 'you can dance, you can jive';
+const nextline = 'having the time of your life';
+const picklerick() = x => {
+  return 1;
+};
+{
+  const lmao = dancingqueen;
+  function abba() {
+    const bestSong = 'the winnner takes it all';
+    const virus = 'OwO';
+    return dancingqueen;
+  }
+  picklerick();
+}
+for (let i = 0; i < 10; i++) {
+  while (1 > 0) {
+    // do something cool
+  }
+  function wowThisIsANestedFunction(x) {
+    const virus = 'imameme';
+    const coronavirus = 'memememe' + virus;
+    return x + 'wowwowowowowowowowowow uwu';
+  }
+  wowThisIsANestedFunction('UwU');
+}
+if (true) {
+  const x = 2;
+} else if (false) {
+  const x = corona => virus => coronavirus;
+} else {
+  return 'idontliketests';
+}`
+  const block = scopeVariables(parse(program, context, true) as Program)
+  const identifiers = getAllIdentifiers(parse(program, context, true) as Program, 'virus')
+  expect(
+    getNodeLocsInCurrentBlockFrame(
+      identifiers,
+      {
+        start: { line: 1, column: 1 },
+        end: { line: 33, column: 2 }
+      },
+      [block]
+    ).length
+  ).toBe(0)
+})
+
+test('getBlockFramesInCurrentBlockFrame', () => {
+  const program = `const y = true;
+const dancingqueen = 'you can dance, you can jive';
+const nextline = 'having the time of your life';
+const picklerick() = x => {
+  return 1;
+};
+{
+  const lmao = dancingqueen;
+  function abba() {
+    const bestSong = 'the winnner takes it all';
+    const virus = 'OwO';
+    return dancingqueen;
+  }
+  picklerick();
+}
+for (let i = 0; i < 10; i++) {
+  while (1 > 0) {
+    // do something cool
+  }
+  function wowThisIsANestedFunction(x) {
+    const virus = 'imameme';
+    const coronavirus = 'memememe' + virus;
+    return x + 'wowwowowowowowowowowow uwu';
+  }
+  wowThisIsANestedFunction('UwU');
+}
+if (true) {
+  const x = 2;
+} else if (false) {
+  const x = corona => virus => coronavirus;
+} else {
+  return 'idontliketests';
+}`
+  const block = scopeVariables(parse(program, context, true) as Program)
+  const blockFrames = block.children.filter(node => node.type === 'BlockFrame')
+  expect(
+    getBlockFramesInCurrentBlockFrame(
+      blockFrames as BlockFrame[],
+      {
+        start: { line: 1, column: 1 },
+        end: { line: 33, column: 2 }
+      },
+      []
+    ).length
+  ).toBe(6)
+})