Repository: SMAT-Lab/Scalpel (language: python)

Issue:
Write uniform tests 
I looked into different test files and found different ways how tests were written. For example, some tests rely on pytest and other not. 
In my opinion, this is not a good practice. Usually, python projects employ a specific test frameworks to write small and readable tests. Moreover, a testing framework helps to keep tests readable and maintainable. So far, that is not the case for Scalpel. Hence, I would recommend to integrate a specific testing framework for all tests and write a short guideline how tests should be written in order to have uniform tests. 

I think pytest is already a suitable framework that should be used in all tests. With pytest, you can also use GitHub actions to test your project whenever changes are pushed. 


Discussion / hints:
Thanks for pointing this out.  You are absolutely correct,  I am working on adding some new functionalities at the moment. Hopefully will address this issue in Sept, we will also see a new release version of the project then. 

Fix patch:
diff --git a/.gitignore b/.gitignore
index 59234817..8281efd4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -147,3 +147,16 @@ dmypy.json
 
 # Vim backup
 *.un~
+
+### VisualStudioCode ###
+.vscode/*      # Maybe .vscode/**/* instead - see comments
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+**/.history
+
+!setup.sh
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..7770ef50
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,42 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+default_language_version:
+    python: python3.10
+repos:
+-   repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.4.0
+    hooks:
+    -   id: check-added-large-files
+    -   id: check-toml
+    -   id: check-yaml
+        args:
+        -   --unsafe
+    -   id: end-of-file-fixer
+    -   id: trailing-whitespace
+-   repo: https://github.com/charliermarsh/ruff-pre-commit
+    rev: v0.0.254
+    hooks:
+    -   id: ruff
+        args:
+        - --exit-zero
+        - --exclude
+        - examples/*
+
+-   repo: https://github.com/pycqa/isort
+    rev: 5.12.0
+    hooks:
+    -   id: isort
+        name: isort (python)
+    -   id: isort
+        name: isort (cython)
+        types: [cython]
+    -   id: isort
+        name: isort (pyi)
+        types: [pyi]
+-   repo: https://github.com/psf/black
+    rev: 23.1.0
+    hooks:
+    -   id: black
+ci:
+    autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
+    autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..a0202dc8
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,16 @@
+{
+    "[python]": {
+        "editor.defaultFormatter": "ms-python.black-formatter",
+        "editor.formatOnSave": true
+      },
+      "python.testing.unittestEnabled": true,
+      "python.testing.pytestEnabled": false,
+      "python.testing.cwd": "${workspaceFolder}",
+      "python.testing.unittestArgs": [
+        "-v",
+        "-s",
+        "./tests",
+        "-p",
+        "test*.py"
+      ]
+}
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..ae0c3adb
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,36 @@
+## Lint code using ruff
+.PHONY: lint
+lint:
+	@echo "\n################\n Running ruff \n################\n"
+	python -m ruff --version
+	python -m ruff scalpel --exit-zero
+	@echo "\n################\n Running isort \n################\n"
+	python -m isort scalpel
+
+## Format code using black
+.PHONY: black
+black:
+	@echo "\n################\n Running black \n################\n"
+	python -m black --version
+	python -m black scalpel
+
+## Run tests using pytest
+.PHONY: pytest
+pytest:
+	@echo "\n################\n Running tests (pytest) \n################\n"
+	python -m pytest --version
+	python -m pytest tests
+
+## Run tests using unittest
+.PHONY: unittest
+unittest:
+	@echo "\n################\n Running tests (unittest) \n################\n"
+	python -m unittest discover -v -s ./tests -p "test_*.py"
+
+## Run all tests
+.PHONY: test
+test: pytest unittest
+
+## Run all
+.PHONY: all
+all: lint black test
diff --git a/README.md b/README.md
index fdfeb227..63a59cb9 100644
--- a/README.md
+++ b/README.md
@@ -7,20 +7,32 @@ Scalpel is a Python Static Analysis Framework. It provides essential program ana
 
 <img src="https://github.com/SMAT-Lab/Scalpel/blob/main/docs/source/_static/resources/Scalpel.svg" width="550">
 
+# Installing
+
+Install Scalpel using pip with the following command
+
+```sh
+pip install python-scalpel
+```
+
 # Contributing
 
 We will be highly appreciated it if you can contribute to this project. Please feel free to do so by [submiting issue reports](https://github.com/SMAT-Lab/Scalpel/issues) or directly [adding pull requests](https://github.com/SMAT-Lab/Scalpel/pulls). We hope to obtain help for:
 1. New features. If you believe your publication/open-source project can be part of our framework, please contact us.
-2. Bug reports. 
-3. Documentation. 
-4. Code refactoring 
+2. Bug reports.
+3. Documentation.
+4. Code refactoring
 
 ## Setting up Scalpel
 Clone the repository of Scalpel and in the root directory simply run:
-```python
-pip install python-scalpel
+```sh
+./setup.sh -i
 ```
 
+The setup script will create a virtual environment for you and install all dependencies. Scalpel will be installed in editable mode using `pip -e`.
+
+The setup script also installs pre-commit hooks that must be kept and followed during the development lifecycle.
+
 ## Brief Introduction
 
 Detailed user guides can be found at [python-scalpel.readthedocs.io](http://python-scalpel.readthedocs.io/).
@@ -33,15 +45,15 @@ We aim to provide Scalpel as a generic Python static analysis framework that inc
 
 * Function 2: Control-Flow Graph Construction. The control-flow graph(CFG) construction module generates intra-procedural CFGs, which are an essential component in static flow analysis with applications such as program optimization and taint analysis. A CFG represents all paths that might be traversed through a program during its execution. The CFGs of a Python project can be combined with the call graph to generate an inter-procedural CFG of the project.
 
-* Function 3: Static Single Assignment (SSA) Representation. The static single assignment module provides compiler-level intermediate representations (IR) for code analysis. It can not only be used for symbolic execution, but also for constant propagation. By renaming each variable assignment with different names,  we are able to obtain explicit use-def chains, therefore precisely tracking how data flow in the program. 
+* Function 3: Static Single Assignment (SSA) Representation. The static single assignment module provides compiler-level intermediate representations (IR) for code analysis. It can not only be used for symbolic execution, but also for constant propagation. By renaming each variable assignment with different names,  we are able to obtain explicit use-def chains, therefore precisely tracking how data flow in the program.
 
-* Function 4: Alias Analysis. Since variables can point to the same memory location or identical values, the alias analysis function is designed to model such usages. This function can be vital to sound constant propagation. In addition, alias analysis will also benefit type checking as well as API name qualifying. 
+* Function 4: Alias Analysis. Since variables can point to the same memory location or identical values, the alias analysis function is designed to model such usages. This function can be vital to sound constant propagation. In addition, alias analysis will also benefit type checking as well as API name qualifying.
 
-* Function 5: Constant Propagation. The constant propagation module will evaluate the actual values for variables at certain program points in different execution paths before runtime. With the actual values known beforehand, we are able to optimize code and detect bugs.  The constant propagation will utilize the representation from the SSA module to keep recording values from each assignment for a single variable. 
+* Function 5: Constant Propagation. The constant propagation module will evaluate the actual values for variables at certain program points in different execution paths before runtime. With the actual values known beforehand, we are able to optimize code and detect bugs.  The constant propagation will utilize the representation from the SSA module to keep recording values from each assignment for a single variable.
 
 * Function 6: Import Graph Construction. In python,  import flows and relations have been pointed out to be important for API mapping, dependency analysis. Our import graph construction aims to provide a data structure to represent these import relationships across the Python module files in the same project. The import graphs of multiple Python projects can be combined to perform inter-library dataflow analysis.
 
-* Function 7: Fully Qualified Name Inferrer. Python APIs or function names can be invoked in different ways depending on how they are imported. However, this results in inconveniences to API usage analysis. In this module, we will convert all function call names to their fully-qualified names which are dotted strings that can represent the path from the top-level module down to the object itself. Various tasks can be benefited from this functionality such as understanding deprecated API  usage, dependency parsing as well as building sound call graphs. 
+* Function 7: Fully Qualified Name Inferrer. Python APIs or function names can be invoked in different ways depending on how they are imported. However, this results in inconveniences to API usage analysis. In this module, we will convert all function call names to their fully-qualified names which are dotted strings that can represent the path from the top-level module down to the object itself. Various tasks can be benefited from this functionality such as understanding deprecated API  usage, dependency parsing as well as building sound call graphs.
 
 * Function 8: Call Graph Construction. A call graph depicts calling relationships between methods in a software program. It is a fundamental component in static flow analysis and can be leveraged in tasks such as profiling, vulnerability propagation, and refactoring. This module addresses the challenges brought by complicated features adopted in Python, such as higher-order functions and nested function definitions, to construct the precise call graphs for given Python projects.
 
@@ -55,11 +67,11 @@ The Scalpel's API documentation is available at [python-scalpel.readthedocs.io](
 <p>We release Scalpel source code in the hope of benefiting others. You are kindly asked to acknowledge usage of the tool by citing the following article: </p>
 
 ```
-@article{li2022scalpel, 
-title={Scalpel: The Python Static Analysis Framework}, 
-author={Li, Li and Wang, Jiawei and Quan, Haowei}, 
-journal={arXiv preprint arXiv:2202.11840}, 
-year={2022} 
+@article{li2022scalpel,
+title={Scalpel: The Python Static Analysis Framework},
+author={Li, Li and Wang, Jiawei and Quan, Haowei},
+journal={arXiv preprint arXiv:2202.11840},
+year={2022}
 }
 ```
 
@@ -76,9 +88,8 @@ This project has been inspired and supported by many existing works. If you thin
 1. [Fuzzyingbook](https://www.fuzzingbook.org/) by Andreas Zeller, Rahul Gopinath, Marcel Böhme, Gordon Fraser, and Christian Holler.
 2. [Debugging book](https://www.debuggingbook.org/) by Andreas Zeller.
 3. [StaticCFG](https://github.com/coetaur0/staticfg).
-4. [PyCG: Practical Call Graph Generation in Python](https://vitsalis.com/papers/pycg.pdf), ICSE 2021. 
+4. [PyCG: Practical Call Graph Generation in Python](https://vitsalis.com/papers/pycg.pdf), ICSE 2021.
 5. [A Simple, Fast Dominance Algorithm](https://www.cs.rice.edu/~keith/EMBED/dom.pdf) Keith D. Cooper, Timothy J. Harvey, and Ken Kennedy
 6. [COS598C Advanced Compilers](https://www.cs.princeton.edu/courses/archive/spr04/cos598C/lectures/02-ControlFlow.pdf), Princeton University
 7. [Restoring Execution Environments of Jupyter Notebooks](https://arxiv.org/ftp/arxiv/papers/2103/2103.02959.pdf)
 8. [Static Single Assignment Book](https://compilers.cs.uni-saarland.de/papers/bbhlmz13cc.pdf)
-
diff --git a/examples/API_name_qualifying.py b/examples/API_name_qualifying.py
index 50515522..8e5ee567 100644
--- a/examples/API_name_qualifying.py
+++ b/examples/API_name_qualifying.py
@@ -1,4 +1,5 @@
 import os
+
 from scalpel.core.mnode import MNode
 
 source = """
@@ -13,8 +14,8 @@
 
 """
 
-def main():
 
+def main():
     mnode = MNode("local")
     mnode.source = source
     mnode.gen_ast()
@@ -28,9 +29,10 @@ def main():
         dotted_parts = call_name.split(".")
         # if this function calls is from a imported module
         if dotted_parts[0] in import_dict:
-            dotted_parts = [import_dict[dotted_parts[0]]]+dotted_parts[1:]
+            dotted_parts = [import_dict[dotted_parts[0]]] + dotted_parts[1:]
             call_name = ".".join(dotted_parts)
         print(call_name)
 
-if __name__ ==  "__main__":
+
+if __name__ == "__main__":
     main()
diff --git a/examples/CFG_tutorial.py b/examples/CFG_tutorial.py
index 777aeda7..12cc6c5c 100644
--- a/examples/CFG_tutorial.py
+++ b/examples/CFG_tutorial.py
@@ -5,10 +5,10 @@
 
 # ## What is the control flow graph
 # The control-flow graph(CFG) construction module generates intra-procedural CFGs, which are an essential component in static flow analysis with applications such as program optimization and taint analysis. A CFG represents all paths that might be traversed through a program during its execution. The CFGs of a Python project can be combined with the call graph to generate an inter-procedural CFG of the project.
-# 
+#
 
 # ## Let's install Scalpel first
-# 
+#
 # ```console
 # python -m pip install .
 # ```
@@ -20,7 +20,6 @@
 
 from scalpel.cfg import CFGBuilder
 
-
 # ### Let's have some example codes for different statements.
 
 # ### Example 1: While statement
@@ -28,8 +27,8 @@
 # In[2]:
 
 
-# let's have an example code 
-src= """
+# let's have an example code
+src = """
 def fib():
     a, b = 0, 1
     while True:
@@ -61,18 +60,18 @@ def fib():
 # In[4]:
 
 
-cfg.build_visual('png')
+cfg.build_visual("png")
 
 
 # In[5]:
 
 
-# let's visualize a function cfg 
-fib_cfg = None 
+# let's visualize a function cfg
+fib_cfg = None
 for (block_id, fun_name), fun_cfg in cfg.functioncfgs.items():
     if fun_name == "fib":
         fib_cfg = fun_cfg
-fib_cfg.build_visual('png')
+fib_cfg.build_visual("png")
 
 
 # The first cfg is for the given source file, the sub-cfg is for the fib function. Above is the code to visit all the functions. Please note that the function CFGs can be indexed by both function name and its block id. The example is a **while** statement, two new blocks will be formed if the while is true after each iteration. Since there is a yield statement, the exit node will be added after each yield block. Therefore the block 7 is appended after block 5. In **visit_while** function, and if the while statement is not true, there will still be a new block for the while is False. The given example doesn't contain a break statement, therefore the loop will be back and add an exit. If that happens, it will look for the parent chain and start a loop.
@@ -82,7 +81,7 @@ def fib():
 # In[6]:
 
 
-src = '''
+src = """
 def compareNum(a,b,c):
     if a == 10:
         if b > c:
@@ -90,9 +89,9 @@ def compareNum(a,b,c):
         else:
             a = c
     print(a,b,c)
-    
+
 compareNum(11,3,2)
-'''
+"""
 
 
 # In[7]:
@@ -100,27 +99,27 @@ def compareNum(a,b,c):
 
 cfgTwo = CFGBuilder().build_from_src("compareNum", src)
 fun_cfg = cfgTwo.functioncfgs.items()
-cfgTwo.build_visual('png')
+cfgTwo.build_visual("png")
 
 
 # In[8]:
 
 
-compareNum_cfg = None 
+compareNum_cfg = None
 for (block_id, fun_name), fun_cfg in cfgTwo.functioncfgs.items():
     if fun_name == "compareNum":
         compareNum_cfg = fun_cfg
-compareNum_cfg.build_visual('png')
+compareNum_cfg.build_visual("png")
 
 
-# The CFG of compareNum function is a nested **if-else** statement. Compared with the first control flow graph of the given source code, there are more blocks in the second graph. Since Scalpel generates cfg for every function in the source code. In **visit_if** function in Scalpel, block 3 is first created for since it's the body of the if, followed by the block 4 which is the second if statement. Then after the if-else there will create a block which is the block 5. After that, visiting the children of the body of the if-else to populate the block. The edges from block 4 to block 6 and 8 is checking the condition of b and c. Thus the block 6 and 8 are formed. 
+# The CFG of compareNum function is a nested **if-else** statement. Compared with the first control flow graph of the given source code, there are more blocks in the second graph. Since Scalpel generates cfg for every function in the source code. In **visit_if** function in Scalpel, block 3 is first created for since it's the body of the if, followed by the block 4 which is the second if statement. Then after the if-else there will create a block which is the block 5. After that, visiting the children of the body of the if-else to populate the block. The edges from block 4 to block 6 and 8 is checking the condition of b and c. Thus the block 6 and 8 are formed.
 
 # ### Example 3: for loop statement
 
 # In[9]:
 
 
-src = '''
+src = """
 def countPrimes(num):
     count = 0
     for i in range(num+1):
@@ -132,9 +131,9 @@ def countPrimes(num):
                 print(i)
                 count += 1
     print (count)
-    
+
 countPrimes(10)
-'''
+"""
 
 
 # In[10]:
@@ -142,17 +141,17 @@ def countPrimes(num):
 
 cfgThree = CFGBuilder().build_from_src("countPrimes", src)
 fun_cfg = cfgThree.functioncfgs.items()
-cfgThree.build_visual('png')
+cfgThree.build_visual("png")
 
 
 # In[11]:
 
 
-countPrimes_cfg = None 
+countPrimes_cfg = None
 for (block_id, fun_name), fun_cfg in cfgThree.functioncfgs.items():
     if fun_name == "countPrimes":
         countPrimes_cfg = fun_cfg
-countPrimes_cfg.build_visual('png')
+countPrimes_cfg.build_visual("png")
 
 
 # The above example is to count the prime numbers in a given range, which is more complex than before with more nested statement about **for-loop statement** and **if-else statement**. Similar to the while statement, two new blocks will be generated when meet the for loop. One for the body of the for loop which is the block 5, another is after the for loop which is block 6 in the graph. When walking to the children of the if statement in block 5, more blocks are formed. The block 7 is for a new for statement when the i is larger than 1. However, the edges walks to the block 4 when i is less than 1 which is the if is not true. Similarily, the block 7 and block 9 are same to before. There is a little difference is that the in block 9, it encounters a break. Thus it looks up the parent chain which walks to the block 4 as still.
@@ -162,7 +161,7 @@ def countPrimes(num):
 # In[12]:
 
 
-src = '''
+src = """
 def divide(x,y):
     try:
         result = x // y
@@ -171,9 +170,9 @@ def divide(x,y):
         print("Erro! Cannot divide zero")
     finally:
         print("Executed anyway!")
-        
+
 divide(10,2)
-'''
+"""
 
 
 # In[13]:
@@ -181,22 +180,22 @@ def divide(x,y):
 
 cfgFour = CFGBuilder().build_from_src("divide", src)
 fun_cfg = cfgFour.functioncfgs.items()
-cfgFour.build_visual('png')
+cfgFour.build_visual("png")
 
 
 # In[14]:
 
 
-divide_cfg = None 
+divide_cfg = None
 for (block_id, fun_name), fun_cfg in cfgFour.functioncfgs.items():
     if fun_name == "divide":
         divide_cfg = fun_cfg
-divide_cfg.build_visual('png')
+divide_cfg.build_visual("png")
 
 
 # The example 4 is about the **try-except statement**, which is more straightforward than other examples. It will create new blocks when the try statement is happen which is block 3 and check what's inside the try statement. Then it visits the handlers and create blocks for the handler which is block 5 here. Finally, visiting the children in the body. Moreover, the Scalpel has predicted more complicated situation like there might be else or break statement. Thus more blocks and exits will also be added.
 
-# ### Blocks in a CFG: A deep look into CFG 
+# ### Blocks in a CFG: A deep look into CFG
 # Let's starting by BFS to get all blocks first based on the example one.
 
 # In[15]:
@@ -207,23 +206,22 @@ def divide(x,y):
 
 
 # ### We can also retreive information from any blocks such as :
-# 
+#
 # 1. block id (taking the first block as example)
 # 2. source code of the block
-# 3. a list of statements 
+# 3. a list of statements
 
 # In[16]:
 
 
-import ast 
+import ast
 
 entry_block = all_blocks[0]
 print(f"block id :{entry_block.id}\n")
 print(f"block source\n {entry_block.get_source()}")
 print(f"block id :{entry_block.id}")
 
-### 
+###
 statements = entry_block.statements
 for stmt in statements:
     print(ast.dump(stmt))  # dump this statement as string from the AST node
-
diff --git a/examples/cg_example_pkg/main.py b/examples/cg_example_pkg/main.py
index f71838ec..0b58d80e 100644
--- a/examples/cg_example_pkg/main.py
+++ b/examples/cg_example_pkg/main.py
@@ -2,6 +2,6 @@
 from .sub_folder1.module2 import Module2
 
 module1 = Module1()
-do_add = module1.add(1,1)
+do_add = module1.add(1, 1)
 module2 = Module2()
-do_minus = module2.minus(1,1)
\ No newline at end of file
+do_minus = module2.minus(1, 1)
diff --git a/examples/cg_example_pkg/sub_folder1/module1.py b/examples/cg_example_pkg/sub_folder1/module1.py
index 475598aa..56c53032 100644
--- a/examples/cg_example_pkg/sub_folder1/module1.py
+++ b/examples/cg_example_pkg/sub_folder1/module1.py
@@ -1,4 +1,4 @@
 class Module1(object):
-    def add(self,a,b):
-        a=a+1
-        return a+b
\ No newline at end of file
+    def add(self, a, b):
+        a = a + 1
+        return a + b
diff --git a/examples/cg_example_pkg/sub_folder1/module2.py b/examples/cg_example_pkg/sub_folder1/module2.py
index 3388876e..549e7798 100644
--- a/examples/cg_example_pkg/sub_folder1/module2.py
+++ b/examples/cg_example_pkg/sub_folder1/module2.py
@@ -1,4 +1,4 @@
 class Module2(object):
-    def minus(self,a,b):
-        b=b-1
-        return a-b
\ No newline at end of file
+    def minus(self, a, b):
+        b = b - 1
+        return a - b
diff --git a/examples/cg_tutorial.py b/examples/cg_tutorial.py
index 06114b4e..1b52d3dc 100644
--- a/examples/cg_tutorial.py
+++ b/examples/cg_tutorial.py
@@ -1,7 +1,7 @@
-from scalpel.call_graph.pycg import CallGraphGenerator
-from scalpel.call_graph.pycg import formats
 import json
 
+from scalpel.call_graph.pycg import CallGraphGenerator, formats
+
 cg_generator = CallGraphGenerator(["./cg_example_pkg/main.py"], "cg_example_pkg")
 cg_generator.analyze()
 cg = cg_generator.output()
@@ -9,4 +9,4 @@
 formatter = formats.Simple(cg_generator)
 print(formatter.generate())
 with open("example_results.json", "w+") as f:
-    f.write(json.dumps(formatter.generate()))
\ No newline at end of file
+    f.write(json.dumps(formatter.generate()))
diff --git a/examples/import_graph_example_pkg/module_a.py b/examples/import_graph_example_pkg/module_a.py
index d4cf0989..adc46ffe 100644
--- a/examples/import_graph_example_pkg/module_a.py
+++ b/examples/import_graph_example_pkg/module_a.py
@@ -1,6 +1,7 @@
 from .module_b import B
 from .module_c import C
 
+
 class A:
     def foo(self):
-        return
\ No newline at end of file
+        return
diff --git a/examples/import_graph_example_pkg/module_b.py b/examples/import_graph_example_pkg/module_b.py
index d9e574a7..c771fcbf 100644
--- a/examples/import_graph_example_pkg/module_b.py
+++ b/examples/import_graph_example_pkg/module_b.py
@@ -1,5 +1,6 @@
 from .module_c import C
 
+
 class B:
     def foo(self):
-        return
\ No newline at end of file
+        return
diff --git a/examples/import_graph_example_pkg/module_c.py b/examples/import_graph_example_pkg/module_c.py
index 63cdc260..1307a62c 100644
--- a/examples/import_graph_example_pkg/module_c.py
+++ b/examples/import_graph_example_pkg/module_c.py
@@ -1,5 +1,6 @@
 import os
 
+
 class C:
     def foo(self):
-        return
\ No newline at end of file
+        return
diff --git a/examples/import_graph_tutorial.py b/examples/import_graph_tutorial.py
index 55d9277c..12990e20 100644
--- a/examples/import_graph_tutorial.py
+++ b/examples/import_graph_tutorial.py
@@ -1,8 +1,7 @@
-from scalpel.import_graph.import_graph import Tree,ImportGraph
+from scalpel.import_graph.import_graph import ImportGraph, Tree
 
 
 def main():
-
     target_dir = "./import_graph_example_pkg"
     import_graph = ImportGraph(target_dir)
     import_graph.build_dir_tree()
@@ -13,4 +12,4 @@ def main():
 
 
 if __name__ == "__main__":
-    main() 
+    main()
diff --git a/examples/rewriter_example.py b/examples/rewriter_example.py
index 6a16d572..dcb5d46b 100644
--- a/examples/rewriter_example.py
+++ b/examples/rewriter_example.py
@@ -1,6 +1,7 @@
+import ast
 import os
 import sys
-import ast
+
 import astor
 
 from scalpel.rewriter import Rewriter
@@ -14,23 +15,25 @@
 b = {}
 """
 
-def rewrite_rules(node)->list:
-
-    if  isinstance(node, ast.Assign):
-      if isinstance(node.value, ast.Call) and hasattr(node.value.func, "id"):
-          if node.value.func.id == "list":
-              new_assign_value = ast.List(elts=[], ctx=ast.Load())
-              new_stmt = ast.Assign(node.targets, new_assign_value)
-              return [new_stmt]
-          if node.value.func.id == "dict":
-              new_assign_value = ast.Dict(keys=[], values=[])
-              new_stmt = ast.Assign(node.targets, new_assign_value)
-              return [new_stmt]            
+
+def rewrite_rules(node) -> list:
+    if isinstance(node, ast.Assign):
+        if isinstance(node.value, ast.Call) and hasattr(node.value.func, "id"):
+            if node.value.func.id == "list":
+                new_assign_value = ast.List(elts=[], ctx=ast.Load())
+                new_stmt = ast.Assign(node.targets, new_assign_value)
+                return [new_stmt]
+            if node.value.func.id == "dict":
+                new_assign_value = ast.Dict(keys=[], values=[])
+                new_stmt = ast.Assign(node.targets, new_assign_value)
+                return [new_stmt]
+
+
 def main():
     rewriter = Rewriter()
-    new_src = rewriter.rewrite(src, rule_func = rewrite_rules)
+    new_src = rewriter.rewrite(src, rule_func=rewrite_rules)
     print(new_src)
 
-if __name__ == '__main__':
-    main()
 
+if __name__ == "__main__":
+    main()
diff --git a/examples/rewriter_var_renaming.py b/examples/rewriter_var_renaming.py
index 7140f4f4..04ff03d4 100644
--- a/examples/rewriter_var_renaming.py
+++ b/examples/rewriter_var_renaming.py
@@ -1,9 +1,11 @@
+import ast
 import os
 import sys
-import ast
+
 import astor
 
 from scalpel.rewriter import Rewriter
+
 src = """
 def func(a,b):
     c = a+b
@@ -16,7 +18,8 @@ def func(x,y):
     return z
 """
 
-def rewrite_rules(node)->list:
+
+def rewrite_rules(node) -> list:
     old_name = "c"
     new_name = "z"
     for tmp_node in ast.walk(node):
@@ -25,12 +28,12 @@ def rewrite_rules(node)->list:
                 tmp_node.id = new_name
     return [node]
 
-                     
+
 def main():
     rewriter = Rewriter()
-    new_src = rewriter.rewrite(src, rule_func = rewrite_rules)
+    new_src = rewriter.rewrite(src, rule_func=rewrite_rules)
     print(new_src)
 
-if __name__ == '__main__':
-    main()
 
+if __name__ == "__main__":
+    main()
diff --git a/examples/ssa_example.py b/examples/ssa_example.py
index f74dbc0e..183c5b05 100644
--- a/examples/ssa_example.py
+++ b/examples/ssa_example.py
@@ -1,8 +1,10 @@
+import ast
 import os
 import sys
-import ast
-import astor
 import unittest
+
+import astor
+
 from scalpel.core.mnode import MNode
 from scalpel.SSA.const import SSA
 
@@ -14,8 +16,9 @@
     a = 10
 print(a)
 """
-def main():
 
+
+def main():
     mnode = MNode("local")
     mnode.source = code_str
     mnode.gen_ast()
@@ -23,11 +26,12 @@ def main():
     m_ssa = SSA()
     ssa_results, const_dict = m_ssa.compute_SSA(cfg)
     for block_id, stmt_res in ssa_results.items():
-      print("These are the results for block ".format(block_id))
-      print(stmt_res)
+        print("These are the results for block ".format(block_id))
+        print(stmt_res)
     for name, value in const_dict.items():
-      print(name, value)
+        print(name, value)
     print(ssa_results)
-    
-if __name__ == '__main__':
+
+
+if __name__ == "__main__":
     main()
diff --git a/examples/type_infer_tutorial.py b/examples/type_infer_tutorial.py
index 1fd1d93d..5d1b8295 100644
--- a/examples/type_infer_tutorial.py
+++ b/examples/type_infer_tutorial.py
@@ -1,6 +1,8 @@
 from scalpel.typeinfer.typeinfer import TypeInference
 
-inferer = TypeInference(name='type_infer_example.py', entry_point='./type_infer_example.py')
+inferer = TypeInference(
+    name="type_infer_example.py", entry_point="./type_infer_example.py"
+)
 inferer.infer_types()
 inferred = inferer.get_types()
-print(inferred)
\ No newline at end of file
+print(inferred)
diff --git a/pyproject.toml b/pyproject.toml
index 7fe144c8..afed4a4f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -49,3 +49,15 @@ classifiers = [
 [project.urls]
 "Homepage" = "https://github.com/SMAT-Lab/Scalpel"
 "Bug Tracker" = "https://github.com/SMAT-Lab/Scalpel/issues"
+
+[tool.ruff]
+# Never enforce `E501` (line length violations).
+ignore = ["E501"]
+
+# Ignore `E402` (import violations) in all `__init__.py` files
+[tool.ruff.per-file-ignores]
+"__init__.py" = ["E402"]
+"path/to/file.py" = ["E402"]
+
+[tool.isort]
+profile = "black"
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..6badcc97
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,19 @@
+astor==0.8.1
+graphviz==0.17
+networkx==3.0
+astunparse==1.6.3
+typed-ast==1.5.4
+typeshed-client==2.2.0
+regex==2023.3.23
+dataclasses==0.6
+pycg==0.0.6
+sphinx
+myst_parser
+sphinx_rtd_theme
+sphinxcontrib-spelling
+pydoctor==22.9.1
+ruff==0.0.260
+black==23.3.0
+isort==5.12.0
+pre-commit==3.2.1
+pytest==7.2.2
diff --git a/scalpel/SSA/__init__.py b/scalpel/SSA/__init__.py
index 7cf7a867..c41bd1ea 100644
--- a/scalpel/SSA/__init__.py
+++ b/scalpel/SSA/__init__.py
@@ -1,8 +1,8 @@
 """
 Static Single Assignment (SSA) is a technique of IR in the compiling thoery, it also shows great benefits to static anaysis tasks such as constant propagation, dead code elimination and etc.
-Constant propagation is also a matured technique in static anaysis. 
-It is the process of evaluating or recognizing the actual constant values or expressions at a particular program point. This is realized by utilizing control flow and data flow information. Determining the possible values for variables before runtime gives great benefits to software anaysis. 
+Constant propagation is also a matured technique in static anaysis.
+It is the process of evaluating or recognizing the actual constant values or expressions at a particular program point. This is realized by utilizing control flow and data flow information. Determining the possible values for variables before runtime gives great benefits to software anaysis.
 For instance, with constant value propagation, we can detect and remove dead code or perfrom type checking.
 In scalpel, we implement constant propagation along with the SSA for execution efficiency.
 """
-__slots__ = ["const"]
\ No newline at end of file
+__slots__ = ["const"]
diff --git a/scalpel/SSA/alg.py b/scalpel/SSA/alg.py
index 981b3fa7..751b4c2a 100644
--- a/scalpel/SSA/alg.py
+++ b/scalpel/SSA/alg.py
@@ -4,10 +4,12 @@
 TODO: implement a datastructure such as G from networkx for control flow graph. so that networkx is not required in this library.
 """
 
-import networkx as nx
 from functools import reduce
 
-__docformat__ = 'numpy'
+import networkx as nx
+
+__docformat__ = "numpy"
+
 
 def immediate_dominators(G, start):
     """Returns the immediate dominators of all nodes of a directed graph.
@@ -118,7 +120,7 @@ def dominance_frontiers(G, start):
            A simple, fast dominance algorithm.
            Software Practice & Experience, 4:110, 2001.
     """
-    
+
     idom = immediate_dominators(G, start)
 
     df = {u: set() for u in idom}
@@ -135,7 +137,7 @@ def dominance_frontiers(G, start):
 def main():
     G = nx.DiGraph([(1, 2), (1, 3), (2, 5), (3, 4), (4, 5)])
     res = sorted((u, sorted(df)) for u, df in dominance_frontiers(G, 1).items())
-    
+
     assert res == [(1, []), (2, [5]), (3, [5]), (4, [5]), (5, [])]
 
 
diff --git a/scalpel/SSA/const.py b/scalpel/SSA/const.py
index 2b97895b..0b3ee835 100644
--- a/scalpel/SSA/const.py
+++ b/scalpel/SSA/const.py
@@ -1,18 +1,21 @@
-""" 
+"""
 In this module, the single static assignment forms are implemented to allow
 further analysis. The module contain a single class named SSA.
 """
 import ast
-import astor
-from functools import reduce
 from collections import OrderedDict
+from functools import reduce
+
+import astor
 import networkx as nx
+
 from ..core.vars_visitor import get_vars
 
+
 def parse_val(node):
     # does not return anything
     if isinstance(node, ast.Constant):
-       return node.value
+        return node.value
     if isinstance(node, ast.Str):
         if hasattr(node, "value"):
             return node.value
@@ -25,12 +28,13 @@ class SSA:
     """
     Build SSA graph from a given AST node based on the CFG.
     """
-    def __init__ (self):
+
+    def __init__(self):
         """
         Args:
             src: the source code as input.
         """
-        # the class SSA takes a module as the input 
+        # the class SSA takes a module as the input
         self.numbering = {}  # numbering variables
         self.var_values = {}  # numbering variables
         self.global_live_idents = []
@@ -49,7 +53,7 @@ def __init__ (self):
     def get_attribute_stmts(self, stmts):
         call_stmts = []
         for stmt in stmts:
-            if isinstance(stmt,ast.Call) and isinstance(stmt.func, ast.Attribute):
+            if isinstance(stmt, ast.Call) and isinstance(stmt.func, ast.Attribute):
                 call_stmts += [stmt]
 
     def get_identifiers(self, ast_node):
@@ -61,18 +65,20 @@ def get_identifiers(self, ast_node):
         if ast_node is None:
             return []
         res = get_vars(ast_node)
-        idents = [r['name'] for r in res if  r['name'] is not None and "." not in r['name']]
+        idents = [
+            r["name"] for r in res if r["name"] is not None and "." not in r["name"]
+        ]
         return idents
 
     def compute_SSA(self, cfg):
         """
-        Compute single static assignment form representations for a given CFG. 
+        Compute single static assignment form representations for a given CFG.
         During the computing, constant value and alias pairs are generated. The following steps are used to compute SSA representations:
         step 1a: compute the dominance frontier
         step 1b: use dominance frontier to place phi node
         if node X contains assignment to a, put phi node for an in dominance frontier of X
-        adding phi function may require introducing additional phi function 
-        start from the entry node 
+        adding phi function may require introducing additional phi function
+        start from the entry node
         step2: rename variables so only one definition per name
 
         Args:
@@ -84,45 +90,48 @@ def compute_SSA(self, cfg):
         ident_const_dict = {}
         # step 1a: compute the dominance frontier
         all_blocks = cfg.get_all_blocks()
-        id2blocks = {block.id:block for block in all_blocks}
+        id2blocks = {block.id: block for block in all_blocks}
 
-        block_loaded_idents = {block.id:[] for block in all_blocks}
-        block_stored_idents = {block.id:[] for block in all_blocks}
+        block_loaded_idents = {block.id: [] for block in all_blocks}
+        block_stored_idents = {block.id: [] for block in all_blocks}
 
-        block_const_dict = {block.id:[] for block in all_blocks}
+        block_const_dict = {block.id: [] for block in all_blocks}
+
+        block_renamed_stored = {block.id: [] for block in all_blocks}
+        block_renamed_loaded = {block.id: [] for block in all_blocks}
 
-        block_renamed_stored = {block.id:[] for block in all_blocks}
-        block_renamed_loaded = {block.id:[] for block in all_blocks}
-        
         DF = self.compute_DF(all_blocks)
 
         for block in all_blocks:
             df_nodes = DF[block.id]
             tmp_const_dict = {}
-            
 
             for idx, stmt in enumerate(block.statements):
                 stmt_const_dict = {}
-                stored_idents, loaded_idents, func_names = self.get_stmt_idents_ctx(stmt, const_dict=stmt_const_dict) 
+                stored_idents, loaded_idents, func_names = self.get_stmt_idents_ctx(
+                    stmt, const_dict=stmt_const_dict
+                )
                 tmp_const_dict[idx] = stmt_const_dict
                 block_loaded_idents[block.id] += [loaded_idents]
                 block_stored_idents[block.id] += [stored_idents]
-                block_renamed_loaded[block.id] += [{ident:set() for ident in loaded_idents}]
-            
-            block_const_dict[block.id]  = tmp_const_dict
-        
+                block_renamed_loaded[block.id] += [
+                    {ident: set() for ident in loaded_idents}
+                ]
+
+            block_const_dict[block.id] = tmp_const_dict
+
         for block in all_blocks:
             stored_idents = block_stored_idents[block.id]
             loaded_idents = block_loaded_idents[block.id]
             n_stmts = len(stored_idents)
-            assert (n_stmts == len(loaded_idents))
+            assert n_stmts == len(loaded_idents)
             affected_idents = []
             tmp_const_dict = block_const_dict[block.id]
             for i in range(n_stmts):
                 stmt_stored_idents = stored_idents[i]
                 stmt_loaded_idents = loaded_idents[i]
-                stmt_renamed_stored =  {}
-                
+                stmt_renamed_stored = {}
+
                 for ident in stmt_stored_idents:
                     affected_idents.append(ident)
                     if ident in ident_name_counter:
@@ -132,43 +141,48 @@ def compute_SSA(self, cfg):
                     # rename the var name as the number of assignments
                     stmt_const_dict = tmp_const_dict[i]
                     if ident in stmt_const_dict:
-                        ident_const_dict[(ident, ident_name_counter[ident])] = stmt_const_dict[ident]
-                        
+                        ident_const_dict[(ident, ident_name_counter[ident])] = (
+                            stmt_const_dict[ident]
+                        )
+
                     stmt_renamed_stored[ident] = ident_name_counter[ident]
                 block_renamed_stored[block.id] += [stmt_renamed_stored]
-                
 
-                #same block, number used identifiers
+                # same block, number used identifiers
                 for ident in stmt_loaded_idents:
                     # a list of dictions for each of idents used in this statement
                     phi_loaded_idents = block_renamed_loaded[block.id][i]
                     if ident in ident_name_counter:
                         phi_loaded_idents[ident].add(ident_name_counter[ident])
-                   
+
             df_block_ids = DF[block.id]
             for df_block_id in df_block_ids:
-                df_block = id2blocks[df_block_id] 
+                df_block = id2blocks[df_block_id]
                 block_ident_gen_produced = []
                 df_block_stored_idents = block_stored_idents[df_block_id]
                 for af_ident in affected_idents:
                     # this for-loop process every statement in the block
-                    for idx, phi_loaded_idents in enumerate(block_renamed_loaded[df_block_id]):
+                    for idx, phi_loaded_idents in enumerate(
+                        block_renamed_loaded[df_block_id]
+                    ):
                         block_ident_gen_produced.extend(df_block_stored_idents[idx])
                         if af_ident in block_ident_gen_produced:
-                            continue 
+                            continue
                         # place phi function here this var used
                         # if af_ident has been assigned in this block beforclee this statement, then discard it
-                        # so theck af_ident has been generated in this block 
-                        if af_ident in phi_loaded_idents:  
-                            phi_loaded_idents[af_ident].add(ident_name_counter[af_ident])
-    
+                        # so theck af_ident has been generated in this block
+                        if af_ident in phi_loaded_idents:
+                            phi_loaded_idents[af_ident].add(
+                                ident_name_counter[af_ident]
+                            )
+
         return block_renamed_loaded, ident_const_dict
 
-    def get_stmt_idents_ctx(self, stmt, del_set=[], const_dict = {}):
+    def get_stmt_idents_ctx(self, stmt, del_set=[], const_dict={}):
         """
-        Extract the contextual information of each of identifiers. 
+        Extract the contextual information of each of identifiers.
         For assignment statements, the assigned values for each of variables will be stored.
-        In addition, the del_set will store all deleted variables. 
+        In addition, the del_set will store all deleted variables.
         Args:
             stmt: statement from AST trees.
             del_set: deleted identifiers
@@ -179,7 +193,7 @@ def get_stmt_idents_ctx(self, stmt, del_set=[], const_dict = {}):
         loaded_idents = []
         func_names = []
         # assignment with only one target
-        
+
         if isinstance(stmt, ast.Assign):
             targets = stmt.targets
             value = stmt.value
@@ -190,7 +204,7 @@ def get_stmt_idents_ctx(self, stmt, del_set=[], const_dict = {}):
                 elif isinstance(targets[0], ast.Attribute):
                     left_name = astor.to_source(stmt.targets[0]).strip()
                     const_dict[left_name] = value
-                # multiple targets are represented as tuple 
+                # multiple targets are represented as tuple
                 elif isinstance(targets[0], ast.Tuple):
                     # value is also represented as tuple
                     if isinstance(value, ast.Tuple):
@@ -199,7 +213,7 @@ def get_stmt_idents_ctx(self, stmt, del_set=[], const_dict = {}):
                                 left_name = elt.id
                                 const_dict[left_name] = val
                             elif isinstance(targets[0], ast.Attribute):
-                                #TODO: resolve attributes
+                                # TODO: resolve attributes
                                 pass
                     # value is represented as call
                     if isinstance(value, ast.Call):
@@ -208,52 +222,54 @@ def get_stmt_idents_ctx(self, stmt, del_set=[], const_dict = {}):
                                 left_name = elt.id
                                 const_dict[left_name] = value
                             elif isinstance(targets[0], ast.Attribute):
-                                #TODO: resolve attributes
+                                # TODO: resolve attributes
                                 pass
             else:
-                # Note  in some python versions, there are more than one target for an assignment 
+                # Note  in some python versions, there are more than one target for an assignment
                 # while in some other python versions, multiple targets are deemed as ast.Tuple type in assignment statement
                 for target in stmt.targets:
                     # this is an assignment to tuple such as a,b = fun()
                     # then no valid constant value can be recorded for this statement
                     if hasattr(target, "id"):
                         left_name = target.id
-                        const_dict[left_name] = None  # TODO: design a type for these kind of values 
+                        const_dict[left_name] = (
+                            None  # TODO: design a type for these kind of values
+                        )
                     elif isinstance(stmt.targets[0], ast.Attribute):
-                        #TODO: resolve attributes
+                        # TODO: resolve attributes
                         pass
 
-
-
         # one target assignment with type annotations
         if isinstance(stmt, ast.AnnAssign):
             if hasattr(stmt.target, "id"):
                 left_name = stmt.target.id
                 const_dict[left_name] = stmt.value
             elif isinstance(stmt.target, ast.Attribute):
-                #TODO: resolve attributes
+                # TODO: resolve attributes
                 pass
         if isinstance(stmt, ast.AugAssign):
             # note here , we need to rewrite this value to its extended form
             # if the statement is "a += 1", then the assigned value should be a+1
             if hasattr(stmt.target, "id"):
                 left_name = stmt.target.id
-                extended_right = ast.BinOp(ast.Name(left_name, ast.Load()), stmt.op, stmt.value)
+                extended_right = ast.BinOp(
+                    ast.Name(left_name, ast.Load()), stmt.op, stmt.value
+                )
                 const_dict[left_name] = extended_right
             elif isinstance(stmt.target, ast.Attribute):
-                #TODO: resolve attributes
+                # TODO: resolve attributes
                 pass
         if isinstance(stmt, ast.For):
             # there is a variation of assignment in for loop
             # in the case of :  for i in [1,2,3]
             # the element of stmt.iter is the value of this assignment
-            if hasattr(stmt.target, "id"):   
+            if hasattr(stmt.target, "id"):
                 left_name = stmt.target.id
                 iter_value = stmt.iter
                 # make a iter call
-                #iter_node = ast.Call(ast.Name("iter", ast.Load()), [stmt.iter], [])
+                # iter_node = ast.Call(ast.Name("iter", ast.Load()), [stmt.iter], [])
                 # make a next call
-                #next_call_node = ast.Call(ast.Name("next", ast.Load()), [iter_node], [])
+                # next_call_node = ast.Call(ast.Name("next", ast.Load()), [iter_node], [])
                 const_dict[left_name] = iter_value
 
             elif isinstance(stmt.target, ast.Tuple):
@@ -263,10 +279,8 @@ def get_stmt_idents_ctx(self, stmt, del_set=[], const_dict = {}):
                     if hasattr(elt, "id"):
                         const_dict[elt.id] = stmt.iter
             elif isinstance(stmt.target, ast.Attribute):
-                #TODO: resolve attributes
+                # TODO: resolve attributes
                 pass
-      
-            
 
         if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)):
             stored_idents.append(stmt.name)
@@ -276,12 +290,12 @@ def get_stmt_idents_ctx(self, stmt, del_set=[], const_dict = {}):
             new_stmt.body = []
             ident_info = get_vars(new_stmt)
             for r in ident_info:
-                if r['name'] is None:
+                if r["name"] is None:
                     continue
-                if r['usage'] == "load":
-                    loaded_idents.append(r['name']) 
+                if r["usage"] == "load":
+                    loaded_idents.append(r["name"])
             return stored_idents, loaded_idents, func_names
- 
+
         if isinstance(stmt, ast.ClassDef):
             stored_idents.append(stmt.name)
             const_dict[stmt.name] = None
@@ -293,19 +307,21 @@ def get_stmt_idents_ctx(self, stmt, del_set=[], const_dict = {}):
         if isinstance(stmt, (ast.Import, ast.ImportFrom)):
             for alias in stmt.names:
                 if alias.asname is None:
-                    stored_idents += [alias.name.split('.')[0]]
+                    stored_idents += [alias.name.split(".")[0]]
                 else:
-                    stored_idents += [alias.asname.split('.')[0]]
+                    stored_idents += [alias.asname.split(".")[0]]
             return stored_idents, loaded_idents, []
 
         if isinstance(stmt, (ast.Try)):
             for handler in stmt.handlers:
                 if handler.name is not None:
                     stored_idents.append(handler.name)
-                    
+
                 if isinstance(handler.type, ast.Name):
                     loaded_idents.append(handler.type.id)
-                elif isinstance(handler.type, ast.Attribute) and isinstance(handler.type.value, ast.Name):
+                elif isinstance(handler.type, ast.Attribute) and isinstance(
+                    handler.type.value, ast.Name
+                ):
                     loaded_idents.append(handler.type.value.id)
             return stored_idents, loaded_idents, []
         if isinstance(stmt, ast.Global):
@@ -315,14 +331,14 @@ def get_stmt_idents_ctx(self, stmt, del_set=[], const_dict = {}):
 
         visit_node = stmt
 
-        if isinstance(visit_node,(ast.If, ast.IfExp)):
+        if isinstance(visit_node, (ast.If, ast.IfExp)):
             # visit_node.body = []
             # visit_node.orlse=[]
             visit_node = stmt.test
 
         elif isinstance(visit_node, (ast.With)):
             visit_node.body = []
-            visit_node.orlse=[]
+            visit_node.orlse = []
 
         elif isinstance(visit_node, (ast.While)):
             visit_node.body = []
@@ -341,14 +357,14 @@ def get_stmt_idents_ctx(self, stmt, del_set=[], const_dict = {}):
 
         ident_info = get_vars(visit_node)
         for r in ident_info:
-            if r['name'] is None or "_hidden_" in r['name']:
+            if r["name"] is None or "_hidden_" in r["name"]:
                 continue
-            if r['usage'] == 'store':
-                stored_idents.append(r['name'])
+            if r["usage"] == "store":
+                stored_idents.append(r["name"])
             else:
-                loaded_idents.append(r['name'])
-            if r['usage'] == 'del':
-                del_set.append(r['name'])
+                loaded_idents.append(r["name"])
+            if r["usage"] == "del":
+                del_set.append(r["name"])
         return stored_idents, loaded_idents, []
 
     def to_json(self):
@@ -367,11 +383,11 @@ def compute_idom(self, ssa_blocks):
         # construct the Graph
         entry_block = ssa_blocks[0]
         G = nx.DiGraph()
-        for block in ssa_blocks: 
+        for block in ssa_blocks:
             G.add_node(block.id)
             exits = block.exits
-            preds =  block.predecessors
-            for link in preds+exits:
+            preds = block.predecessors
+            for link in preds + exits:
                 G.add_edge(link.source.id, link.target.id)
         # DF = nx.dominance_frontiers(G, entry_block.id)
         idom = nx.immediate_dominators(G, entry_block.id)
@@ -387,12 +403,12 @@ def compute_DF(self, ssa_blocks):
         # construct the Graph
         entry_block = ssa_blocks[0]
         G = nx.DiGraph()
-        for block in ssa_blocks: 
+        for block in ssa_blocks:
             G.add_node(block.id)
             exits = block.exits
             preds = block.predecessors
-            for link in preds+exits:
+            for link in preds + exits:
                 G.add_edge(link.source.id, link.target.id)
         DF = nx.dominance_frontiers(G, entry_block.id)
-        #idom = nx.immediate_dominators(G, entry_block.id)
+        # idom = nx.immediate_dominators(G, entry_block.id)
         return DF
diff --git a/scalpel/SSA/def_use.py b/scalpel/SSA/def_use.py
index 62599511..61caebfa 100644
--- a/scalpel/SSA/def_use.py
+++ b/scalpel/SSA/def_use.py
@@ -1,8 +1,8 @@
-""" 
+"""
 In this module, We implement def-use chain structure for Scalple framework.
 """
 
+
 class DefUseChain:
     def ___init__(self):
-        pass 
-    
\ No newline at end of file
+        pass
diff --git a/scalpel/SSA/ssa.py b/scalpel/SSA/ssa.py
index 94e72ff0..053bf264 100644
--- a/scalpel/SSA/ssa.py
+++ b/scalpel/SSA/ssa.py
@@ -1,16 +1,18 @@
-""" 
+"""
 In this module, the single static assignment forms are  implemented to allow
 further analysis. The module contain a single class named SSA.
 """
 import ast
-import astor
-from functools import reduce
 from collections import OrderedDict
+from functools import reduce
+
+import astor
 import networkx as nx
-from ..core.vars_visitor import get_vars
-from ..cfg.builder import CFGBuilder, Block, invert
+
+from ..cfg.builder import Block, CFGBuilder, invert
 from ..core.mnode import MNode
-from ..core.vars_visitor  import get_vars
+from ..core.vars_visitor import get_vars
+
 
 def parse_val(node):
     # does not return anything
@@ -28,19 +30,20 @@ class SSA:
     """
     Build SSA graph from a given AST node based on the CFG.
     """
-    def __init__ (self, src):
+
+    def __init__(self, src):
         """
         Args:
             src: the source code as input.
         """
-        # the class SSA takes a module as the input 
-        self.src = src   # source code
+        # the class SSA takes a module as the input
+        self.src = src  # source code
         self.module_ast = ast.parse(src)
         self.numbering = {}  # numbering variables
         self.var_values = {}  # numbering variables
         self.m_node = MNode("tmp")
         self.m_node.source = self.src
-        self.m_node.gen_ast() 
+        self.m_node.gen_ast()
         self.global_live_idents = []
         self.ssa_blocks = []
         self.error_paths = {}
@@ -55,10 +58,10 @@ def __init__ (self, src):
         self.global_names = []
 
     def get_global_live_vars(self):
-        #import_dict = self.m_node.parse_import_stmts()
-        #def_records = self.m_node.parse_func_defs()
-        #def_idents = [r['name'] for r in def_records if r['scope'] == 'mod']
-        #self.global_live_idents = def_idents + list(import_dict.keys())
+        # import_dict = self.m_node.parse_import_stmts()
+        # def_records = self.m_node.parse_func_defs()
+        # def_idents = [r['name'] for r in def_records if r['scope'] == 'mod']
+        # self.global_live_idents = def_idents + list(import_dict.keys())
         self.global_live_idents = []
 
     def flatten_tuple(ast_tuple):
@@ -66,14 +69,14 @@ def flatten_tuple(ast_tuple):
         input: ast tuple object
         return a list of elements in the given tuple
         """
-        output =[]
+        output = []
         first = ast_tuple[0]
         second = ast_tuple[1]
 
     def get_attribute_stmts(self, stmts):
         call_stmts = []
         for stmt in stmts:
-            if isinstance(stmt,ast.Call) and isinstance(stmt.func, ast.Attribute):
+            if isinstance(stmt, ast.Call) and isinstance(stmt.func, ast.Attribute):
                 call_stmts += [stmt]
 
     def get_identifiers(self, ast_node):
@@ -85,7 +88,9 @@ def get_identifiers(self, ast_node):
         if ast_node is None:
             return []
         res = get_vars(ast_node)
-        idents = [r['name'] for r in res if  r['name'] is not None and "." not in r['name']]
+        idents = [
+            r["name"] for r in res if r["name"] is not None and "." not in r["name"]
+        ]
         return idents
 
     def get_stmt_idents_ctx(self, stmt, del_set=[]):
@@ -96,7 +101,7 @@ def get_stmt_idents_ctx(self, stmt, del_set=[]):
         if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)):
             stored_idents.append(stmt.name)
             func_names.append(stmt.name)
-            #for arg in stmt.args.args: 
+            # for arg in stmt.args.args:
             #    if isinstance(arg.annotation, ast.Name):
             #        loaded_idents.append(arg.annotation.id)
             #    if isinstance(arg.annotation, ast.Attribute):
@@ -106,37 +111,38 @@ def get_stmt_idents_ctx(self, stmt, del_set=[]):
             new_stmt.body = []
             ident_info = get_vars(new_stmt)
             for r in ident_info:
-                if r['name'] is None or "." in r['name'] or "_hidden_" in r['name']:
+                if r["name"] is None or "." in r["name"] or "_hidden_" in r["name"]:
                     continue
-                if r['usage'] == "load":
-                    loaded_idents.append(r['name']) 
+                if r["usage"] == "load":
+                    loaded_idents.append(r["name"])
             return stored_idents, loaded_idents, func_names
-        
+
         if isinstance(stmt, ast.ClassDef):
             stored_idents.append(stmt.name)
             func_names.append(stmt.name)
             return stored_idents, loaded_idents, func_names
 
-
         # if this is control flow statements, we should not visit its body to avoid duplicates
         # as they are already in the next blocks
 
         if isinstance(stmt, (ast.Import, ast.ImportFrom)):
             for alias in stmt.names:
                 if alias.asname is None:
-                    stored_idents += [alias.name.split('.')[0]]
+                    stored_idents += [alias.name.split(".")[0]]
                 else:
-                    stored_idents += [alias.asname.split('.')[0]]
+                    stored_idents += [alias.asname.split(".")[0]]
             return stored_idents, loaded_idents, []
 
         if isinstance(stmt, (ast.Try)):
             for handler in stmt.handlers:
                 if handler.name is not None:
                     stored_idents.append(handler.name)
-                    #print(ast.dump())
-                if isinstance( handler.type, ast.Name):
+                    # print(ast.dump())
+                if isinstance(handler.type, ast.Name):
                     loaded_idents.append(handler.type.id)
-                elif isinstance( handler.type, ast.Attribute) and isinstance(handler.type.value, ast.Name):
+                elif isinstance(handler.type, ast.Attribute) and isinstance(
+                    handler.type.value, ast.Name
+                ):
                     loaded_idents.append(handler.type.value.id)
             return stored_idents, loaded_idents, []
         if isinstance(stmt, ast.Global):
@@ -145,36 +151,36 @@ def get_stmt_idents_ctx(self, stmt, del_set=[]):
             return stored_idents, loaded_idents, []
 
         visit_node = stmt
-        if isinstance(visit_node,(ast.If, ast.IfExp)):
-            #visit_node.body = []
-            #visit_node.orlse=[]
+        if isinstance(visit_node, (ast.If, ast.IfExp)):
+            # visit_node.body = []
+            # visit_node.orlse=[]
             visit_node = stmt.test
 
-        if isinstance(visit_node,(ast.With)):
+        if isinstance(visit_node, (ast.With)):
             visit_node.body = []
-            visit_node.orlse=[]
+            visit_node.orlse = []
 
-        if isinstance(visit_node,(ast.While)):
+        if isinstance(visit_node, (ast.While)):
             visit_node.body = []
-        if isinstance(visit_node,(ast.For)):
+        if isinstance(visit_node, (ast.For)):
             visit_node.body = []
 
         ident_info = get_vars(visit_node)
         for r in ident_info:
-            if r['name'] is None or "." in r['name'] or "_hidden_" in r['name']:
+            if r["name"] is None or "." in r["name"] or "_hidden_" in r["name"]:
                 continue
-            if r['usage'] == 'store':
-                stored_idents.append(r['name'])
+            if r["usage"] == "store":
+                stored_idents.append(r["name"])
             else:
-                loaded_idents.append(r['name'])
-            if r['usage'] == 'del':
-                del_set.append(r['name'])
+                loaded_idents.append(r["name"])
+            if r["usage"] == "del":
+                del_set.append(r["name"])
         return stored_idents, loaded_idents, []
 
     def compute_undefined_names(self, cfg, scope=["mod"]):
         """
         generate undefined names from given cfg
-        """ 
+        """
         all_blocks = cfg.get_all_blocks()
         reachable_table = {}
         id2block = {}
@@ -184,14 +190,14 @@ def compute_undefined_names(self, cfg, scope=["mod"]):
         block_ident_del = {}
 
         subscope_undefined_names = []
-        undefined_names_table = [] 
+        undefined_names_table = []
         undefined_names = []
-        scope_str  = ".".join(scope)
+        scope_str = ".".join(scope)
 
-        dom = self.compute_dom_old(all_blocks) 
-        #idom = self.compute_idom(all_blocks) 
+        dom = self.compute_dom_old(all_blocks)
+        # idom = self.compute_idom(all_blocks)
         for block in all_blocks:
-            #assign_records = self.get_assign_raw(block.statements)
+            # assign_records = self.get_assign_raw(block.statements)
             id2block[block.id] = block
             block_ident_gen[block.id] = []
             block_ident_use[block.id] = []
@@ -200,53 +206,85 @@ def compute_undefined_names(self, cfg, scope=["mod"]):
             ident_to_be_traced = []
             del_set = []
             for stmt in block.statements:
-                stored_idents, loaded_idents, scope_func_names = self.get_stmt_idents_ctx(stmt, del_set=del_set) 
+                stored_idents, loaded_idents, scope_func_names = (
+                    self.get_stmt_idents_ctx(stmt, del_set=del_set)
+                )
                 for ident in loaded_idents:
                     if ident[0:8] == "_hidden_":
                         # this is noise
                         continue
-                    if ident not in block_ident_gen[block.id] and ident not in stored_idents:
-                        ident_to_be_traced.append({
-                                        "name":ident, 
-                                        "scope": scope_str, 
-                                        "type":"unresolved",
-                                        "path_id": [], 
-                                        "path_src": []
-                                    }
-                                )
+                    if (
+                        ident not in block_ident_gen[block.id]
+                        and ident not in stored_idents
+                    ):
+                        ident_to_be_traced.append(
+                            {
+                                "name": ident,
+                                "scope": scope_str,
+                                "type": "unresolved",
+                                "path_id": [],
+                                "path_src": [],
+                            }
+                        )
                 block_ident_gen[block.id] += stored_idents
-                block_ident_unorder[block.id]  += scope_func_names
-            block_ident_use[block.id]  = ident_to_be_traced 
-            block_ident_del[block.id]  = del_set
+                block_ident_unorder[block.id] += scope_func_names
+            block_ident_use[block.id] = ident_to_be_traced
+            block_ident_del[block.id] = del_set
         for block in all_blocks:
             # number of stmts parsed
             for stmt in block.statements:
                 if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)):
-                    fun_undefined_names = self.compute_undefined_names(cfg.functioncfgs[(block.id, stmt.name)], scope = scope+[stmt.name])  
+                    fun_undefined_names = self.compute_undefined_names(
+                        cfg.functioncfgs[(block.id, stmt.name)],
+                        scope=scope + [stmt.name],
+                    )
                     fun_args = cfg.function_args[(block.id, stmt.name)]
-                    # exclude arguments 
-                    #fun_undefined_names = [name for name in fun_undefined_names if name not in tmp_avail_names] 
+                    # exclude arguments
+                    # fun_undefined_names = [name for name in fun_undefined_names if name not in tmp_avail_names]
                     fun_idx = block_ident_gen[block.id].index(stmt.name)
                     part_ident_gen = block_ident_gen[block.id][0:fun_idx]
                     # arguments its own name + part of gen set and unorder func/class/def names
-                    tmp_avail_names = fun_args + [stmt.name] + block_ident_unorder[block.id] +part_ident_gen
-                    fun_undefined_names = [name for name in fun_undefined_names if name["name"] not in tmp_avail_names]
+                    tmp_avail_names = (
+                        fun_args
+                        + [stmt.name]
+                        + block_ident_unorder[block.id]
+                        + part_ident_gen
+                    )
+                    fun_undefined_names = [
+                        name
+                        for name in fun_undefined_names
+                        if name["name"] not in tmp_avail_names
+                    ]
                     subscope_undefined_names += fun_undefined_names
 
                 elif isinstance(stmt, ast.ClassDef):
                     class_cfg = cfg.class_cfgs[stmt.name]
-                    cls_body_undefined_names = self.compute_undefined_names(class_cfg, scope = scope+[stmt.name])
+                    cls_body_undefined_names = self.compute_undefined_names(
+                        class_cfg, scope=scope + [stmt.name]
+                    )
                     cls_idx = block_ident_gen[block.id].index(stmt.name)
-                    part_ident_gen = block_ident_gen[block.id][0:cls_idx] 
-
-                    tmp_avail_names = part_ident_gen + [stmt.name] + block_ident_unorder[block.id]
-                    cls_body_undefined_names = [name for name in cls_body_undefined_names if name["name"] not in tmp_avail_names]
+                    part_ident_gen = block_ident_gen[block.id][0:cls_idx]
+
+                    tmp_avail_names = (
+                        part_ident_gen + [stmt.name] + block_ident_unorder[block.id]
+                    )
+                    cls_body_undefined_names = [
+                        name
+                        for name in cls_body_undefined_names
+                        if name["name"] not in tmp_avail_names
+                    ]
                     subscope_undefined_names += cls_body_undefined_names
             # process this block
             block_id = block.id
-            all_used_idents = block_ident_use[block_id]+subscope_undefined_names
-            #all_used_idents = []
-            idents_non_local = [ident for ident in all_used_idents if ident["name"] not in BUILT_IN_FUNCTIONS and ident["name"] not in self.global_names and ident["type"] == "unresolved"]
+            all_used_idents = block_ident_use[block_id] + subscope_undefined_names
+            # all_used_idents = []
+            idents_non_local = [
+                ident
+                for ident in all_used_idents
+                if ident["name"] not in BUILT_IN_FUNCTIONS
+                and ident["name"] not in self.global_names
+                and ident["type"] == "unresolved"
+            ]
             idents_non_local = idents_non_local
             idents_left = []
             dominators = dom[block_id]
@@ -258,39 +296,66 @@ def compute_undefined_names(self, cfg, scope=["mod"]):
                 for d_b_id in dominators:
                     if d_b_id == block_id:
                         continue
-                    if ident_name in block_ident_gen[d_b_id] and ident_name not in block_ident_del[d_b_id]:
+                    if (
+                        ident_name in block_ident_gen[d_b_id]
+                        and ident_name not in block_ident_del[d_b_id]
+                    ):
                         is_found = True
                         break
                 if is_found == False:
                     idents_left.append(ident)
-                # for those vars that cannot be found in its domiators, backtrace to 
-                # test if there is a path along wich the var is not defined. 
+                # for those vars that cannot be found in its domiators, backtrace to
+                # test if there is a path along wich the var is not defined.
             path_constraint = None
-            if len(block.predecessors) ==1:
+            if len(block.predecessors) == 1:
                 path_constraint = block.predecessors[0].exitcase
             for ident in idents_left:
                 visited = set()
                 exec_path = []
-                is_not_found = self.backward_query_new(block, ident["name"], visited, dom=dom, path=exec_path, block_ident_gen=block_ident_gen, condition_cons=path_constraint,
-                        entry_id=cfg.entryblock.id, block_ident_del=block_ident_del)
+                is_not_found = self.backward_query_new(
+                    block,
+                    ident["name"],
+                    visited,
+                    dom=dom,
+                    path=exec_path,
+                    block_ident_gen=block_ident_gen,
+                    condition_cons=path_constraint,
+                    entry_id=cfg.entryblock.id,
+                    block_ident_del=block_ident_del,
+                )
                 if is_not_found:
-                    #ident["path_id"].append(exec_path)
-                    #ident["path_src"].append([self.print_block(id2block[e_id]) for e_id in exec_path])
-                    is_found_here = self.hit_scope(ident['name'], block_ident_gen, block_ident_unorder)
+                    # ident["path_id"].append(exec_path)
+                    # ident["path_src"].append([self.print_block(id2block[e_id]) for e_id in exec_path])
+                    is_found_here = self.hit_scope(
+                        ident["name"], block_ident_gen, block_ident_unorder
+                    )
                     if is_found_here:
                         if scope_str == ident["scope"]:
                             ident["type"] = "local"
                         else:
                             ident["type"] = "foreign"
                     undefined_names += [ident]
-                    #if  ident[0] == "cell_data":
+                    # if  ident[0] == "cell_data":
                     #    print(self.print_block(block))
         return undefined_names
 
-    # if there exists one path that ident_name is not reachable 
-    # when the entered block is the entry block, it means the variable has not been found in this path. 
-    #Otherwise, the algorithm terminates at a previous block.
-    def backward_query_new(self, block, ident_name, visited, path = [], dom={},idom = {}, dom_stmt_res = [],  block_ident_gen={},  block_ident_del= {}, condition_cons=None, entry_id=1):
+    # if there exists one path that ident_name is not reachable
+    # when the entered block is the entry block, it means the variable has not been found in this path.
+    # Otherwise, the algorithm terminates at a previous block.
+    def backward_query_new(
+        self,
+        block,
+        ident_name,
+        visited,
+        path=[],
+        dom={},
+        idom={},
+        dom_stmt_res=[],
+        block_ident_gen={},
+        block_ident_del={},
+        condition_cons=None,
+        entry_id=1,
+    ):
         # condition constraints:
         visited.add(block.id)
         path += [block.id]
@@ -300,12 +365,12 @@ def backward_query_new(self, block, ident_name, visited, path = [], dom={},idom
             return True
 
         for suc_link in block.predecessors:
-            if condition_cons is not None and suc_link.exitcase is not None: 
-                this_condition = invert(condition_cons) 
-                this_txt = astor.to_source(this_condition) 
+            if condition_cons is not None and suc_link.exitcase is not None:
+                this_condition = invert(condition_cons)
+                this_txt = astor.to_source(this_condition)
                 this_edge_txt = astor.to_source(suc_link.exitcase)
                 # this path contracdict the constraints
-                if this_txt.strip()==this_edge_txt.strip():
+                if this_txt.strip() == this_edge_txt.strip():
                     continue
             parent_block = suc_link.source
             target_block = suc_link.target
@@ -315,7 +380,7 @@ def backward_query_new(self, block, ident_name, visited, path = [], dom={},idom
             if parent_block.id in visited or parent_block.id == block.id:
                 continue
             # if the block dominates the parent block, then give it up
-            if parent_block.id in dom and  block.id in dom[parent_block.id]:
+            if parent_block.id in dom and block.id in dom[parent_block.id]:
                 continue
             ##############
             if parent_block.id not in block_ident_gen:
@@ -323,7 +388,10 @@ def backward_query_new(self, block, ident_name, visited, path = [], dom={},idom
             # if the name id found in this gen set. then stop visiting this path
             if ident_name in block_ident_gen[parent_block.id]:
                 continue
-            if parent_block.id in block_ident_del and ident_name in block_ident_del[parent_block.id]:
+            if (
+                parent_block.id in block_ident_del
+                and ident_name in block_ident_del[parent_block.id]
+            ):
                 path.append(entry_id)
                 return True
             # if the name id is not found in the parent block and the parent is entry  return True
@@ -332,7 +400,16 @@ def backward_query_new(self, block, ident_name, visited, path = [], dom={},idom
                 return True
             # if continue to search
             # not in its parent gen set  then search from this path
-            return self.backward_query_new(parent_block, ident_name, visited, dom=dom, block_ident_gen=block_ident_gen, condition_cons=condition_cons, entry_id=entry_id, path = path) 
+            return self.backward_query_new(
+                parent_block,
+                ident_name,
+                visited,
+                dom=dom,
+                block_ident_gen=block_ident_gen,
+                condition_cons=condition_cons,
+                entry_id=entry_id,
+                path=path,
+            )
         return False
 
     def hit_scope(self, ident_name, block_ident_gen, block_ident_unorder):
@@ -349,31 +426,31 @@ def to_json(self):
         pass
 
     def print_block(self, block):
-        #for stmt in block.statements:
-        #print(block.get_source())
+        # for stmt in block.statements:
+        # print(block.get_source())
         return block.get_source()
 
-    # compute the dominators 
+    # compute the dominators
     def compute_idom(self, ssa_blocks):
         # construct the Graph
         entry_block = ssa_blocks[0]
         G = nx.DiGraph()
-        for block in ssa_blocks: 
+        for block in ssa_blocks:
             G.add_node(block.id)
             exits = block.exits
-            preds =  block.predecessors
-            for link in preds+exits:
+            preds = block.predecessors
+            for link in preds + exits:
                 G.add_edge(link.source.id, link.target.id)
-        #DF = nx.dominance_frontiers(G, entry_block.id)
+        # DF = nx.dominance_frontiers(G, entry_block.id)
         idom = nx.immediate_dominators(G, entry_block.id)
         return idom
 
     def RD(self, cfg_blocks):
-        # worklist 
+        # worklist
         # this is to express the conventional RD anaysis
-        # By using Out and Kill set to compute the latest assignment to a variable  
+        # By using Out and Kill set to compute the latest assignment to a variable
         entry_block = cfg_blocks[0]
-        Out[entry_block.id]  = set()
+        Out[entry_block.id] = set()
         # init the iterative algorithm
         for block in cfg_blocks:
             if block.id != entry_block.id:
@@ -381,18 +458,18 @@ def RD(self, cfg_blocks):
         changed = True
         while changed:
             for block in cfg_blocks:
-                # 
+                #
                 pre_links = block.predecessors
                 pre_blocks = [pl.source for pl in pre_links]
                 pre_outs = [out[pb.id] for pb in pre_blocks]
-                In[block.id] =  reduce(set.intersection, pre_outs) 
-                Out[block.id] = gen(B) (In[block.id]-kill[block.id])
+                In[block.id] = reduce(set.intersection, pre_outs)
+                Out[block.id] = gen(B)(In[block.id] - kill[block.id])
         # boundry
         return 0
 
     def compute_dom_old(self, ssa_blocks):
         entry_block = ssa_blocks[0]
-        id2blocks = {b.id:b for b in ssa_blocks}
+        id2blocks = {b.id: b for b in ssa_blocks}
         block_ids = list(id2blocks.keys())
         entry_id = entry_block.id
         N_blocks = len(ssa_blocks)
@@ -404,7 +481,7 @@ def compute_dom_old(self, ssa_blocks):
             else:
                 dom[b_id] = set(block_ids)
         # Iteratively eliminate nodes that are not dominators
-        #Dom(n) = {n} union with intersection over Dom(p) for all p in pred(n)
+        # Dom(n) = {n} union with intersection over Dom(p) for all p in pred(n)
         changed = True
         counter = 0
         while changed:
@@ -412,12 +489,16 @@ def compute_dom_old(self, ssa_blocks):
             for b_id in block_ids:
                 if b_id == entry_id:
                     continue
-                pre_block_ids = [pre_link.source.id for pre_link in id2blocks[b_id].predecessors ]
-                pre_dom_set = [dom[pre_b_id] for pre_b_id in pre_block_ids if pre_b_id in dom]
+                pre_block_ids = [
+                    pre_link.source.id for pre_link in id2blocks[b_id].predecessors
+                ]
+                pre_dom_set = [
+                    dom[pre_b_id] for pre_b_id in pre_block_ids if pre_b_id in dom
+                ]
                 new_dom_set = set([b_id])
 
                 if len(pre_dom_set) != 0:
-                    new_dom_tmp = reduce(set.intersection, pre_dom_set) 
+                    new_dom_tmp = reduce(set.intersection, pre_dom_set)
                     new_dom_set = new_dom_set.union(new_dom_tmp)
                 old_dom_set = dom[b_id]
 
@@ -425,4 +506,3 @@ def compute_dom_old(self, ssa_blocks):
                     changed = True
                     dom[b_id] = new_dom_set
         return dom
-
diff --git a/scalpel/__init__.py b/scalpel/__init__.py
index 961ac404..367f5db0 100644
--- a/scalpel/__init__.py
+++ b/scalpel/__init__.py
@@ -8,4 +8,4 @@
 """
 
 __all__ = ["cfg", "call_graph", "SSA", "core", "typeinfer", "import_graph", "rewriter"]
-__version__ = '1.0dev'
+__version__ = "1.0dev"
diff --git a/scalpel/call_graph/__init__.py b/scalpel/call_graph/__init__.py
index 66fb9f04..20728037 100644
--- a/scalpel/call_graph/__init__.py
+++ b/scalpel/call_graph/__init__.py
@@ -1,11 +1,10 @@
 """
-A call graph depicts calling relationships between subroutines in a computer program. 
-It is an essential component in most static analysis and can be leveraged to build more sophisicated applications such as profiling, vunerability propagation and refactoring. 
+A call graph depicts calling relationships between subroutines in a computer program.
+It is an essential component in most static analysis and can be leveraged to build more sophisicated applications such as profiling, vunerability propagation and refactoring.
 
-Please note scalpel.pycg module is a wrapper of [PyCG](https://pypi.org/project/pycg/). 
-It aims to construct the call graphs for given Python projects. 
+Please note scalpel.pycg module is a wrapper of [PyCG](https://pypi.org/project/pycg/).
+It aims to construct the call graphs for given Python projects.
 
-The basic node can be either a function, a class or a module. 
+The basic node can be either a function, a class or a module.
 The edges represent calling relationships between program nodes.
 """
-
diff --git a/scalpel/call_graph/pycg.py b/scalpel/call_graph/pycg.py
index 0f689f8f..96772bf0 100644
--- a/scalpel/call_graph/pycg.py
+++ b/scalpel/call_graph/pycg.py
@@ -6,14 +6,16 @@
 Call Graph Generation in Python. In 43rd International Conference on Software Engineering, ICSE '21, 25–28 May 2021.
 """
 
-from pycg.pycg import CallGraphGenerator as CallGraphGeneratorPyCG
-from pycg import formats
-import pycg
-import pkg_resources
 import packaging
+import pkg_resources
+import pycg
 from packaging import version
-pycg_version = pkg_resources.get_distribution('pycg').version
-if packaging.version.Version(pycg_version)>packaging.version.Version("0.0.3"):
+from pycg import formats
+from pycg.pycg import CallGraphGenerator as CallGraphGeneratorPyCG
+
+pycg_version = pkg_resources.get_distribution("pycg").version
+if packaging.version.Version(pycg_version) > packaging.version.Version("0.0.3"):
+
     class CallGraphGenerator(CallGraphGeneratorPyCG):
         def __init__(self, entry_points, package, max_iter=-1, operation="call-graph"):
             super().__init__(entry_points, package, max_iter, operation)
diff --git a/scalpel/cfg/__init__.py b/scalpel/cfg/__init__.py
index ddb2fd59..508a879d 100644
--- a/scalpel/cfg/__init__.py
+++ b/scalpel/cfg/__init__.py
@@ -7,4 +7,4 @@
 the form of an expression. Please see the example diagram a control flow graph ![Fibonacci CFG](https://raw.githubusercontent.com/SMAT-Lab/Scalpel/main/docs/_static/resources/cfg_example.png)
 """
 from .builder import CFGBuilder
-from .model import Block, Link, CFG
+from .model import CFG, Block, Link
diff --git a/scalpel/cfg/builder.py b/scalpel/cfg/builder.py
index 05cb2e56..73de5c60 100755
--- a/scalpel/cfg/builder.py
+++ b/scalpel/cfg/builder.py
@@ -4,9 +4,10 @@
 """
 
 import ast
-from .model import Block, Link, CFG
 import sys
+
 from ..core.func_call_visitor import get_func_calls
+from .model import CFG, Block, Link
 
 
 def is_py38_or_higher():
@@ -26,21 +27,24 @@ def invert(node):
     Returns:
         An ast node object containing the inverse (negation) of the input node.
     """
-    inverse = {ast.Eq: ast.NotEq,
-               ast.NotEq: ast.Eq,
-               ast.Lt: ast.GtE,
-               ast.LtE: ast.Gt,
-               ast.Gt: ast.LtE,
-               ast.GtE: ast.Lt,
-               ast.Is: ast.IsNot,
-               ast.IsNot: ast.Is,
-               ast.In: ast.NotIn,
-               ast.NotIn: ast.In}
+    inverse = {
+        ast.Eq: ast.NotEq,
+        ast.NotEq: ast.Eq,
+        ast.Lt: ast.GtE,
+        ast.LtE: ast.Gt,
+        ast.Gt: ast.LtE,
+        ast.GtE: ast.Lt,
+        ast.Is: ast.IsNot,
+        ast.IsNot: ast.Is,
+        ast.In: ast.NotIn,
+        ast.NotIn: ast.In,
+    }
 
     if type(node) == ast.Compare:
         op = type(node.ops[0])
-        inverse_node = ast.Compare(left=node.left, ops=[inverse[op]()],
-                                   comparators=node.comparators)
+        inverse_node = ast.Compare(
+            left=node.left, ops=[inverse[op]()], comparators=node.comparators
+        )
     elif isinstance(node, ast.BinOp) and type(node.op) in inverse:
         op = type(node.op)
         inverse_node = ast.BinOp(node.left, inverse[op](), node.right)
@@ -100,7 +104,7 @@ def build(self, name, tree, asynchr=False, entry_id=0, flattened=False):
                    program is being built, it is considered like a synchronous
                    'main' function.
             entry_id: Value for the id of the entry block of the CFG.
-            flattened:  if use k-v format for all CFGs while hiding its nested information. Key will be fully-qualified names. 
+            flattened:  if use k-v format for all CFGs while hiding its nested information. Key will be fully-qualified names.
 
         Returns:
             The CFG produced from the AST.
@@ -113,11 +117,11 @@ def build(self, name, tree, asynchr=False, entry_id=0, flattened=False):
         # Actual building of the CFG is done here.
         self.visit(tree)
         visited = []
-        self.clean_cfg(self.cfg.entryblock,visited)
+        self.clean_cfg(self.cfg.entryblock, visited)
 
         if flattened:
             self.cfg = self._flatten_cfg(self.cfg)
-            pass 
+            pass
         return self.cfg
 
     def _flatten_cfg(self, mod_cfg):
@@ -125,16 +129,23 @@ def _flatten_cfg(self, mod_cfg):
 
         def process_cfg(cfg, dotted_name=["mod"], name_type="mod"):
             fully_qualified_name = ".".join(dotted_name)
-            flattend_cfg[fully_qualified_name] = cfg 
-            for fun_name_tup, fun_cfg in  cfg.functioncfgs.items():
-                process_cfg(fun_cfg, dotted_name = dotted_name +[fun_name_tup[1]], name_type= "func")
-                
+            flattend_cfg[fully_qualified_name] = cfg
+            for fun_name_tup, fun_cfg in cfg.functioncfgs.items():
+                process_cfg(
+                    fun_cfg,
+                    dotted_name=dotted_name + [fun_name_tup[1]],
+                    name_type="func",
+                )
+
             for cls_name, cls_cfg in cfg.class_cfgs.items():
-                process_cfg(cls_cfg, dotted_name = dotted_name +[cls_name], name_type = "cls")
-    
+                process_cfg(
+                    cls_cfg, dotted_name=dotted_name + [cls_name], name_type="cls"
+                )
+
         process_cfg(mod_cfg)
 
         return flattend_cfg
+
     def build_from_src(self, name, src, flattened=False):
         """
         Build a CFG from some Python source code.
@@ -142,13 +153,13 @@ def build_from_src(self, name, src, flattened=False):
         Args:
             name: The name of the CFG being built.
             src: A string containing the source code to build the CFG from.
-            flattened:  if use k-v format for all CFGs while hiding its nested information. Key will be fully-qualified names. 
+            flattened:  if use k-v format for all CFGs while hiding its nested information. Key will be fully-qualified names.
 
 
         Returns:
             The CFG produced from the source code.
         """
-        tree = ast.parse(src, mode='exec')
+        tree = ast.parse(src, mode="exec")
         return self.build(name, tree, flattened=flattened)
 
     def build_from_file(self, name, filepath, flattened=False):
@@ -158,13 +169,13 @@ def build_from_file(self, name, filepath, flattened=False):
         Args:
             name: The name of the CFG being built.
             filepath: The path to the file containing the Python source code to build the CFG from.
-            flattened:  if use k-v format for all CFGs while hiding its nested information. Key will be fully-qualified names. 
+            flattened:  if use k-v format for all CFGs while hiding its nested information. Key will be fully-qualified names.
 
 
         Returns:
             The CFG produced from the source file.
         """
-        with open(filepath, 'r',encoding="utf8") as src_file:
+        with open(filepath, "r", encoding="utf8") as src_file:
             src = src_file.read()
             return self.build_from_src(name, src, flattened=flattened)
 
@@ -210,8 +221,7 @@ def new_loopguard(self):
         Returns:
             The block to be used as new loop guard.
         """
-        if (self.current_block.is_empty() and
-                len(self.current_block.exits) == 0):
+        if self.current_block.is_empty() and len(self.current_block.exits) == 0:
             # If the current block is empty and has no exits, it is used as
             # entry block (condition test) for the loop.
             loopguard = self.current_block
@@ -237,19 +247,20 @@ def new_functionCFG(self, node, asynchr=False, enclosing_block_id=-1):
         # added to the function CFGs of the current CFG.
         func_body = ast.Module(body=node.body)
         func_builder = CFGBuilder()
-        self.cfg.functioncfgs[(enclosing_block_id,node.name)] = func_builder.build(node.name,
-                                                                                   func_body,
-                                                                                   asynchr,
-                                                                                   self.current_id)
+        self.cfg.functioncfgs[(enclosing_block_id, node.name)] = func_builder.build(
+            node.name, func_body, asynchr, self.current_id
+        )
 
         def get_arg_names(argument_node):
             arg_names = []
             for node in ast.walk(argument_node):
                 if isinstance(node, ast.arg):
-                    arg_names.append( node.arg)
+                    arg_names.append(node.arg)
             return arg_names
 
-        self.cfg.function_args[(enclosing_block_id, node.name)] = get_arg_names(node.args)
+        self.cfg.function_args[(enclosing_block_id, node.name)] = get_arg_names(
+            node.args
+        )
         self.current_id = func_builder.current_id + 1
 
     def new_ClassCFG(self, node, asynchr=False):
@@ -274,19 +285,20 @@ def new_ClassCFG(self, node, asynchr=False):
             base_names.append(base.id)
         if node.name in self.cfg.class_cfgs and node.name in base_names:
             existing_class_cfg = self.cfg.class_cfgs[node.name]
-            new_class_cfg = func_builder.build(node.name,
-                                               func_body,
-                                               asynchr,
-                                               self.current_id)
-            new_class_cfg.entryblock.statements = new_class_cfg.entryblock.statements+existing_class_cfg.entryblock.statements
+            new_class_cfg = func_builder.build(
+                node.name, func_body, asynchr, self.current_id
+            )
+            new_class_cfg.entryblock.statements = (
+                new_class_cfg.entryblock.statements
+                + existing_class_cfg.entryblock.statements
+            )
             new_class_cfg.functioncfgs.update(existing_class_cfg.functioncfgs)
             new_class_cfg.function_args.update(existing_class_cfg.function_args)
-            self.cfg.class_cfgs[node.name]=new_class_cfg
+            self.cfg.class_cfgs[node.name] = new_class_cfg
         else:
-            self.cfg.class_cfgs[node.name] = func_builder.build(node.name,
-                                                                func_body,
-                                                                asynchr,
-                                                                self.current_id)
+            self.cfg.class_cfgs[node.name] = func_builder.build(
+                node.name, func_body, asynchr, self.current_id
+            )
 
         self.current_id = func_builder.current_id + 1
 
@@ -309,9 +321,11 @@ def clean_cfg(self, block, visited):
         if block.is_empty():
             for pred in block.predecessors:
                 for exit in block.exits:
-                    self.add_exit(pred.source, exit.target,
-                                  merge_exitcases(pred.exitcase,
-                                                  exit.exitcase))
+                    self.add_exit(
+                        pred.source,
+                        exit.target,
+                        merge_exitcases(pred.exitcase, exit.exitcase),
+                    )
                     # Check if the exit hasn't yet been removed from
                     # the predecessors of the target block.
                     if exit in exit.target.predecessors:
@@ -357,8 +371,8 @@ def visit_func(node):
             elif type(node) == ast.Subscript:
                 return node.value.id
 
-        #func = node.func
-        #func_name = visit_func(func)
+        # func = node.func
+        # func_name = visit_func(func)
         func_name = get_func_calls(node)[0]
         self.current_block.func_calls.append(func_name)
 
@@ -417,8 +431,8 @@ def visit_Try(self, node):
         try_block = self.new_block()
         self.add_exit(self.current_block, try_block, ast.Constant(True))
         n_else_stmts = len(node.orelse)
-        #else_block = self.new_block()
-        #self.add_exit(self.current_block, try_block, ast.Constant(True))
+        # else_block = self.new_block()
+        # self.add_exit(self.current_block, try_block, ast.Constant(True))
 
         # Create blocks for handlers
         n_handlers = len(node.handlers)
@@ -427,16 +441,16 @@ def visit_Try(self, node):
             h_block = self.new_block()
             handler_blocks += [h_block]
         after_try_block = self.new_block()
-        #self.add_exit(self.current_block, after_try_block, ast.Constant(False))
+        # self.add_exit(self.current_block, after_try_block, ast.Constant(False))
         # keep the original block
-        current_block = self.current_block 
+        current_block = self.current_block
         #
         self.current_block = try_block
 
         for child in node.body:
             self.visit(child)
 
-        if n_else_stmts>0:
+        if n_else_stmts > 0:
             else_block = self.new_block()
             self.add_exit(self.current_block, else_block)
             self.current_block = else_block
@@ -445,7 +459,6 @@ def visit_Try(self, node):
                 self.visit(child)
         self.add_exit(self.current_block, after_try_block)
 
-
         for i in range(n_handlers):
             self.current_block = current_block
             handler = node.handlers[i]
@@ -455,21 +468,21 @@ def visit_Try(self, node):
             # If encountered a break, exit will have already been added
             if not self.current_block.exits:
                 self.add_exit(self.current_block, after_try_block)
-            #self.add_exit(self.current_block, after_try_block)
+            # self.add_exit(self.current_block, after_try_block)
 
-        #if not self.current_block.exits:
+        # if not self.current_block.exits:
         #    self.add_exit(self.current_block, after_try_block)
         # Continue building the CFG in the after-if block.
 
         self.current_block = after_try_block
 
-        #populate the block in the try
-        #self.current_block = try_block
-        #for child in node.body:
+        # populate the block in the try
+        # self.current_block = try_block
+        # for child in node.body:
         #    self.visit(child)
-        #if not self.current_block.exits:
+        # if not self.current_block.exits:
         #   self.add_exit(self.current_block, after_try_block)
-        #self.current_block = after_try_block
+        # self.current_block = after_try_block
 
     def visit_If(self, node):
         # Add the If statement at the end of the current block.
@@ -506,7 +519,6 @@ def visit_If(self, node):
         # Continue building the CFG in the after-if block.
         self.current_block = afterif_block
 
-
     def visit_While(self, node):
         loop_guard = self.new_loopguard()
         self.current_block = loop_guard
@@ -521,8 +533,10 @@ def visit_While(self, node):
         self.after_loop_block_stack.append(afterwhile_block)
         inverted_test = invert(node.test)
         # Skip shortcut loop edge if while True:
-        if not (isinstance(inverted_test, NAMECONSTANT_TYPE) and
-                inverted_test.value is False):
+        if not (
+            isinstance(inverted_test, NAMECONSTANT_TYPE)
+            and inverted_test.value is False
+        ):
             self.add_exit(self.current_block, afterwhile_block, inverted_test)
         # Populate the while block.
         self.current_block = while_block
@@ -565,8 +579,8 @@ def visit_For(self, node):
         self.after_loop_block_stack.pop()
         self.curr_loop_guard_stack.pop()
 
-    # Async for loops and async with context managers. 
-    # They have the same fields as For and With, respectively. 
+    # Async for loops and async with context managers.
+    # They have the same fields as For and With, respectively.
     # Only valid in the body of an AsyncFunctionDef.
     # https://docs.python.org/3/library/ast.html
     def visit_AsyncFor(self, node):
@@ -596,6 +610,7 @@ def visit_AsyncFor(self, node):
         # Popping the current after loop stack,taking care of errors in case of nested for loops
         self.after_loop_block_stack.pop()
         self.curr_loop_guard_stack.pop()
+
     def visit_Break(self, node):
         assert len(self.after_loop_block_stack), "Found break not inside loop"
         self.add_exit(self.current_block, self.after_loop_block_stack[-1])
@@ -612,11 +627,15 @@ def visit_ImportFrom(self, node):
 
     def visit_FunctionDef(self, node):
         self.add_statement(self.current_block, node)
-        self.new_functionCFG(node, asynchr=False, enclosing_block_id=self.current_block.id)
+        self.new_functionCFG(
+            node, asynchr=False, enclosing_block_id=self.current_block.id
+        )
 
     def visit_AsyncFunctionDef(self, node):
         self.add_statement(self.current_block, node)
-        self.new_functionCFG(node, asynchr=True,enclosing_block_id=self.current_block.id)
+        self.new_functionCFG(
+            node, asynchr=True, enclosing_block_id=self.current_block.id
+        )
 
     def visit_ClassDef(self, node):
         self.add_statement(self.current_block, node)
@@ -654,9 +673,9 @@ def visit_With(self, node):
         afterwith_block = self.new_block()
         # no branch here
         # link with block and body of with
-        #print(with_block, afterwith_block)
-        #self.add_exit(with_block, afterwith_block)
-        # go to with block and create more 
+        # print(with_block, afterwith_block)
+        # self.add_exit(with_block, afterwith_block)
+        # go to with block and create more
         self.current_block = with_block
 
         # Populate the body of the with loop.
@@ -668,8 +687,8 @@ def visit_With(self, node):
         # Continue building the CFG in the after-with block.
         self.current_block = afterwith_block
 
-    # Async for loops and async with context managers. 
-    # They have the same fields as For and With, respectively. 
+    # Async for loops and async with context managers.
+    # They have the same fields as For and With, respectively.
     # Only valid in the body of an AsyncFunctionDef.
     # https://docs.python.org/3/library/ast.html
     def visit_AsyncWith(self, node):
@@ -684,9 +703,9 @@ def visit_AsyncWith(self, node):
         afterwith_block = self.new_block()
         # no branch here
         # link with block and body of with
-        #print(with_block, afterwith_block)
-        #self.add_exit(with_block, afterwith_block)
-        # go to with block and create more 
+        # print(with_block, afterwith_block)
+        # self.add_exit(with_block, afterwith_block)
+        # go to with block and create more
         self.current_block = with_block
 
         # Populate the body of the with loop.
@@ -697,4 +716,3 @@ def visit_AsyncWith(self, node):
             self.add_exit(self.current_block, afterwith_block)
         # Continue building the CFG in the after-with block.
         self.current_block = afterwith_block
-
diff --git a/scalpel/cfg/model.py b/scalpel/cfg/model.py
index 9e8fb074..61d7c5e2 100755
--- a/scalpel/cfg/model.py
+++ b/scalpel/cfg/model.py
@@ -3,16 +3,17 @@
 """
 
 import ast
-import sys 
 import re
+import sys
 import token
-import tokenize 
-import astor
+import tokenize
 
+import astor
 import graphviz as gv
 
 __all__ = ["Block", "Link", "CFG"]
 
+
 class Block(object):
     """
     Basic block in a control flow graph.
@@ -71,7 +72,8 @@ def is_empty(self):
             A boolean indicating if the block is empty (True) or not (False).
         """
         return len(self.statements) == 0
-    '''
+
+    """
     def strip_comment(self, src):
         clean_src = ""
 
@@ -103,7 +105,8 @@ def strip_comment(self, src):
             prev_toktype = toktype
             last_col = ecol
             last_lineno = elineno
-        '''
+        """
+
     def get_source(self):
         """
         Get a string containing the Python source code corresponding to the
@@ -153,7 +156,7 @@ def get_calls(self):
         """
         txt = ""
         for func_call_entry in self.func_calls:
-            txt += func_call_entry['name'] + '\n'
+            txt += func_call_entry["name"] + "\n"
         return txt
 
 
@@ -183,8 +186,7 @@ def __str__(self):
 
     def __repr__(self):
         if self.exitcase is not None:
-            return "{}, with exitcase {}".format(str(self),
-                                                 ast.dump(self.exitcase))
+            return "{}, with exitcase {}".format(str(self), ast.dump(self.exitcase))
         return str(self)
 
     def get_exitcase(self):
@@ -198,6 +200,7 @@ def get_exitcase(self):
         if self.exitcase:
             return astor.to_source(self.exitcase)
         return ""
+
     def __del__(self):
         self.source = None
         # Target block of the control flow jump.
@@ -206,7 +209,6 @@ def __del__(self):
         self.exitcase = None
 
 
-
 class CFG(object):
     """
     Control flow graph (CFG).
@@ -216,9 +218,10 @@ class CFG(object):
     possible 'final' blocks (blocks with no exits representing the end of the
     CFG).
     """
+
     def __init__(self, name, asynchr=False):
         """
-        The constructor of CFG class. Only name of this graph is required. 
+        The constructor of CFG class. Only name of this graph is required.
         """
         assert type(name) == str, "Name of a CFG must be a string"
         assert type(asynchr) == bool, "Async must be a boolean value"
@@ -239,33 +242,32 @@ def __init__(self, name, asynchr=False):
         self.class_args = {}
 
     def __del__(self):
-        pass 
-    
+        pass
+
     def __str__(self):
         return "CFG for {}".format(self.name)
 
     def remove_comments(self, src):
         pass
 
-    
-    
     def get_all_blocks(self):
         """
         Get a list of code blocks in this CFG; This is generated by BFS order.
 
         Returns:
-            A list of code blocks. 
+            A list of code blocks.
         """
         import queue
+
         all_blocks = []
-        
+
         visited = set()
-        working_queue = queue.Queue() 
+        working_queue = queue.Queue()
         working_queue.put(self.entryblock)
 
         while not working_queue.empty():
             block = working_queue.get()
-            # this block has been visited 
+            # this block has been visited
             if block.id in visited:
                 continue
             all_blocks.append(block)
@@ -274,9 +276,9 @@ def get_all_blocks(self):
                 if suc_link.target.id not in visited:
                     working_queue.put(suc_link.target)
         return all_blocks
-        #def dfs(start_block):
+        # def dfs(start_block):
         #    # non-recurisve implementation of DFS search
-   
+
     def __iter__(self):
         """
         Generator that yields all the blocks in the current graph, then
@@ -296,7 +298,7 @@ def __iter__(self):
 
         for subcfg in self.functioncfgs.values():
             yield from subcfg
-    
+
     def _visit_blocks(self, graph, block, visited=[], calls=True):
         # Don't visit blocks twice.
         if block.id in visited:
@@ -308,21 +310,25 @@ def _visit_blocks(self, graph, block, visited=[], calls=True):
         visited.append(block.id)
         # Show the block's function calls in a node.
         if calls and block.func_calls:
-            calls_node = str(block.id)+"_calls"
+            calls_node = str(block.id) + "_calls"
             calls_label = block.get_calls().strip()
-            graph.node(calls_node, label=calls_label,
-                       _attributes={'shape': 'box'})
-            graph.edge(str(block.id), calls_node, label="calls",
-                       _attributes={'style': 'dashed'})
+            graph.node(calls_node, label=calls_label, _attributes={"shape": "box"})
+            graph.edge(
+                str(block.id),
+                calls_node,
+                label="calls",
+                _attributes={"style": "dashed"},
+            )
         # Recursively visit all the blocks of the CFG.
         for exit in block.exits:
             self._visit_blocks(graph, exit.target, visited, calls=calls)
             edgelabel = exit.get_exitcase().strip()
             graph.edge(str(block.id), str(exit.target.id), label=edgelabel)
 
-    def _build_visual(self, format='pdf', calls=True):
-        graph = gv.Digraph(name='cluster'+self.name, format=format,
-                           graph_attr={'label': self.name})
+    def _build_visual(self, format="pdf", calls=True):
+        graph = gv.Digraph(
+            name="cluster" + self.name, format=format, graph_attr={"label": self.name}
+        )
         self._visit_blocks(graph, self.entryblock, visited=[], calls=False)
         return graph
 
@@ -340,4 +346,3 @@ def build_visual(self, format, calls=True, show=True):
         """
         graph = self._build_visual(format, calls)
         return graph
-
diff --git a/scalpel/core/_scope_graph.py b/scalpel/core/_scope_graph.py
index 089adbc2..2d2caacd 100644
--- a/scalpel/core/_scope_graph.py
+++ b/scalpel/core/_scope_graph.py
@@ -1,7 +1,9 @@
-import networkx as nx
-import ast 
+import ast
 import queue
 
+import networkx as nx
+
+
 class ScopeGraph(ast.NodeVisitor):
     def __init__(self) -> None:
         """
@@ -9,21 +11,21 @@ def __init__(self) -> None:
         """
         self.sg = nx.DiGraph()  # scope graph
         self.ig = nx.DiGraph()  # inheritance graph
-        self.imports = {}  # save information about imported names and scopes 
+        self.imports = {}  # save information about imported names and scopes
         self.MRO_graph = {}  # method resolution order
-        self.parent_relations = {} # parent relations among scopes
-        self.references = {}   # dictionary for refereced names 
-        self.declarations = {} # dictionary for declared names 
+        self.parent_relations = {}  # parent relations among scopes
+        self.references = {}  # dictionary for refereced names
+        self.declarations = {}  # dictionary for declared names
         self.current_scope_name = None
         pass
 
     def build(self, ast_tree):
         self.visit(ast_tree)
-        pass 
+        pass
 
     def visit_FunctionDef(self, node):
         self.declarations[self.current_scope_name].append(node.name)
-        
+
         save_scope_name = self.current_scope_name
         self.current_scope_name = node.name
 
@@ -35,10 +37,10 @@ def visit_FunctionDef(self, node):
 
         self.generic_visit(node)
         self.current_scope_name = save_scope_name
-        return node 
+        return node
 
     def visit_ClassDef(self, node):
-        # let's ignore basename is in the form of X.B.C which is annolying 
+        # let's ignore basename is in the form of X.B.C which is annolying
         for bc in node.bases:
             if hasattr(bc, "id"):
                 self.ig.add_edge(node.name, bc.id)
@@ -47,9 +49,8 @@ def visit_ClassDef(self, node):
                 else:
                     self.MRO_graph[node.name] = [bc.id]
 
-
         self.declarations[self.current_scope_name].append(node.name)
-        
+
         save_scope_name = self.current_scope_name
         self.current_scope_name = node.name
 
@@ -76,17 +77,19 @@ def visit_Module(self, node):
         self.generic_visit(node)
         self.current_scope_name = save_scope_name
         return node
+
     def visit_Name(self, node):
         if isinstance(node.ctx, (ast.Load, ast.Del)):
-            # this is 
+            # this is
             self.references[self.current_scope_name].append(node.id)
         elif isinstance(node.ctx, ast.Store):
             self.declarations[self.current_scope_name].append(node.id)
 
     def visit_Global(self, node):
         pass
+
     def visit_Nonlocal(self, node):
-        pass 
+        pass
 
     def visit_Import(self, node):
         for alias in node.names:
@@ -103,135 +106,133 @@ def visit_ImportFrom(self, node):
             else:
                 name = alias.name
             self.imports[current_scope_name].append(alias.name)
-        pass 
+        pass
 
     def resolve(name, working_scope):
         """
         Find the name in given working scope
         That is, a path with fewer parent transitions is more specific than a path with
-        more parent transitions. 
+        more parent transitions.
         """
-        pass 
+        pass
+
     def add_scope(self, scope_name, parent_name):
         self._add_scope_name(scope_name, parent_name)
-        
+
     def add_reference(self, scope_name, name, ctx):
         if ctx == "load":
-            self.references[scope_name] = name 
+            self.references[scope_name] = name
 
         elif ctx == "del":
             # deletion operation is deemed as using the reference
-            self.references[scope_name] = name 
- 
+            self.references[scope_name] = name
+
         elif ctx == "store":
             self._add_declared()
-            self.declarations[scope_name] = name 
+            self.declarations[scope_name] = name
 
         else:
             raise "Unknown context for given name reference"
 
     def _add_contained(self):
-        pass 
+        pass
 
     def _add_declared(self):
         pass
 
     def get_parent(self, scope_name):
-        #map scope to its parent scope 
+        # map scope to its parent scope
         if scope_name in self.parent_relations:
             return self.parent_relations[scope_name]
         raise "Failed to locate parent scope!"
 
     def print_out(self):
-
         print(self.MRO_graph)
 
-        #for k, v in self.references.items():
+        # for k, v in self.references.items():
         #    print(k, v )
-        #for k, v in self.declarations.items():
+        # for k, v in self.declarations.items():
         #    print(k, v )
 
-
     def MRO_resolve(self, start_name):
         if start_name not in self.MRO_graph:
             raise "Cannot locate the given name"
 
-        init_names = self.MRO_graph[start_name] 
-        
+        init_names = self.MRO_graph[start_name]
+
         cls_name_order = []
-        is_visited = set() 
+        is_visited = set()
         dfs_queue = queue.Queue()
 
         for name in init_names:
             dfs_queue.put(name)
-      
+
         while not dfs_queue.empty():
             cur_name = dfs_queue.get()
             if cur_name not in is_visited:
                 is_visited.add(cur_name)
             else:
-                continue 
+                continue
             cls_name_order.append(cur_name)
             if cur_name in self.MRO_graph:
                 tmp_names = self.MRO_graph[cur_name]
                 for name in tmp_names:
-                    dfs_queue.put(name) 
-                
-        #print(cls_name_order)
-         
-    
+                    dfs_queue.put(name)
+
+        # print(cls_name_order)
+
     def MRO_resolve_method(self, cls_name, method_name):
         """
         given current class name and method name;
-        using method resolution order to locate which class the method name is defined 
+        using method resolution order to locate which class the method name is defined
         """
-        if cls_name not in self.MRO_graph: 
-            #raise Exception("Cannot locate the given name", cls_name)
+        if cls_name not in self.MRO_graph:
+            # raise Exception("Cannot locate the given name", cls_name)
             return None
 
         init_names = self.MRO_graph[cls_name]
-        
+
         cls_name_order = []
-        is_visited = set() 
+        is_visited = set()
         dfs_queue = queue.Queue()
-        
-        target_cls_name = None 
+
+        target_cls_name = None
 
         for name in init_names:
             dfs_queue.put(name)
 
         while not dfs_queue.empty():
-            cur_name = dfs_queue.get() 
+            cur_name = dfs_queue.get()
             if cur_name not in is_visited:
                 is_visited.add(cur_name)
             else:
-                continue 
+                continue
             cls_name_order.append(cur_name)
             if cur_name not in self.declarations:
-                continue 
+                continue
             if method_name in self.declarations[cur_name]:
                 target_cls_name = cur_name
-                break 
+                break
 
             if cur_name in self.MRO_graph:
                 tmp_names = self.MRO_graph[cur_name]
                 for name in tmp_names:
-                    dfs_queue.put(name) 
+                    dfs_queue.put(name)
         return target_cls_name
 
     def test(self):
-        #print(self.MRO_garph 
-        pass 
+        # print(self.MRO_garph
+        pass
 
     def test_MRO_resolve(self, start_name):
-        #self.MRO_resolve(start_name)
+        # self.MRO_resolve(start_name)
         target_cls_name = self.MRO_resolve_method("D", "rk")
         print(target_cls_name)
-        pass 
+        pass
 
 
-# need to write resolve a method name 
+# need to write resolve a method name
 
-def create_MRO():
-    pass 
 
+def create_MRO():
+    pass
diff --git a/scalpel/core/class_visitor.py b/scalpel/core/class_visitor.py
index 2eac1278..f7d1ca50 100644
--- a/scalpel/core/class_visitor.py
+++ b/scalpel/core/class_visitor.py
@@ -1,6 +1,8 @@
 import ast
+
 from .fun_def_visitor import FunDefVisitor
 
+
 def get_keywords(node):
     args = node.args
     arg_names = []
@@ -14,9 +16,7 @@ class ClassVisitor(ast.NodeVisitor):
     def __init__(self):
         self.result = {}
 
-    def visit_FunctionDef(self, node): 
+    def visit_FunctionDef(self, node):
         kw_names = get_keywords(node)
         self.result[node.name] = kw_names
         return node
-
-
diff --git a/scalpel/core/fun_def_visitor.py b/scalpel/core/fun_def_visitor.py
index 7dbc2e35..4d2a0b96 100644
--- a/scalpel/core/fun_def_visitor.py
+++ b/scalpel/core/fun_def_visitor.py
@@ -7,7 +7,8 @@ def __init__(self):
 
     def visit(self, node):
         print(ast.dump(node))
-        self.result['name'] = node.name
+        self.result["name"] = node.name
         return node
+
     def visit_Args(self, node):
-        print('1111')
+        print("1111")
diff --git a/scalpel/core/func_call_visitor.py b/scalpel/core/func_call_visitor.py
index 336fc132..12336900 100644
--- a/scalpel/core/func_call_visitor.py
+++ b/scalpel/core/func_call_visitor.py
@@ -1,7 +1,7 @@
-import sys
 import ast
-from collections import deque
+import sys
 from ast import NodeVisitor
+from collections import deque
 from copy import deepcopy
 
 
@@ -23,7 +23,6 @@ def visit_Attribute(self, node):
         return node
 
     def param2str(self, param):
-
         def get_func(node):
             if type(node) == ast.Name:
                 return node.id
@@ -51,14 +50,14 @@ def get_func(node):
                 # currently, we will ignore the slices because we cannot track the type of the value.
                 # for instance,  a[something].fun() ->  a.fun()
                 # this sacrifice
-                return get_func(node.value)  
-            #elif type(node) == ast.JoinedStr:
+                return get_func(node.value)
+            # elif type(node) == ast.JoinedStr:
             #    return ""
             elif type(node) == ast.Attribute:
                 if type(node.value) in [ast.JoinedStr, ast.Constant]:
                     return node.attr
                 else:
-                    return get_func(node.value) + "." + node.attr 
+                    return get_func(node.value) + "." + node.attr
             elif type(node) == ast.Call:
                 return get_func(node.func)
             elif type(node) == ast.IfExp:
@@ -67,12 +66,12 @@ def get_func(node):
                 return ""
             elif type(node) == ast.UnaryOp:
                 return ""
-            #ast.UnaryOp
+            # ast.UnaryOp
             else:
-                #import astor
-                #print(astor.to_source(node))
+                # import astor
+                # print(astor.to_source(node))
                 raise Exception(str(type(node)))
-    
+
         if isinstance(param, ast.Subscript):
             return self.param2str(param.value)
         if isinstance(param, ast.Call):
@@ -80,9 +79,9 @@ def get_func(node):
         elif isinstance(param, ast.Name):
             return param.id
         elif isinstance(param, ast.Num):
-            # python 3.6  
+            # python 3.6
             return param.n
-            #return param.value
+            # return param.value
         elif isinstance(param, ast.List):
             return "List"
         elif isinstance(param, ast.ListComp):
@@ -105,7 +104,6 @@ def get_func(node):
             return "unknown"
 
     def visit_Call(self, node):
-
         tmp_fun_node = deepcopy(node)
         tmp_fun_node.args = []
         tmp_fun_node.keywords = []
@@ -113,11 +111,12 @@ def visit_Call(self, node):
         callvisitor = FuncCallVisitor()
         callvisitor.visit(tmp_fun_node)
 
-        call_info = {"name": callvisitor.name,
-                     "lineno": tmp_fun_node.lineno,
-                     "col_offset": tmp_fun_node.col_offset,
-                     "params": []
-                     }
+        call_info = {
+            "name": callvisitor.name,
+            "lineno": tmp_fun_node.lineno,
+            "col_offset": tmp_fun_node.col_offset,
+            "params": [],
+        }
         self.call_names += [call_info]
         for arg in node.args:
             call_info["params"] += [self.param2str(arg)]
@@ -142,7 +141,7 @@ def clear(self):
 
     @property
     def name(self):
-        return '.'.join(self._name)
+        return ".".join(self._name)
 
     @name.deleter
     def name(self):
@@ -152,7 +151,6 @@ def visit_Name(self, node):
         self._name.appendleft(node.id)
 
     def visit_Attribute(self, node):
-
         try:
             self._name.appendleft(node.attr)
             self._name.appendleft(node.value.id)
@@ -165,12 +163,12 @@ def visit_Call(self, node):
         self.generic_visit(node)
         return node
 
-
     def visit_Subscript(self, node):
-        # ingore subscription slice 
+        # ingore subscription slice
         self.visit(node.value)
         return node
 
+
 def get_args(node):
     arg_type = []
     for arg in node.args:
@@ -206,7 +204,7 @@ def get_args(node):
 
 
 def get_call_type(tree):
-    # how to remove 
+    # how to remove
     func_calls = []
     for node in ast.walk(tree):
         if isinstance(node, ast.Call):
@@ -217,7 +215,7 @@ def get_call_type(tree):
 
 
 def get_call_type(tree):
-    # how to remove 
+    # how to remove
     func_calls = []
     for node in ast.walk(tree):
         if isinstance(node, ast.Call):
diff --git a/scalpel/core/kw_visitor.py b/scalpel/core/kw_visitor.py
index 9e46c9e7..94d5fbcf 100644
--- a/scalpel/core/kw_visitor.py
+++ b/scalpel/core/kw_visitor.py
@@ -1,4 +1,6 @@
 import ast
+
+
 class KWVisitor(ast.NodeVisitor):
     def __init__(self):
         self.result = list()
diff --git a/scalpel/core/mnode.py b/scalpel/core/mnode.py
index a5dd89c6..cc9e2a88 100755
--- a/scalpel/core/mnode.py
+++ b/scalpel/core/mnode.py
@@ -2,34 +2,35 @@
 In this module, Scalplel provides the interface to users. Each of Python source
 files are fed into this module to generate a frontend object for both parsing
 and code instrumentation. In addition, scope information can also be given for
-fine-grained operations. 
+fine-grained operations.
 """
 import ast
-from ..core.vars_visitor import get_vars
-from ..core.func_call_visitor import get_func_calls
+
 from scalpel.core.util import UnitWalker
+
 from ..cfg.builder import CFGBuilder
+from ..core.func_call_visitor import get_func_calls
+from ..core.vars_visitor import get_vars
 
 
-def get_attr_name (node):
+def get_attr_name(node):
     if isinstance(node, ast.Call):
         # to be test
         return get_attr_name(node.func)
     if isinstance(node, ast.Name):
         return node.id
     elif isinstance(node, ast.Attribute):
-        return get_attr_name(node.value)+"."+node.attr
+        return get_attr_name(node.value) + "." + node.attr
     elif isinstance(node, ast.Subscript):
         return get_attr_name(node.value)
 
 
 class ImportRelation:
-
     def __init__(self):
         self.path = []
-        self.src : MNode
-        self.dest:MNode
-        self.payload = [] 
+        self.src: MNode
+        self.dest: MNode
+        self.payload = []
         self.stmts = []
 
 
@@ -40,6 +41,7 @@ class MNode:
     file.
 
     """
+
     def __init__(self, name):
         """
         Args:
@@ -49,12 +51,12 @@ def __init__(self, name):
         self.full_name = ""
         self.children = []
         self.parent = None
-        self.source = ''
+        self.source = ""
         self.ast = None
         self.class_pair = None
         self.node_type_dict = None
         self.node_type_gt = None
-        self.call_links = None 
+        self.call_links = None
 
     def __str__(self):
         """
@@ -62,7 +64,7 @@ def __str__(self):
         """
         return str(self.name)
 
-    def rewrite(self, scope = "mod"):
+    def rewrite(self, scope="mod"):
         """
         rewrite code
         """
@@ -71,7 +73,7 @@ def rewrite(self, scope = "mod"):
     def _read_scope(self, scope):
         pass
 
-    def parse_vars(self, scope = ""):
+    def parse_vars(self, scope=""):
         """
         Returns a list of variable records ranking by their line numbers
         Args:
@@ -85,11 +87,11 @@ def parse_vars(self, scope = ""):
         wanted_ast = self._retrieve_by_scope(self.ast, scope)
         results = get_vars(wanted_ast)
         return results
+
     def gen_import_relations():
         pass
 
-    def parse_func_calls(self, scope = ""):
-
+    def parse_func_calls(self, scope=""):
         """
         Returns a list of function calls ranking by their line numbers
         Args:
@@ -97,19 +99,19 @@ def parse_func_calls(self, scope = ""):
             means to retrieve the function named fun in the class A
         """
         wanted_ast = self._retrieve_by_scope(self.ast, scope)
-        results =  get_func_calls(wanted_ast)
+        results = get_func_calls(wanted_ast)
         return results
 
     def gen_ast(self):
         """
-        Build AST tree for th source 
+        Build AST tree for th source
         """
         try:
             self.ast = ast.parse(self.source)
         except Exception as e:
             self.ast = None
 
-    def _parse_func_defs(self, ast_node_lst, def_records, scope = "mod"):
+    def _parse_func_defs(self, ast_node_lst, def_records, scope="mod"):
         """
         Parse the function and class definitions in this module
         Args:
@@ -124,16 +126,28 @@ def _parse_func_defs(self, ast_node_lst, def_records, scope = "mod"):
         for node in ast_node_lst:
             def_info = {}
             if isinstance(node, ast.FunctionDef):
-                def_info = {"scope":scope, "name": node.name, "arg": [], "kws": [],
-                        "lineno":node.lineno, "col_offset":node.col_offset}
+                def_info = {
+                    "scope": scope,
+                    "name": node.name,
+                    "arg": [],
+                    "kws": [],
+                    "lineno": node.lineno,
+                    "col_offset": node.col_offset,
+                }
                 def_records.append(def_info)
                 self._parse_func_defs(node.body, def_records, scope=node.name)
 
             elif isinstance(node, ast.ClassDef):
-                #visit class body
+                # visit class body
                 # to consider class records
-                def_info = {"scope":scope, "name": node.name, "arg": [], "kws": [],
-                        "lineno":node.lineno, "col_offset":node.col_offset}
+                def_info = {
+                    "scope": scope,
+                    "name": node.name,
+                    "arg": [],
+                    "kws": [],
+                    "lineno": node.lineno,
+                    "col_offset": node.col_offset,
+                }
                 def_records.append(def_info)
                 self._parse_func_defs(node.body, def_records, scope=node.name)
                 pass
@@ -147,7 +161,7 @@ def _parse_func_defs(self, ast_node_lst, def_records, scope = "mod"):
 
     def _retrieve_by_scope(self, target_search_node, scope):
         """
-        retrieve an AST node by the scope directive. 
+        retrieve an AST node by the scope directive.
         Args:
             target_search_node: the AST node to be examined for entries in the
             given scope.
@@ -165,18 +179,18 @@ def _retrieve_by_scope(self, target_search_node, scope):
             if isinstance(node, ast.FunctionDef):
                 if node.name == scope:
                     return node
-                if scope.split('.')[0] == node.name:
-                    return self._retrieve_by_scope(node, scope.lstrip(node.name+'.')) 
+                if scope.split(".")[0] == node.name:
+                    return self._retrieve_by_scope(node, scope.lstrip(node.name + "."))
 
             elif isinstance(node, ast.ClassDef):
-                #visit class body
+                # visit class body
                 # to consider class records
                 if node.name == scope:
                     return node
-                if scope.split('.')[0] == node.name:
-                    return self._retrieve_by_scope(node, scope.lstrip(node.name+'.'))
-        #cfg = CFGBuilder().build("toy", self.module_ast)
-        #cfg.build_visual('cfg', 'pdf')
+                if scope.split(".")[0] == node.name:
+                    return self._retrieve_by_scope(node, scope.lstrip(node.name + "."))
+            # cfg = CFGBuilder().build("toy", self.module_ast)
+            # cfg.build_visual('cfg', 'pdf')
             # assignment statements are useful for assignment graph
             elif isinstance(node, ast.Assign):
                 pass
@@ -190,10 +204,10 @@ def parse_func_defs(self):
         Return a list of dictionaries, each of its item is a dictionary of
         function/class definition information.
         """
-        # mod : module 
+        # mod : module
         # func: function
         def_records = []
-        self._parse_func_defs(self.ast.body, def_records, scope = "mod")
+        self._parse_func_defs(self.ast.body, def_records, scope="mod")
         return def_records
 
     def parse_import_stmts(self):
@@ -211,50 +225,54 @@ def parse_import_stmts(self):
             if isinstance(stmt, ast.Import):
                 items = [nn.__dict__ for nn in stmt.names]
                 for d in items:
-                    if d['asname'] is None:  # alias name not found, use its imported name
-                        import_dict[d['name']] = d['name']
+                    if (
+                        d["asname"] is None
+                    ):  # alias name not found, use its imported name
+                        import_dict[d["name"]] = d["name"]
                     else:
-                        import_dict[d['asname']] = d['name'] # otherwise , use alias name
+                        import_dict[d["asname"]] = d[
+                            "name"
+                        ]  # otherwise , use alias name
             if isinstance(stmt, ast.ImportFrom):
                 m_name = stmt.module
-                if m_name is None and stmt.level== 1:
-                    m_name = '.'
-                if m_name is None and stmt.level== 2:
-                    m_name = '..' 
+                if m_name is None and stmt.level == 1:
+                    m_name = "."
+                if m_name is None and stmt.level == 2:
+                    m_name = ".."
                 items = [nn.__dict__ for nn in stmt.names]
                 for d in items:
-                    if d['asname'] is None: # alias name not found
-                        import_dict[d['name']] = m_name +'.'+d['name']
+                    if d["asname"] is None:  # alias name not found
+                        import_dict[d["name"]] = m_name + "." + d["name"]
                     else:
-                        import_dict[d['asname']] = m_name +'.'+d['name']
+                        import_dict[d["asname"]] = m_name + "." + d["name"]
         return import_dict
 
     def retrieve_meta(self, node):
-        results = {"assign_pairs":[], "other_calls":[]}
+        results = {"assign_pairs": [], "other_calls": []}
         assign_pairs = []
-        other_calls  = []
+        other_calls = []
         for node in ast.walk(node):
             if isinstance(node, ast.Assign):
                 call_lst = get_func_calls(node.value)
                 for target in node.targets:
                     var_info = get_vars(target)[0]
-                    assign_pairs += [{"var":var_info, "calls":call_lst}]
+                    assign_pairs += [{"var": var_info, "calls": call_lst}]
             elif isinstance(node, ast.AnnAssign):
                 call_lst = get_func_calls(node.value)
                 var_name = get_vars(node.target)[0]
-                assign_pairs += [{"var":var_info, "calls":call_lst}]
+                assign_pairs += [{"var": var_info, "calls": call_lst}]
             elif isinstance(node, ast.AugAssign):
                 call_lst = get_func_calls(node.value)
                 var_name = get_vars(node.target)[0]
-                assign_pairs += [{"var":var_info, "calls":call_lst}]
+                assign_pairs += [{"var": var_info, "calls": call_lst}]
             else:
                 call_lst = get_func_calls(node)
                 if call_lst not in other_calls:
                     other_calls.append(call_lst)
 
-        results["assign_pairs"]  = assign_pairs
-        results["other_calls"]  = other_calls
-        return results 
+        results["assign_pairs"] = assign_pairs
+        results["other_calls"] = other_calls
+        return results
 
     def _process_base_names(self, bases):
         base_names = []
@@ -265,7 +283,7 @@ def _process_base_names(self, bases):
                 base_names.append(get_attr_name(b_node))
         return base_names
 
-    #def parse_function_body(self):
+    # def parse_function_body(self):
     #    """
     #    Prase all function/class definitions
     #    """
diff --git a/scalpel/core/source_visitor.py b/scalpel/core/source_visitor.py
index 596c05f4..1155a0c7 100644
--- a/scalpel/core/source_visitor.py
+++ b/scalpel/core/source_visitor.py
@@ -1,4 +1,5 @@
 import ast
+
 from .class_visitor import ClassVisitor
 from .fun_def_visitor import FunDefVisitor
 
@@ -25,8 +26,8 @@ def visit_FunctionDef(self, node):
     def visit_ClassDef(self, node):
         visitor = ClassVisitor()
         visitor.visit(node)
-        if len(node.bases)>0:
-            if hasattr(node.bases[0],"id"):
+        if len(node.bases) > 0:
+            if hasattr(node.bases[0], "id"):
                 self.pair[node.name] = node.bases[0].id
         else:
             self.pair[node.name] = None
diff --git a/scalpel/core/util.py b/scalpel/core/util.py
index cee992e6..041eccf7 100644
--- a/scalpel/core/util.py
+++ b/scalpel/core/util.py
@@ -1,9 +1,10 @@
 import ast
-from  _ast import *
-import pkgutil
-import astor
 import os
+import pkgutil
 import sys
+from _ast import *
+
+import astor
 
 
 def iter_fields(node):
@@ -49,22 +50,36 @@ def iter_stmt_children(node):
 
 def find_local_modules(import_smts):
     smts = "\n".join(import_smts)
-    tree = ast.parse(smts, mode='exec')
-    search_path = ['.']
+    tree = ast.parse(smts, mode="exec")
+    search_path = ["."]
     module_names = set()
     for node in ast.walk(tree):
-        if isinstance(node, ast.Import) :
+        if isinstance(node, ast.Import):
             for nn in node.names:
-                module_names.add(nn.name.split('.')[0])
+                module_names.add(nn.name.split(".")[0])
         if isinstance(node, ast.ImportFrom):
-            if node.level==2:
-                search_path += ['..']
+            if node.level == 2:
+                search_path += [".."]
             if node.module is not None:
-                module_names.add(node.module.split('.')[0])
+                module_names.add(node.module.split(".")[0])
             else:
                 for nn in node.names:
                     module_names.add(nn.name)
-    module_name_plus = ['random', 'unittest', 'warning', 'os', 'pandas', 'IPython', 'seaborn', 'matplotlib', 'sklearn', 'numpy', 'scipy', 'math', 'matplotlib']
+    module_name_plus = [
+        "random",
+        "unittest",
+        "warning",
+        "os",
+        "pandas",
+        "IPython",
+        "seaborn",
+        "matplotlib",
+        "sklearn",
+        "numpy",
+        "scipy",
+        "math",
+        "matplotlib",
+    ]
     search_path = list(set(search_path))
     all_modules = [x[1] for x in pkgutil.iter_modules(path=search_path)]
     all_modules += list(sys.builtin_module_names) + module_name_plus
@@ -75,11 +90,11 @@ def find_local_modules(import_smts):
     return result
 
 
-def get_path_by_extension(root_dir, num_of_required_paths, flag='.ipynb'):
+def get_path_by_extension(root_dir, num_of_required_paths, flag=".ipynb"):
     paths = []
     for root, dirs, files in os.walk(root_dir):
-        files = [f for f in files if not f[0] == '.'] 
-        dirs[:] = [d for d in dirs if not d[0] == '.']
+        files = [f for f in files if not f[0] == "."]
+        dirs[:] = [d for d in dirs if not d[0] == "."]
         for file in files:
             if file.endswith(flag):
                 paths.append(os.path.join(root, file))
@@ -101,7 +116,7 @@ def __str__(self):
 
     def search_for_pos(self, stmt_lst, current_stmt):
         for i, stmt in enumerate(stmt_lst):
-    #        print(astor.to_source(stmt), astor.to_source(current_stmt))
+            #        print(astor.to_source(stmt), astor.to_source(current_stmt))
             if stmt == current_stmt:
                 return i
         return -1
@@ -120,17 +135,18 @@ def insert_stmts_before(self, new_stmts):
         if self.parent is not None and hasattr(self.parent, "body"):
             try:
                 pos = self.parent.body.index(self.node)
-               
-                self.parent.body[pos:pos+1] = new_stmts
+
+                self.parent.body[pos : pos + 1] = new_stmts
             except Exception as e:
                 raise Exception("Insertion Failure")
         else:
             raise Exception("Error!!")
+
     def insert_after(self, new_stmt):
         if self.parent is not None and hasattr(self.parent, "body"):
             try:
                 pos = self.parent.body.index(self.node)
-                self.parent.body.insert(pos+1, new_stmt)
+                self.parent.body.insert(pos + 1, new_stmt)
             except Exception as e:
                 raise Exception("Insertion Failure")
         else:
@@ -142,11 +158,13 @@ def remove():
     def replace():
         return 0
 
+
 def UnitWalker(module_node):
     # this code is adapted from the implementation of ast.walk
     # it does only handle statement level
     # offset to the first
     from collections import deque
+
     init_stmts = []
     for node in module_node.body:
         node.parent = module_node
@@ -157,13 +175,12 @@ def UnitWalker(module_node):
         node = todo.popleft()
         yield Unit(node, node.parent)
         if hasattr(node, "body"):
-            for ch_node in  node.body:
+            for ch_node in node.body:
                 ch_node.parent = node
                 todo.append(ch_node)
 
 
 class StmtIterator:
-
     def __init__(self, src):
         self.src = src
         self.ast = ast.parse(src)
@@ -173,7 +190,7 @@ def __init__(self, src):
     def __iter__(self):
         return self
 
-    def __next__(self): 
+    def __next__(self):
         # needs to return statement with the block information to allow
         # insertion and removal
         current_loc = 0
@@ -190,4 +207,3 @@ def remove(self):
 
     def replace(self, new_stmt):
         pass
-
diff --git a/scalpel/core/vars_visitor.py b/scalpel/core/vars_visitor.py
index f5e64f28..a90b5685 100644
--- a/scalpel/core/vars_visitor.py
+++ b/scalpel/core/vars_visitor.py
@@ -1,5 +1,6 @@
 import ast
 
+
 class VarsVisitor(ast.NodeVisitor):
     def __init__(self):
         self.result = list()
@@ -16,15 +17,14 @@ def _ctx2str(self, ctx):
 
     def visit_Name(self, node):
         var_info = {
-                      "name": node.id, 
-                      "lineno": node.lineno, 
-                      "col_offset": node.col_offset,
-                      "usage" : self._ctx2str(node.ctx)
-                  }
+            "name": node.id,
+            "lineno": node.lineno,
+            "col_offset": node.col_offset,
+            "usage": self._ctx2str(node.ctx),
+        }
 
         self.result.append(var_info)
 
-
     def visit_BoolOp(self, node):
         for v in node.values:
             self.visit(v)
@@ -77,7 +77,6 @@ def visit_DictComp(self, node):
         self.visit(node.key)
         self.visit(node.value)
 
-
     def visit_GeneratorComp(self, node):
         self.visit(node.elt)
         for gen in node.generators:
@@ -108,16 +107,16 @@ def visit_keyword(self, node):
     def visit_Attribute(self, node):
         full_name = self.get_attr_name(node)
 
-        var_info = {  
-                    "name": full_name, 
-                    "lineno": node.lineno, 
-                    "col_offset": node.col_offset,
-                    "usage" : self._ctx2str(node.ctx)
-                }
+        var_info = {
+            "name": full_name,
+            "lineno": node.lineno,
+            "col_offset": node.col_offset,
+            "usage": self._ctx2str(node.ctx),
+        }
         self.result.append(var_info)
         self.visit(node.value)
 
-    def get_attr_name (self, node):
+    def get_attr_name(self, node):
         if isinstance(node, ast.Call):
             # to be test
             return self.get_attr_name(node.func)
@@ -127,7 +126,7 @@ def get_attr_name (self, node):
             attr_name = self.get_attr_name(node.value)
             if attr_name is None:
                 return None
-            return attr_name +"."+node.attr
+            return attr_name + "." + node.attr
         elif isinstance(node, ast.Subscript):
             return self.get_attr_name(node.value)
         else:
@@ -135,9 +134,8 @@ def get_attr_name (self, node):
             return None
 
     def slicev(self, node):
-
         if isinstance(node, ast.Constant):
-            return 
+            return
         if isinstance(node, ast.Slice):
             if node.lower:
                 self.visit(node.lower)
@@ -149,14 +147,14 @@ def slicev(self, node):
             if node.dims:
                 for d in node.dims:
                     self.visit(d)
-        elif isinstance(node,ast.Tuple):
+        elif isinstance(node, ast.Tuple):
             for elt in node.elts:
                 self.visit(elt)
         elif isinstance(node, ast.UnaryOp):
             self.visit(node.operand)
         elif isinstance(node, ast.Name):
             self.visit(node)
-        # this is due to syntax change 
+        # this is due to syntax change
         elif hasattr(node, "value"):
             self.visit(node.value)
 
@@ -177,25 +175,24 @@ def visit_Tuple(self, node):
         for el in node.elts:
             self.visit(el)
 
-    def visit_FunctionDef(self, node): 
- 
+    def visit_FunctionDef(self, node):
         for stmt in node.body:
             self.visit(stmt)
-        #return node
+        # return node
 
     def visit_Assign(self, node):
         for target in node.targets:
-            #if isinstance(target, ast.Subscript):
+            # if isinstance(target, ast.Subscript):
             #    target.value.ctx = ast.Store()
             self.visit(target)
         if not isinstance(node.value, ast.Lambda):
             self.visit(node.value)
 
-            #for target in node.targets:
+            # for target in node.targets:
             #    if isinstance(target, ast.Subscript):
             #        target.value.ctx = ast.Store()
             #    self.visit(target)
-        #else:
+        # else:
         #    self.visit(target)
 
 
diff --git a/scalpel/file_system.py b/scalpel/file_system.py
index 06d732e0..06c5bc0e 100755
--- a/scalpel/file_system.py
+++ b/scalpel/file_system.py
@@ -4,32 +4,32 @@
 under development.
 """
 
+import ast
 import os
 from dataclasses import dataclass
-import ast
 
 from scalpel.core.source_visitor import SourceVisitor
 
 
 @dataclass
 class Node:
-    # a data structure that contain information for a module node 
+    # a data structure that contain information for a module node
     def __init__(self, name):
-        self.name:str = name
-        self.full_name:str = ""
-        self.children:list = []
-        self.parent:str = None
-        self.cargo:dict = {}
-        self.source:str = ''
+        self.name: str = name
+        self.full_name: str = ""
+        self.children: list = []
+        self.parent: str = None
+        self.cargo: dict = {}
+        self.source: str = ""
         self.ast = None
         self.class_pair = None
         self.node_type_dict = None
         self.node_type_gt = None
-        self.line_numbers:dict = {}
+        self.line_numbers: dict = {}
         self.static_assignments = None
         self.call_links = None
         self.imports = {}
-        self.abs_path = None 
+        self.abs_path = None
 
     def __str__(self):
         return str(self.name)
@@ -40,9 +40,9 @@ def __repr__(self):
 
 class FileSystem:
     """
-    This class is an upgraded implementation of import graph which will be removed in to later releases. 
-    It is a data structure that allows users to manipulate different files under a Python project. 
-    It leverage import relations in each of source files along with their absolute import name. 
+    This class is an upgraded implementation of import graph which will be removed in to later releases.
+    It is a data structure that allows users to manipulate different files under a Python project.
+    It leverage import relations in each of source files along with their absolute import name.
     """
 
     def __init__(self, entry_point, file_ext=".py"):
@@ -51,43 +51,42 @@ def __init__(self, entry_point, file_ext=".py"):
         Args:
         entry_point: the top level folder path such as "my-python-projects/homework1". The argument must not endswith slash!
         """
-        # entry_point is the top level module folder 
+        # entry_point is the top level module folder
         self.entry_point = entry_point
         # note that the entry_point must not ends with slash. The recommended one is "a/b/c.py"
         self.root = Node(os.path.basename(self.entry_point))
-      
-        self.file_ext = file_ext  # can be used to parse pyi files in the future 
-       
+
+        self.file_ext = file_ext  # can be used to parse pyi files in the future
+
     def _build_dir_tree(self, node):
         if os.path.isdir(node.name) is True:
             os.chdir(node.name)
-            
-            items = os.listdir('.')
+
+            items = os.listdir(".")
             for item in items:
                 child_node = Tree(item)
                 child_node.parent = node
                 self._build_dir_tree(child_node)
                 node.children.append(child_node)
-            os.chdir('..')
+            os.chdir("..")
         else:
             if node.name.endswith(self.file_ext):
-            
-                with open(node.name, 'r') as f:     
-                    try:  
+                with open(node.name, "r") as f:
+                    try:
                         source = f.read()
-                          
+
                     except:
                         source = ""
 
-                    node.source = source      
+                    node.source = source
                     try:
                         node.ast = ast.parse(source)
                     except:
-                        node.ast = None 
-                        
-                    #node.class_pair = pair
+                        node.ast = None
+
+                    # node.class_pair = pair
                     node.prefix = self.leaf2root(node)
-                    node.full_name = node.prefix # + '.' + node.name
+                    node.full_name = node.prefix  # + '.' + node.name
 
     def build_dir_tree(self):
         """
@@ -98,14 +97,14 @@ def build_dir_tree(self):
         os.chdir(working_dir)
         self._build_dir_tree(self.root)
         os.chdir(cwd)
-    
+
     def get_leaf_nodes(self):
         """
         To return all the leaf nodes in this tree. Each of leaf nodes represents a single Python script.
         """
         leaf_nodes = []
         working_queue = [self.root]
-        
+
         while len(working_queue) > 0:
             cur_node = working_queue.pop(0)
             # skip the git folder
@@ -117,19 +116,19 @@ def get_leaf_nodes(self):
         return leaf_nodes
 
     def format_all_imports(self, import_statement):
-        '''
+        """
         TODO:
         To convert all import path such as from ..X import fun as  its abs import path.
-        
-        '''
-        pass 
-        
-        '''
+
+        """
+        pass
+
+        """
         for key, val in self.module_name_imports.items():
             #print(key, val)
-            imported_to = key  # this is where from import statement is invoked  
-            imported_from = val[1] # from X import y  : here is X 
-            
+            imported_to = key  # this is where from import statement is invoked
+            imported_from = val[1] # from X import y  : here is X
+
             imported_from_scope = val[0]
             level = int(val[2])
             newly_introduced_name = val[-1]
@@ -141,33 +140,32 @@ def format_all_imports(self, import_statement):
             if level == 0:
                 # this is absolute import
                 foreign_imported_module = imported_from
-                prefix_str = None 
-                
-            elif level >0:     # if this is an init node 
+                prefix_str = None
+
+            elif level >0:     # if this is an init node
                 #print(imported_to_parts)
                 #imported_to =
                 prefix_parts = imported_to_parts[0:(n_parts-level)]
                 prefix_str = ".".join(prefix_parts)
                 foreign_imported_module =  prefix_str+ "." + imported_from
-        
-                #rename to its full name  
+
+                #rename to its full name
             if foreign_imported_module not in self.module_name_to_id:
-                foreign_imported_module =  foreign_imported_module + ".__init__" 
-            
+                foreign_imported_module =  foreign_imported_module + ".__init__"
+
             if foreign_imported_module in self.module_name_to_id:
-                
+
                 foreign_scope_id = self.module_name_to_id[foreign_imported_module]
                 #dest_node = self.scope_summaries[imported_from_scope]
                 #src_node = self.scope_summaries[foreign_scope_id]
-                
-            '''
 
-        
+            """
+
     def go_to_that_node(self, cur_node, visit_path):
         """
-        To locate a particular node from the tree from the current node given a visit path from import statement. 
-        For instance,  a visit path of [example, module_a, func] means, if we can locate the function definition `func` from module_a under example folder. 
-        The function tries to locate the given path using both relative and absolute import path. 
+        To locate a particular node from the tree from the current node given a visit path from import statement.
+        For instance,  a visit path of [example, module_a, func] means, if we can locate the function definition `func` from module_a under example folder.
+        The function tries to locate the given path using both relative and absolute import path.
         Args:
         visit_path: a list of names that represent different level python modules.
         """
@@ -202,8 +200,8 @@ def go_to_that_node(self, cur_node, visit_path):
                 if tmp_node is None:
                     break
         # we are still in the directory
-        if tmp_node is not None and tmp_node.name.endswith('.py') is not True:
-            tmp_node = self.find_node_by_name(tmp_node.children, '__init__.py')
+        if tmp_node is not None and tmp_node.name.endswith(".py") is not True:
+            tmp_node = self.find_node_by_name(tmp_node.children, "__init__.py")
         return tmp_node
 
     @staticmethod
@@ -225,9 +223,9 @@ def parse_import(tree):
                     items = [nn.__dict__ for nn in node.names]
                     for d in items:
                         if node.module is None:
-                            module_item_dict[node.level].append(d['name'])
+                            module_item_dict[node.level].append(d["name"])
                         else:
-                            module_item_dict[node.module].append(d['name'])
+                            module_item_dict[node.module].append(d["name"])
 
             return module_item_dict
         except AttributeError:
@@ -239,20 +237,20 @@ def extract_class_from_source(source):
         To parse import statements from the AST tree
         Args:
         source: source code text
-        Returns: class/function definitions, ast tree and alias pairs 
+        Returns: class/function definitions, ast tree and alias pairs
         """
-        tree = ast.parse(source, mode='exec')
+        tree = ast.parse(source, mode="exec")
         visitor = SourceVisitor()
         visitor.visit(tree)
-     
+
         return visitor.result, tree, visitor.pair
-    
+
     @staticmethod
     def leaf2root(node):
         """
-        To generate the full path to the top level module from the given node. 
+        To generate the full path to the top level module from the given node.
         Args:
-        node: the tree node 
+        node: the tree node
         Returns: the full path name in the form of dotted string.
         """
 
@@ -262,15 +260,15 @@ def leaf2root(node):
         while tmp_node is not None:
             path_to_root.append(tmp_node.name)
             tmp_node = tmp_node.parent
-        #  
-        if node.name == '__init__.py':
-            #path_to_root = path_to_root[1:]
+        #
+        if node.name == "__init__.py":
+            # path_to_root = path_to_root[1:]
             path_to_root[0] = path_to_root[0].split(".")[0]
             path_name = ".".join(reversed(path_to_root))
             return path_name
         else:
             path_name = ".".join(reversed(path_to_root[1:]))
-            path_name = "{}.{}".format(path_name, node.name.split('.')[0])
+            path_name = "{}.{}".format(path_name, node.name.split(".")[0])
             return path_name
 
     @staticmethod
@@ -289,6 +287,6 @@ def find_node_by_name(nodes, name):
         To locate a  node using node name
         """
         for node in nodes:
-            if node.name == name or node.name.rstrip('.py') == name:
+            if node.name == name or node.name.rstrip(".py") == name:
                 return node
         return None
diff --git a/scalpel/import_graph/__init__.py b/scalpel/import_graph/__init__.py
index ff5887c9..dd60ac6a 100644
--- a/scalpel/import_graph/__init__.py
+++ b/scalpel/import_graph/__init__.py
@@ -1,7 +1,7 @@
 """
-scalpel.import_graph is data structure for contructing an import graph of a python project. 
-An import graph represents the dependency relationship of  module files  in the given project. 
+scalpel.import_graph is data structure for contructing an import graph of a python project.
+An import graph represents the dependency relationship of  module files  in the given project.
 This information can be important to understand the import flow, hierarchy, encapsulation as well as software architecture.
 Each node in the import graph datastructure is a module file that can be manipulated to extract statements, function calls.
-All the leaf nodes in the import graph can be processed future. 
-"""
\ No newline at end of file
+All the leaf nodes in the import graph can be processed future.
+"""
diff --git a/scalpel/import_graph/import_graph.py b/scalpel/import_graph/import_graph.py
index f170f715..9cfba58d 100755
--- a/scalpel/import_graph/import_graph.py
+++ b/scalpel/import_graph/import_graph.py
@@ -1,22 +1,23 @@
 """
-Import Graph module 
+Import Graph module
 """
 
-import os
 import ast
+import os
+
 from scalpel.core.source_visitor import SourceVisitor
 
 
 class Tree:
     # a data structure that contain information for a tree node
-    # to be named 
+    # to be named
     def __init__(self, name):
         self.name = name
         self.full_name = ""
         self.children = []
         self.parent = None
         self.cargo = {}
-        self.source = ''
+        self.source = ""
         self.ast = None
         self.class_pair = None
         self.node_type_dict = None
@@ -32,8 +33,8 @@ def __str__(self):
 
 class ImportGraph:
     """
-    The Import Graph class is a data structure that allows users to manipulate different files under a Python project. 
-    It leverage import relations in each of source files. 
+    The Import Graph class is a data structure that allows users to manipulate different files under a Python project.
+    It leverage import relations in each of source files.
     """
 
     def __init__(self, entry_point):
@@ -43,29 +44,29 @@ def __init__(self, entry_point):
         entry_point: the top level folder path such as "my-python-projects/homework1".
         The argument must not endswith slash!
         """
-        # entry_point is the top level module folder 
+        # entry_point is the top level module folder
         self.entry_point = entry_point
         # note that the entry_point must not ends with slash. The recommended one is "a/b/c.py"
         self.root = Tree(os.path.basename(self.entry_point))
-       
+
     def _build_dir_tree(self, node):
         if os.path.isdir(node.name) is True:
             os.chdir(node.name)
-            items = os.listdir('.')
+            items = os.listdir(".")
             for item in items:
                 child_node = Tree(item)
                 child_node.parent = node
                 self._build_dir_tree(child_node)
                 node.children.append(child_node)
-            os.chdir('..')
+            os.chdir("..")
         else:
-            if node.name.endswith('.py'):
-                with open(node.name, 'r') as f:
+            if node.name.endswith(".py"):
+                with open(node.name, "r") as f:
                     source = f.read()
                     node.source = source
                     node.ast = ast.parse(source)
                     node.prefix = self.leaf2root(node)
-                    node.full_name = node.prefix + '.' + node.name
+                    node.full_name = node.prefix + "." + node.name
 
     def build_dir_tree(self):
         """
@@ -76,7 +77,7 @@ def build_dir_tree(self):
         os.chdir(working_dir)
         self._build_dir_tree(self.root)
         os.chdir(cwd)
-    
+
     def get_leaf_nodes(self):
         """
         To return all the leaf nodes in this tree. Each of leaf nodes represents a single Python script.
@@ -88,16 +89,16 @@ def get_leaf_nodes(self):
             # skip the git folder
             if str(cur_node) == ".git":
                 continue
-            if cur_node.name.endswith('.py'):
+            if cur_node.name.endswith(".py"):
                 leaf_nodes.append(cur_node)
             working_queue.extend(cur_node.children)
         return leaf_nodes
-        
+
     def go_to_that_node(self, cur_node, visit_path):
         """
-        To locate a particular node from the tree from the current node given a visit path from import statement. 
-        For instance,  a visit path of [example, module_a, func] means, if we can locate the function definition `func` from module_a under example folder. 
-        The function tries to locate the given path using both relative and absolute import path. 
+        To locate a particular node from the tree from the current node given a visit path from import statement.
+        For instance,  a visit path of [example, module_a, func] means, if we can locate the function definition `func` from module_a under example folder.
+        The function tries to locate the given path using both relative and absolute import path.
         Args:
         visit_path: a list of names that represent different level python modules.
         """
@@ -132,8 +133,8 @@ def go_to_that_node(self, cur_node, visit_path):
                 if tmp_node is None:
                     break
         # we are still in the directory
-        if tmp_node is not None and tmp_node.name.endswith('.py') is not True:
-            tmp_node = self.find_node_by_name(tmp_node.children, '__init__.py')
+        if tmp_node is not None and tmp_node.name.endswith(".py") is not True:
+            tmp_node = self.find_node_by_name(tmp_node.children, "__init__.py")
         return tmp_node
 
     @staticmethod
@@ -155,9 +156,9 @@ def parse_import(tree):
                     items = [nn.__dict__ for nn in node.names]
                     for d in items:
                         if node.module is None:
-                            module_item_dict[node.level].append(d['name'])
+                            module_item_dict[node.level].append(d["name"])
                         else:
-                            module_item_dict[node.module].append(d['name'])
+                            module_item_dict[node.module].append(d["name"])
 
             return module_item_dict
         except AttributeError:
@@ -169,20 +170,20 @@ def extract_class_from_source(source):
         To parse import statements from the AST tree
         Args:
         source: source code text
-        Returns: class/function definitions, ast tree and alias pairs 
+        Returns: class/function definitions, ast tree and alias pairs
         """
-        tree = ast.parse(source, mode='exec')
+        tree = ast.parse(source, mode="exec")
         visitor = SourceVisitor()
         visitor.visit(tree)
-     
+
         return visitor.result, tree, visitor.pair
-    
+
     @staticmethod
     def leaf2root(node):
         """
-        To generate the full path to the top level module from the given node. 
+        To generate the full path to the top level module from the given node.
         Args:
-        node: the tree node 
+        node: the tree node
         Returns: the full path name in the form of dotted string.
         """
         tmp_node = node
@@ -191,13 +192,13 @@ def leaf2root(node):
         while tmp_node is not None:
             path_to_root.append(tmp_node.name)
             tmp_node = tmp_node.parent
-        if node.name == '__init__.py':
+        if node.name == "__init__.py":
             # path_to_root = path_to_root[1:]
             path_name = ".".join(reversed(path_to_root))
             return path_name
         else:
             path_name = ".".join(reversed(path_to_root[1:]))
-            path_name = "{}.{}".format(path_name, node.name.split('.')[0])
+            path_name = "{}.{}".format(path_name, node.name.split(".")[0])
             return path_name
 
     @staticmethod
@@ -216,6 +217,6 @@ def find_node_by_name(nodes, name):
         To locate a  node using node name
         """
         for node in nodes:
-            if node.name == name or node.name.rstrip('.py') == name:
+            if node.name == name or node.name.rstrip(".py") == name:
                 return node
         return None
diff --git a/scalpel/rewriter.py b/scalpel/rewriter.py
index 1286e2f0..f903183d 100644
--- a/scalpel/rewriter.py
+++ b/scalpel/rewriter.py
@@ -1,36 +1,37 @@
-""" 
-The objective of rewriting module of Scalple is to provide APIs that allow users to rewrite their code implenmentation. 
+"""
+The objective of rewriting module of Scalple is to provide APIs that allow users to rewrite their code implenmentation.
 This can be used for various purposes such as code desugaring (removing code sugar usages), testing and code instrumentation.
 Code rewriting can bring great benefits such as API extraction and dynamic testing.
 """
 
+import ast
 import os
+import random
 import sys
-import ast
-import random 
+
 import astor
-from astor.source_repr import count  
+from astor.source_repr import count
 
-from scalpel.core.util import  UnitWalker
+from scalpel.core.util import UnitWalker
 from scalpel.core.vars_visitor import get_vars
 
 
-
 class Rewriter:
     """
-    The rewriter class contains a set of static methods. 
+    The rewriter class contains a set of static methods.
     """
+
     def __init__(self, src):
         self.src = src
         self.ast = ast.parse(src)
-        
+
     @staticmethod
-    def rewrite(src, rule_func= None):
+    def rewrite(src, rule_func=None):
         """
         To constuct a import graph.
         Args:
         src: the source code to be rewritten
-        rule_func: the function that should be applied for each of statements. 
+        rule_func: the function that should be applied for each of statements.
         The value of rule_func must not be None
         """
         if rule_func is None:
@@ -41,80 +42,93 @@ def rewrite(src, rule_func= None):
         for unit in Walker:
             new_stmts = rule_func(unit.node)
             if not isinstance(new_stmts, list):
-                raise Exception("The return type for rule_func function must be list type!")
+                raise Exception(
+                    "The return type for rule_func function must be list type!"
+                )
             unit.insert_stmts_before(new_stmts)
 
         new_ast = ast.fix_missing_locations(module_node)
         new_src = astor.to_source(new_ast)
-        return new_src 
+        return new_src
 
-    def random_var_renaming(self, new_name_candidates = [], K = 2):
-        
+    def random_var_renaming(self, new_name_candidates=[], K=2):
         all_vars = get_vars(self.ast, skip_call_name=True)
 
-        var_name_set = [var["name"] for var in all_vars if "." not in var["name"] ]
-        if K>len(var_name_set):
-            raise Exception("K is too large for given input")   
-             
-        if K>len(new_name_candidates):
-            raise Exception("K is too large for given new name candidates")   
-            
+        var_name_set = [var["name"] for var in all_vars if "." not in var["name"]]
+        if K > len(var_name_set):
+            raise Exception("K is too large for given input")
+
+        if K > len(new_name_candidates):
+            raise Exception("K is too large for given new name candidates")
+
         chosen_vars = random.sample(var_name_set, K)
         chosen_new_names = random.sample(new_name_candidates, K)
         renaming_pairs = zip(chosen_vars, chosen_new_names)
-        
-        
+
         renaming_dict = dict(renaming_pairs)
-        
+
         renamer = VarRenamer(renaming_dict)
-        
+
         self.ast = renamer.visit(self.ast)
-        
+
         self.ast = ast.fix_missing_locations(self.ast)
 
-    def unused_stmt_insertion(self):    
-        inserted_stmt = ast.Expr(ast.Call(ast.Name("print", ast.Load()), [ast.Constant("this is an unused statement")], []))
-       
+    def unused_stmt_insertion(self):
+        inserted_stmt = ast.Expr(
+            ast.Call(
+                ast.Name("print", ast.Load()),
+                [ast.Constant("this is an unused statement")],
+                [],
+            )
+        )
+
         num_insertion_point = 0
         walker = UnitWalker(self.ast)
         for unit in walker:
             num_insertion_point += 1
-        insertion_loc = random.randint(0, num_insertion_point-1)
+        insertion_loc = random.randint(0, num_insertion_point - 1)
         idx = 0
         walker = UnitWalker(self.ast)
-        for unit in walker: 
+        for unit in walker:
             if idx == insertion_loc:
                 unit.insert_stmts_before([inserted_stmt, unit.node])
-            
+
             idx += 1
         self.ast = ast.fix_missing_locations(self.ast)
-    
+
     def for2while(self, node):
         if isinstance(node, ast.For) and isinstance(node.target, ast.Name):
-            iter_object = ast.Name("_iter_obj_"+str(node.lineno), ast.Store())
-            counter_var = ast.Name("_counter_"+str(node.lineno), ast.Store())
-            max_counter_var = ast.Name("_len_of_iter_" +str(node.lineno), ast.Store())
+            iter_object = ast.Name("_iter_obj_" + str(node.lineno), ast.Store())
+            counter_var = ast.Name("_counter_" + str(node.lineno), ast.Store())
+            max_counter_var = ast.Name("_len_of_iter_" + str(node.lineno), ast.Store())
 
             iter_save_stmt = ast.Assign([iter_object], node.iter)
             counter_init_stmt = ast.Assign([counter_var], ast.Constant(0))
-            max_counter_init_stmt = ast.Assign([max_counter_var], ast.Call(ast.Name("len"), [iter_object], []))
-            test_node = ast.Compare(counter_var, [ast.Lt()], [max_counter_var]) 
+            max_counter_init_stmt = ast.Assign(
+                [max_counter_var], ast.Call(ast.Name("len"), [iter_object], [])
+            )
+            test_node = ast.Compare(counter_var, [ast.Lt()], [max_counter_var])
             counter_inc_stmt = ast.AugAssign(counter_var, ast.Add(), ast.Constant(1))
 
-            new_target_var =  ast.Subscript(iter_object,counter_var, ast.Load())
+            new_target_var = ast.Subscript(iter_object, counter_var, ast.Load())
 
-            #print(astor.to_source(iter_save_stmt))
-            #print(astor.to_source(counter_init_stmt))
-            #print(astor.to_source(max_counter_init_stmt))
-            #print(astor.to_source(test_node))
-            
-            new_body = node.body+[counter_inc_stmt]
+            # print(astor.to_source(iter_save_stmt))
+            # print(astor.to_source(counter_init_stmt))
+            # print(astor.to_source(max_counter_init_stmt))
+            # print(astor.to_source(test_node))
+
+            new_body = node.body + [counter_inc_stmt]
             while_node = ast.While(test_node, new_body, node.orelse)
             renaming_dict = {node.target.id: new_target_var}
             renamer = VarRenamer(renaming_dict)
             while_node = renamer.visit(while_node)
 
-            return [iter_save_stmt, counter_init_stmt, max_counter_init_stmt, while_node]
+            return [
+                iter_save_stmt,
+                counter_init_stmt,
+                max_counter_init_stmt,
+                while_node,
+            ]
         raise Exception("Invalid Input!")
         return node
 
@@ -122,72 +136,71 @@ def loop_exchange(self):
         Walker = UnitWalker(self.ast)
         for unit in Walker:
             if isinstance(unit.node, ast.For):
-                new_stmts = self.for2while(unit.node) 
+                new_stmts = self.for2while(unit.node)
                 unit.insert_stmts_before(new_stmts)
 
         self.ast = ast.fix_missing_locations(self.ast)
-    
+
     def get_src(self):
         return astor.to_source(self.ast)
-        
-        
+
+
 class VarRenamer(ast.NodeTransformer):
     """
     Here is the implementation of code rewriter at AST node level.
     """
-  
-    def __init__(self, renaming_dict, inserted_node =None):
+
+    def __init__(self, renaming_dict, inserted_node=None):
         self.renaming_dict = renaming_dict
-        self.inserted_node = inserted_node 
-    
+        self.inserted_node = inserted_node
+
     def visit_Name(self, node):
         if node.id in self.renaming_dict:
-            #if type(self.renaming_dict[node.id]) == ast.expr:
+            # if type(self.renaming_dict[node.id]) == ast.expr:
             #    print("testing")
-            
+
             target = self.renaming_dict[node.id]
             if type(target) == str:
-                node.id = target 
-                return node 
-            return target 
+                node.id = target
+                return node
+            return target
+
+        return node
 
-        return node 
-        
     def visit_arg(self, node):
         if node.arg in self.renaming_dict:
             node.arg = self.renaming_dict[node.arg]
         self.generic_visit(node)
         return node
-        
+
 
 class LoopExchanger(ast.NodeTransformer):
     """
     Here is the implementation of code rewriter at AST node level.
     """
+
     def visit_For(self, node):
         # let something to be assigned by node.iter
         iter_var = ast.Assign()
         # copy the body
-        # get the test 
-        #make a test
-        #new_while_node = ast.While(node.test, node.body, node.orelse)
+        # get the test
+        # make a test
+        # new_while_node = ast.While(node.test, node.body, node.orelse)
 
         return node
 
-   
 
 class ASTRewriter(ast.NodeTransformer):
     """
     Here is the implementation of code rewriter at AST node level.
     """
-  
+
     def __init__(self, src):
-  
         self.src = src
         self.ast = None
-        self.ast = ast.parse(self.src, mode='exec')
+        self.ast = ast.parse(self.src, mode="exec")
 
-    def search_for_pos(self, stmt_lst, pattern): 
+    def search_for_pos(self, stmt_lst, pattern):
         for i, stmt in enumerate(stmt_lst):
             if pattern(stmt):
                 return i
@@ -197,22 +210,24 @@ def rewrite(self):
         self.generic_visit(self.ast)
         return ast.fix_missing_locations(self.ast)
 
-    # once or all 
+    # once or all
     def insert(self):
         assert self.ast is not None
         assert isinstance(self.ast, ast.Module)
         self.insert_after()
+
     # once or all
 
     def insert_before(self, loc=""):
         assert self.ast is not None
         assert isinstance(self.ast, ast.Module)
         pos = self.search_for_pos(self.ast.body, self.pattern)
-        if pos<0:
+        if pos < 0:
             return self.ast
 
-        call_node = ast.Call(ast.Name(id='print',ctx=ast.Load()),
-                [ast.Constant("testing", None)], [])
+        call_node = ast.Call(
+            ast.Name(id="print", ctx=ast.Load()), [ast.Constant("testing", None)], []
+        )
         new_stmt = ast.Expr(call_node)
 
         self.ast.body.insert(pos, new_stmt)
@@ -223,35 +238,39 @@ def insert_after(self):
         assert self.ast is not None
         assert isinstance(self.ast, ast.Module)
         pos = self.search_for_pos(self.ast.body, self.pattern)
-        new_stmt = ast.Call(ast.Name(id='print',ctx=ast.Load()),
-                [ast.Name(id="testing", ctx=ast.Load())], [])
-        self.ast.body.insert(pos+1, new_stmt)
-        self.ast =  ast.fix_missing_locations(self.ast)
+        new_stmt = ast.Call(
+            ast.Name(id="print", ctx=ast.Load()),
+            [ast.Name(id="testing", ctx=ast.Load())],
+            [],
+        )
+        self.ast.body.insert(pos + 1, new_stmt)
+        self.ast = ast.fix_missing_locations(self.ast)
 
     def remove(self):
         assert self.ast is not None
         assert isinstance(self.ast, ast.Module)
         pos = self.search_for_pos(self.ast.body, self.pattern)
-        if pos<0:
+        if pos < 0:
             return self.ast
-        del self.ast.body[pos] 
-        self.ast =  ast.fix_missing_locations(self.ast)
+        del self.ast.body[pos]
+        self.ast = ast.fix_missing_locations(self.ast)
         return self.ast
 
     def replace(self):
         assert self.ast is not None
         assert isinstance(self.ast, ast.Module)
         pos = self.search_for_pos(self.ast.body, self.pattern)
-        if pos<0:
+        if pos < 0:
             return self.ast
-        call_node = ast.Call(ast.Name(id='print',ctx=ast.Load()),
-                [ast.Constant("testing1", None)], [])
+        call_node = ast.Call(
+            ast.Name(id="print", ctx=ast.Load()), [ast.Constant("testing1", None)], []
+        )
         new_stmt = ast.Expr(call_node)
         self.ast.body[pos] = new_stmt
         return ast.fix_missing_locations(self.ast)
 
     def visit_Name(self, node):
-        #if node.id in self.pattern['Name']:
+        # if node.id in self.pattern['Name']:
         #    new_name = self.pattern['Name'][node.id]
         #    node.id = new_name
         return node
@@ -259,6 +278,7 @@ def visit_Name(self, node):
     def visit_Attribute(self, node):
         self.generic_visit(node)
         return node
+
     def visit_FunctionDef(self, node):
         self.generic_visit(node)
         return node
@@ -266,8 +286,8 @@ def visit_FunctionDef(self, node):
     def get_func_name(self, node):
         if hasattr(node, "id"):
             return node.id
-        elif hasattr(node,"attr"):
-            return self.get_func_name(node.value)+"."+node.attr
+        elif hasattr(node, "attr"):
+            return self.get_func_name(node.value) + "." + node.attr
         else:
             pass
 
@@ -295,30 +315,39 @@ def visit_Delete(self, node):
     def visit_Assign(self, node):
         # to rewrite
 
-        if len(node.targets) ==1 and isinstance(node.value, ast.Lambda):
+        if len(node.targets) == 1 and isinstance(node.value, ast.Lambda):
             if isinstance(node.targets[0], ast.Name):
                 fun_name = node.targets[0].id
                 return_stmt = ast.Return(node.value.body)
                 body_stmts = [return_stmt]
                 decorator_list = []
-                return ast.FunctionDef(fun_name, node.value.args, body_stmts, decorator_list)
+                return ast.FunctionDef(
+                    fun_name, node.value.args, body_stmts, decorator_list
+                )
 
-        if len(node.targets) ==1 and isinstance(node.value, ast.ListComp):
-        
+        if len(node.targets) == 1 and isinstance(node.value, ast.ListComp):
             iter = node.value.generators[0].iter
-            ifs  = node.value.generators[0].ifs
+            ifs = node.value.generators[0].ifs
             target_name = node.value.generators[0].target.id
             target = ast.Name(id=target_name, ctx=ast.Store())
 
             orelse = []
             new_lst_name = "_hidden_" + node.targets[0].id
             if len(ifs) == 0:
-                append_attr = ast.Attribute(value=ast.Name(id=new_lst_name, ctx=ast.Load()),attr='append', ctx=ast.Load())  
+                append_attr = ast.Attribute(
+                    value=ast.Name(id=new_lst_name, ctx=ast.Load()),
+                    attr="append",
+                    ctx=ast.Load(),
+                )
                 append_call = ast.Call(append_attr, [node.value.elt], [])
                 append_stmt = ast.Expr(append_call)
                 body_stmts = [append_stmt]
             else:
-                append_attr = ast.Attribute(value=ast.Name(id=new_lst_name, ctx=ast.Load()),attr='append', ctx=ast.Load())  
+                append_attr = ast.Attribute(
+                    value=ast.Name(id=new_lst_name, ctx=ast.Load()),
+                    attr="append",
+                    ctx=ast.Load(),
+                )
                 append_call = ast.Call(append_attr, [node.value.elt], [])
                 append_stmt = ast.Expr(append_call)
                 if_body_stmts = [append_stmt]
@@ -349,13 +378,12 @@ def visit_While(self, node):
         self.generic_visit(node)
         return node
 
-
-    def visit_If(self, node): 
+    def visit_If(self, node):
         self.generic_visit(node)
         return node
 
-    def visit_IfExp(self, node): 
-        #if "if" in self.pattern["Stmt"]:
+    def visit_IfExp(self, node):
+        # if "if" in self.pattern["Stmt"]:
         #    alt_stmt = self.pattern["Stmt"]["if"]
         #    if alt_stmt is None:
         #        return None
@@ -401,6 +429,7 @@ def visit_Nonlocal(self, node):
     def visit_Expr(self, node):
         self.generic_visit(node)
         return node
+
     def visit_Pass(self, node):
         self.generic_visit(node)
         return node
@@ -412,4 +441,3 @@ def visit_Break(self, node):
     def visit_Continue(self, node):
         self.generic_visit(node)
         return node
-
diff --git a/scalpel/scope_graph/__init__.py b/scalpel/scope_graph/__init__.py
index 9ddfd25f..390936bb 100644
--- a/scalpel/scope_graph/__init__.py
+++ b/scalpel/scope_graph/__init__.py
@@ -1,5 +1,5 @@
 """
-In this sub-module, we implement the scope graph for Python programs. 
+In this sub-module, we implement the scope graph for Python programs.
 The overall idea is borrowed from the scope graph theory, please see references.
 For name binding and resolution, we follow the Local, Enclosing, Global and Built-in(LEGB) rule instead of the resolution calculus in the scope graph theory.
 
@@ -31,4 +31,4 @@
 2. Hendrik van Antwerpen, Casper Bach Poulsen, Arjen Rouvoet, and Eelco Visser. 2018. Scopes as types. Proc. ACM Program. Lang. 2, OOPSLA, Article 114 (November 2018), 30 pages. https://doi.org/10.1145/3276484
 
 """
-from .scope_graph import ScopeGraph 
+from .scope_graph import ScopeGraph
diff --git a/scalpel/scope_graph/scope_graph.py b/scalpel/scope_graph/scope_graph.py
new file mode 100644
index 00000000..2d2caacd
--- /dev/null
+++ b/scalpel/scope_graph/scope_graph.py
@@ -0,0 +1,238 @@
+import ast
+import queue
+
+import networkx as nx
+
+
+class ScopeGraph(ast.NodeVisitor):
+    def __init__(self) -> None:
+        """
+        The central concepts in the framework are declarations, references, and scopes
+        """
+        self.sg = nx.DiGraph()  # scope graph
+        self.ig = nx.DiGraph()  # inheritance graph
+        self.imports = {}  # save information about imported names and scopes
+        self.MRO_graph = {}  # method resolution order
+        self.parent_relations = {}  # parent relations among scopes
+        self.references = {}  # dictionary for refereced names
+        self.declarations = {}  # dictionary for declared names
+        self.current_scope_name = None
+        pass
+
+    def build(self, ast_tree):
+        self.visit(ast_tree)
+        pass
+
+    def visit_FunctionDef(self, node):
+        self.declarations[self.current_scope_name].append(node.name)
+
+        save_scope_name = self.current_scope_name
+        self.current_scope_name = node.name
+
+        if self.current_scope_name not in self.references:
+            self.references[self.current_scope_name] = []
+
+        if self.current_scope_name not in self.declarations:
+            self.declarations[self.current_scope_name] = []
+
+        self.generic_visit(node)
+        self.current_scope_name = save_scope_name
+        return node
+
+    def visit_ClassDef(self, node):
+        # let's ignore basename is in the form of X.B.C which is annolying
+        for bc in node.bases:
+            if hasattr(bc, "id"):
+                self.ig.add_edge(node.name, bc.id)
+                if node.name in self.MRO_graph:
+                    self.MRO_graph[node.name].append(bc.id)
+                else:
+                    self.MRO_graph[node.name] = [bc.id]
+
+        self.declarations[self.current_scope_name].append(node.name)
+
+        save_scope_name = self.current_scope_name
+        self.current_scope_name = node.name
+
+        if self.current_scope_name not in self.references:
+            self.references[self.current_scope_name] = []
+
+        if self.current_scope_name not in self.declarations:
+            self.declarations[self.current_scope_name] = []
+
+        self.generic_visit(node)
+        self.current_scope_name = save_scope_name
+        return node
+
+    def visit_Module(self, node):
+        save_scope_name = self.current_scope_name
+        self.current_scope_name = "Mod"
+
+        if self.current_scope_name not in self.references:
+            self.references[self.current_scope_name] = []
+
+        if self.current_scope_name not in self.declarations:
+            self.declarations[self.current_scope_name] = []
+
+        self.generic_visit(node)
+        self.current_scope_name = save_scope_name
+        return node
+
+    def visit_Name(self, node):
+        if isinstance(node.ctx, (ast.Load, ast.Del)):
+            # this is
+            self.references[self.current_scope_name].append(node.id)
+        elif isinstance(node.ctx, ast.Store):
+            self.declarations[self.current_scope_name].append(node.id)
+
+    def visit_Global(self, node):
+        pass
+
+    def visit_Nonlocal(self, node):
+        pass
+
+    def visit_Import(self, node):
+        for alias in node.names:
+            if alias.asname is not None:
+                name = alias.asname
+            else:
+                name = alias.name
+            self.imports[current_scope_name].append(alias.name)
+
+    def visit_ImportFrom(self, node):
+        for alias in node.names:
+            if alias.asname is not None:
+                name = alias.asname
+            else:
+                name = alias.name
+            self.imports[current_scope_name].append(alias.name)
+        pass
+
+    def resolve(name, working_scope):
+        """
+        Find the name in given working scope
+        That is, a path with fewer parent transitions is more specific than a path with
+        more parent transitions.
+        """
+        pass
+
+    def add_scope(self, scope_name, parent_name):
+        self._add_scope_name(scope_name, parent_name)
+
+    def add_reference(self, scope_name, name, ctx):
+        if ctx == "load":
+            self.references[scope_name] = name
+
+        elif ctx == "del":
+            # deletion operation is deemed as using the reference
+            self.references[scope_name] = name
+
+        elif ctx == "store":
+            self._add_declared()
+            self.declarations[scope_name] = name
+
+        else:
+            raise "Unknown context for given name reference"
+
+    def _add_contained(self):
+        pass
+
+    def _add_declared(self):
+        pass
+
+    def get_parent(self, scope_name):
+        # map scope to its parent scope
+        if scope_name in self.parent_relations:
+            return self.parent_relations[scope_name]
+        raise "Failed to locate parent scope!"
+
+    def print_out(self):
+        print(self.MRO_graph)
+
+        # for k, v in self.references.items():
+        #    print(k, v )
+        # for k, v in self.declarations.items():
+        #    print(k, v )
+
+    def MRO_resolve(self, start_name):
+        if start_name not in self.MRO_graph:
+            raise "Cannot locate the given name"
+
+        init_names = self.MRO_graph[start_name]
+
+        cls_name_order = []
+        is_visited = set()
+        dfs_queue = queue.Queue()
+
+        for name in init_names:
+            dfs_queue.put(name)
+
+        while not dfs_queue.empty():
+            cur_name = dfs_queue.get()
+            if cur_name not in is_visited:
+                is_visited.add(cur_name)
+            else:
+                continue
+            cls_name_order.append(cur_name)
+            if cur_name in self.MRO_graph:
+                tmp_names = self.MRO_graph[cur_name]
+                for name in tmp_names:
+                    dfs_queue.put(name)
+
+        # print(cls_name_order)
+
+    def MRO_resolve_method(self, cls_name, method_name):
+        """
+        given current class name and method name;
+        using method resolution order to locate which class the method name is defined
+        """
+        if cls_name not in self.MRO_graph:
+            # raise Exception("Cannot locate the given name", cls_name)
+            return None
+
+        init_names = self.MRO_graph[cls_name]
+
+        cls_name_order = []
+        is_visited = set()
+        dfs_queue = queue.Queue()
+
+        target_cls_name = None
+
+        for name in init_names:
+            dfs_queue.put(name)
+
+        while not dfs_queue.empty():
+            cur_name = dfs_queue.get()
+            if cur_name not in is_visited:
+                is_visited.add(cur_name)
+            else:
+                continue
+            cls_name_order.append(cur_name)
+            if cur_name not in self.declarations:
+                continue
+            if method_name in self.declarations[cur_name]:
+                target_cls_name = cur_name
+                break
+
+            if cur_name in self.MRO_graph:
+                tmp_names = self.MRO_graph[cur_name]
+                for name in tmp_names:
+                    dfs_queue.put(name)
+        return target_cls_name
+
+    def test(self):
+        # print(self.MRO_garph
+        pass
+
+    def test_MRO_resolve(self, start_name):
+        # self.MRO_resolve(start_name)
+        target_cls_name = self.MRO_resolve_method("D", "rk")
+        print(target_cls_name)
+        pass
+
+
+# need to write resolve a method name
+
+
+def create_MRO():
+    pass
diff --git a/scalpel/typeinfer/__init__.py b/scalpel/typeinfer/__init__.py
index 44f7a65a..365cb93e 100644
--- a/scalpel/typeinfer/__init__.py
+++ b/scalpel/typeinfer/__init__.py
@@ -2,4 +2,4 @@
 `scalpel.typeinfer` is provided for automatic type inference to facilitate static analysis for Python programs.
 The analyser takes a python file or the root folder of a whole package as input, and will output a dictionary of
 detailed type information for each variable. Type information is essential for many sophisticated static analysis tasks.
-"""
\ No newline at end of file
+"""
diff --git a/scalpel/typeinfer/analysers.py b/scalpel/typeinfer/analysers.py
index 107e64a2..4947ee61 100644
--- a/scalpel/typeinfer/analysers.py
+++ b/scalpel/typeinfer/analysers.py
@@ -6,22 +6,28 @@
 import ast
 import re
 import tokenize
-from typed_ast import ast3
+from typing import Dict, List
+
 import typeshed_client
-from typing import List, Dict
+from typed_ast import ast3
 
 from scalpel.cfg import CFGBuilder
-from scalpel.typeinfer.classes import BinaryOperation, ScalpelVariable, ScalpelFunction, ScalpelClass
-from scalpel.typeinfer.visitors import get_func_calls_type as get_func_calls, get_call_type
+from scalpel.SSA.const import SSA
+from scalpel.typeinfer.classes import (
+    BinaryOperation,
+    ScalpelClass,
+    ScalpelFunction,
+    ScalpelVariable,
+)
 from scalpel.typeinfer.utilities import (
-    get_type,
+    check_consistent_list_types,
     get_attr_name,
     get_built_in_types,
+    get_type,
     resolve_name,
-    check_consistent_list_types
 )
-
-from scalpel.SSA.const import SSA
+from scalpel.typeinfer.visitors import get_call_type
+from scalpel.typeinfer.visitors import get_func_calls_type as get_func_calls
 
 
 class BinaryOperatorMap:
@@ -57,8 +63,7 @@ def __getitem__(self, item):
         return None
 
     def chain_types(self):
-
-        #TODO: Chain together types of variables involved in binary operations to determine types
+        # TODO: Chain together types of variables involved in binary operations to determine types
         for variable, operation_list in self.hash.items():
             if isinstance(self._type_hash.get(variable), ast.Constant):
                 pass
@@ -68,6 +73,7 @@ class _StaticAnalyzer(ast.NodeVisitor):
     """
     The base class of analyzer, provides functions that read and parse a python program file
     """
+
     def __init__(self):
         self.file_name = None
 
@@ -118,13 +124,13 @@ def map(self):
                 module = []
             elif isinstance(node, ast.ImportFrom):
                 if node.module is not None:
-                    module = node.module.split('.')
+                    module = node.module.split(".")
             else:
                 continue
 
             for names in node.names:
                 if module:
-                    for name in names.name.split('.'):
+                    for name in names.name.split("."):
                         import_name = ".".join(module + [name])
                         if module:
                             # Importing from module
@@ -144,7 +150,9 @@ def get_imported_type(self, import_name: str):
             import_name: a fully qualified name of the function, the variable, etc.
 
         """
-        fully_qualified_name = self.typeshed_resolver.get_fully_qualified_name(import_name)
+        fully_qualified_name = self.typeshed_resolver.get_fully_qualified_name(
+            import_name
+        )
         if isinstance(fully_qualified_name, typeshed_client.parser.NameInfo):
             node = fully_qualified_name.ast
             if isinstance(node, ast.FunctionDef):
@@ -190,15 +198,10 @@ def map(self) -> List[ScalpelClass]:
                 for n in node.body:
                     if isinstance(n, ast.FunctionDef):
                         # We don't pass return type into function def since we will figure it out later
-                        methods.append(ScalpelFunction(
-                            name=n.name,
-                            line=n.lineno
-                        ))
-                class_definitions.append(ScalpelClass(
-                    name=class_name,
-                    methods=methods,
-                    inherits=inheritance
-                ))
+                        methods.append(ScalpelFunction(name=n.name, line=n.lineno))
+                class_definitions.append(
+                    ScalpelClass(name=class_name, methods=methods, inherits=inheritance)
+                )
 
         return class_definitions
 
@@ -225,10 +228,9 @@ def map(self) -> List[ScalpelFunction]:
             if isinstance(node, ast.FunctionDef):
                 # We don't pass return type into function def since we will figure it out later
                 # Also worth noting that this won't pick up on methods defined in classes
-                function_definitions.append(ScalpelFunction(
-                    name=node.name,
-                    line=node.lineno
-                ))
+                function_definitions.append(
+                    ScalpelFunction(name=node.name, line=node.lineno)
+                )
 
         return function_definitions
 
@@ -261,13 +263,13 @@ def map(self) -> List[ScalpelVariable]:
             if isinstance(node, ast.FunctionDef):
                 # Function arguments
                 for arg in node.args.args:
-                    if arg.arg != 'self':
+                    if arg.arg != "self":
                         variable = ScalpelVariable(
                             name=arg.arg,
                             function=node.name,
                             line=node.lineno,
                             type=any.__name__,
-                            is_arg=True
+                            is_arg=True,
                         )
                         variables.append(variable)
             elif isinstance(node, ast.Assign) and isinstance(node.targets[0], ast.Name):
@@ -279,11 +281,12 @@ def map(self) -> List[ScalpelVariable]:
                     name=variable_name,
                     function="",
                     line=node.lineno,
-                    type=assignment_type
+                    type=assignment_type,
                 )
-                variable_type = 'any'
-                if isinstance(node.value, ast.Call) and not isinstance(node.value.func, ast.Attribute):
-
+                variable_type = "any"
+                if isinstance(node.value, ast.Call) and not isinstance(
+                    node.value.func, ast.Attribute
+                ):
                     # Assignment is to a callable
                     if isinstance(node.value.func, ast.Name):
                         called = node.value.func.id  # Name of callable
@@ -294,20 +297,26 @@ def map(self) -> List[ScalpelVariable]:
 
                 elif isinstance(node.value, ast.Constant):
                     # String, int, float, boolean
-                    variable_type = type(node.value.value).__name__  # Determine specific type
+                    variable_type = type(
+                        node.value.value
+                    ).__name__  # Determine specific type
                 elif isinstance(node.value, ast.Dict):
                     # Dictionary
                     key_type = check_consistent_list_types(node.value.keys)
                     value_type = check_consistent_list_types(node.value.values)
                     variable_type = f"Dict[{key_type}, {value_type}]"
-                elif isinstance(node.value, ast.List) or isinstance(node.value, ast.Tuple):
+                elif isinstance(node.value, ast.List) or isinstance(
+                    node.value, ast.Tuple
+                ):
                     # List or tuple, check to see if types in list are constant
                     values = node.value.elts
                     value_type = check_consistent_list_types(values)
                     variable_type = f"{type(node.value).__name__}[{value_type}]"
                 elif isinstance(node.value, ast.ListComp):
                     variable_type = list.__name__
-                elif isinstance(node.value, ast.IfExp) or isinstance(node.value, ast.Compare):
+                elif isinstance(node.value, ast.IfExp) or isinstance(
+                    node.value, ast.Compare
+                ):
                     # Boolean, see heuristic 4
                     variable_type = bool.__name__
                 elif isinstance(node.value, ast.BinOp):
@@ -347,7 +356,6 @@ def map(self) -> BinaryOperatorMap:
 
         binary_operator_map = BinaryOperatorMap()
         for node in ast.walk(self.root):
-
             if isinstance(node, ast.BinOp):
                 # Check to see if we have an easily resolvable variable type since this will allow
                 # us to determine the type of all variables involved in the binary operation
@@ -383,7 +391,7 @@ def map(self) -> BinaryOperatorMap:
                         right=resolve_name(node.right),
                         right_ast_type=type(node.right),
                         operator=type(node.op),
-                        shared_type=assignment_type
+                        shared_type=assignment_type,
                     )
                 else:
                     # Not between two binary operations
@@ -393,7 +401,7 @@ def map(self) -> BinaryOperatorMap:
                         right=resolve_name(node.right),
                         right_ast_type=type(node.right),
                         operator=type(node.op),
-                        shared_type=assignment_type
+                        shared_type=assignment_type,
                     )
                 binary_operator_map.append(binary_operation)
 
@@ -404,6 +412,7 @@ class HeuristicParser(ast.NodeVisitor):
     """
     A heuristic NodeVisitor for assisting type inference
     """
+
     def __init__(self, node):
         self.node = node
         self.assign_nodes = []
@@ -469,14 +478,19 @@ def visit_Compare(self, node):
         right = node.comparators[0]
         left_type = get_type(left)
         right_type = get_type(right)
-        if left_type not in ["unknown", "ID", "subscript", "attr"] and right_type == "call":
+        if (
+            left_type not in ["unknown", "ID", "subscript", "attr"]
+            and right_type == "call"
+        ):
             self.type_hint_pairs += [(get_func_calls(right)[0], left_type)]
 
-        if right_type not in ["unknown", "ID", "subscript", "attr"] and left_type == "call":
+        if (
+            right_type not in ["unknown", "ID", "subscript", "attr"]
+            and left_type == "call"
+        ):
             self.type_hint_pairs += [(get_func_calls(left)[0], right_type)]
         if left_type == "call" and right_type == "call":
-            self.call_links = [(get_func_calls(left)[0],
-                                get_func_calls(right)[0])]
+            self.call_links = [(get_func_calls(left)[0], get_func_calls(right)[0])]
 
         self.generic_visit(node)
         return node
@@ -487,10 +501,16 @@ def visit_BinOp(self, node):
         right = node.right
         left_type = get_type(left)
         right_type = get_type(right)
-        if left_type not in ["unknown", "ID", "subscript", "attr"] and right_type == "call":
+        if (
+            left_type not in ["unknown", "ID", "subscript", "attr"]
+            and right_type == "call"
+        ):
             self.type_hint_pairs += [(get_func_calls(right)[0], left_type)]
 
-        if right_type not in ["unknown", "ID", "subscript", "attr"] and left_type == "call":
+        if (
+            right_type not in ["unknown", "ID", "subscript", "attr"]
+            and left_type == "call"
+        ):
             self.type_hint_pairs += [(get_func_calls(left)[0], right_type)]
 
         if left_type == "call" and right_type == "call":
@@ -523,6 +543,7 @@ class SourceSplitVisitor(ast.NodeVisitor):
     """
     ast NodeVisitor class for retrieving assignments
     """
+
     def __init__(self):
         self.assign_dict = {}
 
@@ -557,6 +578,7 @@ class ClassSplitVisitor(ast.NodeVisitor):
     """
     ast NodeVisitor class for retrieving compositions of a class: function nodes, bases and assignment records
     """
+
     def __init__(self):
         self.fun_nodes = []
         self.class_assign_records = {"init_arg_name_lst": []}
@@ -564,11 +586,13 @@ def __init__(self):
 
     def visit_FunctionDef(self, node):
         self.fun_nodes.append(node)
-        if node.name == '__init__':
+        if node.name == "__init__":
             for arg in node.args.args:
                 self.class_assign_records["init_arg_name_lst"] += [arg.arg]
             for stmt in ast.walk(node):
-                if isinstance(stmt, ast.Assign) and isinstance(stmt.targets[0], ast.Attribute):
+                if isinstance(stmt, ast.Assign) and isinstance(
+                    stmt.targets[0], ast.Attribute
+                ):
                     left = get_attr_name(stmt.targets[0])
                     if left not in self.class_assign_records:
                         self.class_assign_records[left] = [stmt.value]
@@ -578,7 +602,7 @@ def visit_ClassDef(self, node):
         self.bases = []
         for n in node.bases:
             if not isinstance(n, ast.Attribute) and not isinstance(n, ast.Subscript):
-                if hasattr(n, 'id'):
+                if hasattr(n, "id"):
                     self.bases.append(n.id)
 
         for tmp_node in node.body:
@@ -602,6 +626,7 @@ class ReturnStmtVisitor(ast.NodeVisitor):
     """
     Class for inferring function return types
     """
+
     def __init__(self, imports=None, assignments=None):
         self.ast_nodes = []
         self.assign_records = {}
@@ -672,7 +697,7 @@ def visit_FunctionDef(self, node):
 
     def visit_Yield(self, node):
         self.n_returns += 1
-        self.r_types += ['generator']
+        self.r_types += ["generator"]
         return node
 
     def infer_actual_return_value(self, actual_return_value):
@@ -696,14 +721,14 @@ def infer_actual_return_value(self, actual_return_value):
                 self.r_types += ["callable"]
                 return
 
-        init_val = actual_return_value #
+        init_val = actual_return_value  #
         type_val = get_type(init_val, imports=self.imports)
 
         if init_val is None and isinstance(actual_return_value, ast.Name):
             if return_value.id in self.args:
-                self.r_types += ['input']
+                self.r_types += ["input"]
             if return_value.id in self.args:
-                self.r_types += ['input']
+                self.r_types += ["input"]
                 return
 
         if isinstance(actual_return_value, ast.Name):
@@ -716,8 +741,7 @@ def infer_actual_return_value(self, actual_return_value):
         if isinstance(actual_return_value, ast.BinOp):
             heuristics = Heuristics()
             return_type = heuristics.heuristic_five_return(
-                assignments=self.assignments,
-                return_node=actual_return_value
+                assignments=self.assignments, return_node=actual_return_value
             )
             if isinstance(return_type, list):
                 for type in return_type:
@@ -728,10 +752,10 @@ def infer_actual_return_value(self, actual_return_value):
 
         if type_val in ["ID", "attr"]:
             # TODO: Is block.id correct here?
-            if type_val == 'attr' and isinstance(init_val.value, ast.Call):
+            if type_val == "attr" and isinstance(init_val.value, ast.Call):
                 # Check for super class call
                 if not isinstance(init_val.value.func, ast.Attribute):
-                    if init_val.value.func.id == 'super':
+                    if init_val.value.func.id == "super":
                         # Check super class attributes
                         attribute_name = f"super.{init_val.attr}"
                         super_assign = self.class_assign_records.get(attribute_name)
@@ -759,21 +783,27 @@ def infer_actual_return_value(self, actual_return_value):
         elif type_val == "call":
             func_name = get_func_calls(actual_return_value)
             func_name = func_name[0]
-            first_part = func_name.split('.')[0]
+            first_part = func_name.split(".")[0]
             if func_name == "self.__class__":
                 # same as class itself
-                self.r_types += ['self']
-            elif first_part != 'self' and first_part in self.args:
-                self.r_types += ['input']
-            elif first_part != 'self' and first_part in self.class_assign_records["init_arg_name_lst"]:
-                self.r_types += ['input']
+                self.r_types += ["self"]
+            elif first_part != "self" and first_part in self.args:
+                self.r_types += ["input"]
+            elif (
+                first_part != "self"
+                and first_part in self.class_assign_records["init_arg_name_lst"]
+            ):
+                self.r_types += ["input"]
             else:
                 self.stem_from.append(func_name)
 
         elif type_val == "subscript":
             if isinstance(init_val, ast.Name):
-                if init_val.id in self.args or init_val.id in self.class_assign_records["init_arg_name_lst"]:
-                    self.r_types += ['input']
+                if (
+                    init_val.id in self.args
+                    or init_val.id in self.class_assign_records["init_arg_name_lst"]
+                ):
+                    self.r_types += ["input"]
             else:
                 # Can't deduce type, return Any
                 self.r_types += [any.__name__]
@@ -808,6 +838,7 @@ def type_infer_CFG(self, node):
 
         ssa_analyzer = SSA()
         ssa_results, ident_const_dict = ssa_analyzer.compute_SSA(cfg)
+
         def get_return_value(block):
             for idx, stmt in enumerate(block.statements):
                 if isinstance(stmt, (ast.Return, ast.Yield)):
@@ -817,10 +848,12 @@ def get_return_value(block):
                         # use the first value
                         # TODO: consider multiple return values
                         ident_all_numbers = set(stmt_loaded_rec[stmt.value.id])
-                        if len(ident_all_numbers)>0:
+                        if len(ident_all_numbers) > 0:
                             const_values = []
                             for ident_no in ident_all_numbers:
-                                const_values.append(ident_const_dict[stmt.value.id, ident_no])
+                                const_values.append(
+                                    ident_const_dict[stmt.value.id, ident_no]
+                                )
                             return const_values
                     # this includes the case when the return identifier cannot be traced to a definition
                     return [stmt.value]
@@ -831,17 +864,14 @@ def get_return_value(block):
             if return_values:
                 for return_value in return_values:
                     self.infer_actual_return_value(return_value)
-                    #print(block.statements[0].lineno)
-                    #print(self.r_types)
-
+                    # print(block.statements[0].lineno)
+                    # print(self.r_types)
 
-
-
-            #if isinstance(return_value, ast.IfExp):
+            # if isinstance(return_value, ast.IfExp):
 
             #    self.backward(cfg, block, return_value.body)
             #    self.backward(cfg, block, return_value.orelse)
-            #else:
+            # else:
             #    self.backward(cfg, block, return_value)
 
     def query_assign_records(self, var_id):
@@ -863,31 +893,36 @@ def query_assign_records(self, var_id):
     def type_infer(self, node):
         # if node is None, return None??
         type_val = get_type(node)
-        if type_val == 'ID':
+        if type_val == "ID":
             right = self.query_assign_records(node.id)
             if right is not None:
                 self.type_infer(right)
             else:
                 pass
-        elif type_val == 'call':
+        elif type_val == "call":
             # Returns a function call
             func_name = get_func_calls(node)
             func_name = func_name[0]
-            first_part_name = func_name.split('.')[0]
+            first_part_name = func_name.split(".")[0]
 
-            if first_part_name in self.args or first_part_name in self.class_assign_records["init_arg_name_lst"]:
-                self.r_types += ['input']
+            if (
+                first_part_name in self.args
+                or first_part_name in self.class_assign_records["init_arg_name_lst"]
+            ):
+                self.r_types += ["input"]
             elif func_name in self.args:
-                self.r_types += ['input']
+                self.r_types += ["input"]
             elif func_name in self.class_assign_records["init_arg_name_lst"]:
-                self.r_types += ['input']
-            elif func_name in ['copy', 'deepcopy', 'copy.copy', 'copy.deepcopy']:
+                self.r_types += ["input"]
+            elif func_name in ["copy", "deepcopy", "copy.copy", "copy.deepcopy"]:
                 pass
             else:
-                self.stem_from.append(func_name)  # if this is a function call # self.r_types += [type_val]
+                self.stem_from.append(
+                    func_name
+                )  # if this is a function call # self.r_types += [type_val]
         elif type_val == "subscript":
             if isinstance(node.value, ast.Name) and node.value.id in self.args:
-                self.r_types += ['input']
+                self.r_types += ["input"]
         else:
             # Known type
             self.r_types += [type_val]
@@ -910,6 +945,7 @@ class Heuristics:
     """
     A set of heuristic function for assisting type inference
     """
+
     @staticmethod
     def heuristic_two(ast_tree, processed_file, assignments):
         """
@@ -923,30 +959,37 @@ def heuristic_two(ast_tree, processed_file, assignments):
             if isinstance(node, ast.FunctionDef):
                 func_args = node.args.args
                 func_name = node.name
-                function_param_types[func_name] = [{
-                    "possible_arg_types": [],
-                    "funcs": [],
-                    "type": "",
-                    "param_name": x.arg
-                } for x in func_args
+                function_param_types[func_name] = [
+                    {
+                        "possible_arg_types": [],
+                        "funcs": [],
+                        "type": "",
+                        "param_name": x.arg,
+                    }
+                    for x in func_args
                 ]
 
             elif hasattr(node, "value") and isinstance(node.value, ast.Call):
-
-                if isinstance(node.value.func, ast.Attribute) or isinstance(node.value.func, ast.Name):
+                if isinstance(node.value.func, ast.Attribute) or isinstance(
+                    node.value.func, ast.Name
+                ):
                     continue
 
-                if hasattr(node.value.func, 'id'):
+                if hasattr(node.value.func, "id"):
                     func_name = node.value.func.id
                     args = node.value.args
                     for i in range(len(args)):
                         arg = args[i]
                         if isinstance(arg, ast.Call):
-                            function_param_types[func_name][i]["funcs"].append(arg.func.id)
+                            function_param_types[func_name][i]["funcs"].append(
+                                arg.func.id
+                            )
                         elif isinstance(arg, ast.Name):
                             pass
                         else:
-                            function_param_types[func_name][i]["possible_arg_types"].append(type(arg.value).__name__)
+                            function_param_types[func_name][i][
+                                "possible_arg_types"
+                            ].append(type(arg.value).__name__)
 
         for function in function_param_types.values():
             for arg in function:
@@ -982,13 +1025,16 @@ def heuristic_five(self, import_mappings, processed_file, function_node):
                 right_operation = variable.binary_operation.right
                 operation = variable.binary_operation.op
                 # Check for involved parameters
-                involved_params = [i for i in param_list if self.in_bin_op(i, variable.binary_operation)]
+                involved_params = [
+                    i
+                    for i in param_list
+                    if self.in_bin_op(i, variable.binary_operation)
+                ]
 
                 if isinstance(left_operation, ast.BinOp):
                     # Greater than two values in the operation
                     bin_op_types = {}
                     while isinstance(left_operation, ast.BinOp):
-
                         if isinstance(right_operation, ast.Name):
                             # Named variable
                             if isinstance(left_operation.right, ast.Name):
@@ -997,7 +1043,9 @@ def heuristic_five(self, import_mappings, processed_file, function_node):
                                 if right_variable:
                                     bin_op_types[right_variable.type] = True
                             elif isinstance(left_operation.right, ast.Constant):
-                                bin_op_types[type(left_operation.right.value).__name__] = True
+                                bin_op_types[
+                                    type(left_operation.right.value).__name__
+                                ] = True
                         elif isinstance(right_operation, ast.Constant):
                             # Constant value, e.g. 25 as an integer of 'Hello World!' as a string
                             bin_op_types[type(right_operation.value).__name__] = True
@@ -1031,8 +1079,10 @@ def heuristic_five(self, import_mappings, processed_file, function_node):
                         right_name = right_operation.id
                         right_variable = assignment_dict.get(right_name)
                         if right_variable:
-                            if right_variable.type == "any" \
-                                    and (isinstance(operation, ast.Pow) or isinstance(operation, ast.Mod)):
+                            if right_variable.type == "any" and (
+                                isinstance(operation, ast.Pow)
+                                or isinstance(operation, ast.Mod)
+                            ):
                                 variable.type = "float"
                             else:
                                 variable.type = right_variable.type
@@ -1050,7 +1100,7 @@ def heuristic_five_return(self, assignments, return_node: ast.BinOp):
                 assignment_dict = {v.name: v for v in assignments}
                 assignment = assignment_dict.get(i.id)
                 if assignment:
-                    if assignment.type != 'any':
+                    if assignment.type != "any":
                         return assignment.type
             elif isinstance(i, ast.Constant):
                 # Constant, check its type
@@ -1080,7 +1130,9 @@ def heuristic_six(processed_file, function_node):
                     function_calls.append(node.func.id)
 
         # Get variables that are called
-        involved = [v for v in processed_file.static_assignments if v.name in function_calls]
+        involved = [
+            v for v in processed_file.static_assignments if v.name in function_calls
+        ]
         for variable in involved:
             variable.type = callable.__name__
 
@@ -1096,10 +1148,12 @@ def heuristic_seven(processed_file, function_node):
         for node in ast.walk(function_node):
             if isinstance(node, ast.Call):
                 if isinstance(node.func, ast.Name):
-                    if node.func.id == 'isinstance':
+                    if node.func.id == "isinstance":
                         # Check to see what the value being compared to is
                         variable, type_compared = node.args[0], node.args[1]
-                        if isinstance(variable, ast.Name) and isinstance(type_compared, ast.Name):
+                        if isinstance(variable, ast.Name) and isinstance(
+                            type_compared, ast.Name
+                        ):
                             type_list = is_instance_type_map.get(variable.id)
                             if type_list:
                                 if type_compared.id not in type_list:
@@ -1108,12 +1162,16 @@ def heuristic_seven(processed_file, function_node):
                                 is_instance_type_map[variable.id] = [type_compared.id]
 
         # Check static assignments for the variables involved in calls to isinstance
-        involved = [s for s in processed_file.static_assignments if s.name in is_instance_type_map]
+        involved = [
+            s
+            for s in processed_file.static_assignments
+            if s.name in is_instance_type_map
+        ]
         for variable in involved:
             is_instance_types = is_instance_type_map[variable.name]
 
             # Variable hasn't been resolved by any other heuristics
-            if variable.type == 'any':
+            if variable.type == "any":
                 if len(is_instance_types) > 1:
                     variable.type = is_instance_types
                 else:
@@ -1142,7 +1200,9 @@ def heuristic_eight(ast_tree, function_name, function_params):
                         inputs = [n.value for n in node.args if hasattr(n, "value")]
                         if len(inputs) == len(function_params):
                             for index, param in enumerate(function_params):
-                                param_type_map[param.name][type(inputs[index]).__name__] = True
+                                param_type_map[param.name][
+                                    type(inputs[index]).__name__
+                                ] = True
 
         # Assign parameter input types to parameters
         for param_name, param_types in param_type_map.items():
@@ -1150,7 +1210,7 @@ def heuristic_eight(ast_tree, function_name, function_params):
             type_values = list(param_types.keys())
             if len(type_values) == 1:
                 # Validate
-                if parameter.type == 'any':
+                if parameter.type == "any":
                     parameter.type = type_values[0]
                 elif not param_types.get(parameter.type):
                     # Mismatched inferred types
@@ -1159,7 +1219,7 @@ def heuristic_eight(ast_tree, function_name, function_params):
 
             else:
                 # See if we already inferred type from another heuristic, and check against the input type
-                if parameter.type != 'any':
+                if parameter.type != "any":
                     if parameter.type not in type_values:
                         # Bad input to function
                         # TODO: Raise a warning here?
@@ -1178,11 +1238,13 @@ def heuristic_nine(import_mappings, processed_file, function_node):
             function_node: The function node being checked
         """
         # work through params
-        for variable in [v for v in processed_file.static_assignments if v.type == 'any']:
-            regex_query = r'\b^(.{0,12}_{0,1}(count|counter|sum)_{0,1}.{0,12}|(int|num|sum|count|counter))$\b'
+        for variable in [
+            v for v in processed_file.static_assignments if v.type == "any"
+        ]:
+            regex_query = r"\b^(.{0,12}_{0,1}(count|counter|sum)_{0,1}.{0,12}|(int|num|sum|count|counter))$\b"
             regex = re.search(regex_query, variable.name)
             if regex:
-                variable.type = 'int'
+                variable.type = "int"
 
     def heuristic_eleven(self, processed_file, function_node):
         # Find what variables are in comparison operations and resolve types
@@ -1206,16 +1268,18 @@ def heuristic_twelve(function_node, function_params):
         # Collect parameter input types
         args = function_node.args
         kwarg = args.kwarg
-        kw_defaults=args.kw_defaults
+        kw_defaults = args.kw_defaults
         defaults = args.defaults
         position = 0
-        if len(defaults)>0 :
-            for arg in list(param_type_map.keys())[-len(defaults):]:
+        if len(defaults) > 0:
+            for arg in list(param_type_map.keys())[-len(defaults) :]:
                 # None is not helpful, not providing any hint
                 if hasattr(defaults[position], "value"):
                     if defaults[position].value is not None:
-                        param_type_map[arg][type(defaults[position].value).__name__] = True
-                        position+=1
+                        param_type_map[arg][
+                            type(defaults[position].value).__name__
+                        ] = True
+                        position += 1
 
         # Assign parameter input types to parameters
         for param_name, param_types in param_type_map.items():
@@ -1223,7 +1287,7 @@ def heuristic_twelve(function_node, function_params):
             type_values = list(param_types.keys())
             if len(type_values) == 1:
                 # Validate
-                if parameter.type == 'any':
+                if parameter.type == "any":
                     parameter.type = type_values[0]
                 elif not param_types.get(parameter.type):
                     # Mismatched inferred types
@@ -1232,7 +1296,7 @@ def heuristic_twelve(function_node, function_params):
 
             else:
                 # See if we already inferred type from another heuristic, and check against the input type
-                if parameter.type != 'any':
+                if parameter.type != "any":
                     if parameter.type not in type_values:
                         # Bad input to function
                         # TODO: Raise a warning here?
diff --git a/scalpel/typeinfer/classes.py b/scalpel/typeinfer/classes.py
index adb17ac3..42b391de 100644
--- a/scalpel/typeinfer/classes.py
+++ b/scalpel/typeinfer/classes.py
@@ -3,7 +3,7 @@
 """
 import ast
 from dataclasses import dataclass, field
-from typing import List, Dict, Optional,Any
+from typing import Any, Dict, List, Optional
 
 
 @dataclass
@@ -11,6 +11,7 @@ class TypeWarning:
     """
     Holds information about a type warning
     """
+
     warning: str  # Stores warning text
     line: int  # Line number of warning
     file: str  # Line where the warning was found
@@ -25,22 +26,31 @@ class ScalpelVariable:
     """
     Holds information about a variable
     """
+
     name: str  # The name of the variable
     function: str  # The name of the function where the variable is defined
     line: int  # The line number that the variable is defined at
     type: Optional[str] = None  # The type of the variable as a string
-    in_conditional: bool = False  # Whether the variable is involved in any conditional statements
-    in_equality: bool = False  # Whether the variable is involved in an equality statement
+    in_conditional: bool = (
+        False  # Whether the variable is involved in any conditional statements
+    )
+    in_equality: bool = (
+        False  # Whether the variable is involved in an equality statement
+    )
     is_callable: bool = False  # Whether the variable has been called as a function
-    called_methods: Optional[List[str]] = None  # List of methods called from the variable
+    called_methods: Optional[List[str]] = (
+        None  # List of methods called from the variable
+    )
     binary_operation: Optional[ast.BinOp] = None  # Binary operation assignment
     is_arg: bool = None  # Whether the variable is an argument in a function
 
+
 @dataclass
 class ScalpelFunction:
     """
     Holds information about a function
     """
+
     name: str  # The name of the function
     line: int  # The line number that the variable is defined at
     return_type: str = None  # The return type of the function
@@ -51,6 +61,7 @@ class ScalpelClass:
     """
     Holds information about a class
     """
+
     name: str  # The name of the class
     methods: List[ScalpelFunction]  # List of functions defined in the class
     inherits: List[str] = None  # Name of the class that this class inherits from
@@ -61,12 +72,15 @@ class BinaryOperation:
     """
     Holds information about a binary operation between two variables
     """
+
     left: str  # The left variable
     left_ast_type: any  # The AST type of the left variable
     right: str  # The right variable
     right_ast_type: any  # The AST type of the right variable
     operator: Any  # The binary operator used between the two variables
-    shared_type: Optional[str] = None  # Stores shared type between the variables in the operation
+    shared_type: Optional[str] = (
+        None  # Stores shared type between the variables in the operation
+    )
 
 
 @dataclass
@@ -74,6 +88,7 @@ class ProcessedFile:
     """
     Holds information about a processed file
     """
+
     type_dict: dict = field(default_factory=dict)
     type_gt: dict = field(default_factory=dict)
     type_stem_links: dict = field(default_factory=dict)
diff --git a/scalpel/typeinfer/typeinfer.py b/scalpel/typeinfer/typeinfer.py
index 38b4352f..ebe5015d 100644
--- a/scalpel/typeinfer/typeinfer.py
+++ b/scalpel/typeinfer/typeinfer.py
@@ -3,35 +3,37 @@
 files and infer types.
 """
 
-import os
 import ast
-import astunparse
-from typing import List
+import os
 from pprint import pprint
+from typing import List
 
-from scalpel.typeinfer.visitors import get_call_type
-from scalpel.typeinfer.classes import ProcessedFile, ScalpelVariable
+import astunparse
+
+from scalpel.import_graph.import_graph import ImportGraph, Tree
 from scalpel.typeinfer.analysers import (
-    ImportTypeMap,
-    SourceSplitVisitor,
     ClassSplitVisitor,
-    ReturnStmtVisitor,
     HeuristicParser,
     Heuristics,
-    VariableAssignmentMap
+    ImportTypeMap,
+    ReturnStmtVisitor,
+    SourceSplitVisitor,
+    VariableAssignmentMap,
 )
-from scalpel.import_graph.import_graph import Tree, ImportGraph
+from scalpel.typeinfer.classes import ProcessedFile, ScalpelVariable
 from scalpel.typeinfer.utilities import (
+    find_class_by_attr,
     generate_ast,
-    parse_module,
     get_api_ref_id,
     get_function_comment,
+    is_done,
     is_imported_fun,
     is_valid_call_link,
+    parse_module,
     rename_from_name,
-    is_done,
-    find_class_by_attr,
 )
+from scalpel.typeinfer.visitors import get_call_type
+
 
 def process_code_with_heuristics(node):
     def pick_type(type_lst):
@@ -57,7 +59,7 @@ def pick_type(type_lst):
         return type_hint_pair_list
 
     try:
-        tree = ast.parse(node.source, mode='exec')
+        tree = ast.parse(node.source, mode="exec")
         visitor = HeuristicParser(node)
         visitor.visit(tree)
         func_arg_db = {}
@@ -66,7 +68,7 @@ def pick_type(type_lst):
         all_call_names = []
         for pair in function_arg_types:
             name, arg_type = pair
-            name_parts = name.split('.')
+            name_parts = name.split(".")
             name = ".".join(name_parts)
             all_call_names += [name]
             if name in func_arg_db:
@@ -98,7 +100,7 @@ def __init__(self, name: str, entry_point: str):
         self.leaves = []
 
         # Build graph of the directory
-      
+
         self.import_graph = ImportGraph(self.entry_point)
         self.import_graph.build_dir_tree()
         self.leaves = self.import_graph.get_leaf_nodes()
@@ -117,19 +119,26 @@ def infer_types(self):
             node.static_assignments = processed_file.static_assignments
             node.line_numbers = processed_file.line_numbers
             node.imports = processed_file.imports
-           
+
         for node in self.leaves:
-            type_hint_pairs, client_call_link, all_call_names = process_code_with_heuristics(node)
+            (
+                type_hint_pairs,
+                client_call_link,
+                all_call_names,
+            ) = process_code_with_heuristics(node)
             for pair in type_hint_pairs:
                 if pair is None:
                     continue
                 function_name, t_val = pair
-                if t_val in ['call', 'input']:
+                if t_val in ["call", "input"]:
                     # TODO: Why do this?
                     continue
 
                 # Type hints are known
-                if function_name in node.node_type_dict and node.node_type_dict[function_name] is not None:
+                if (
+                    function_name in node.node_type_dict
+                    and node.node_type_dict[function_name] is not None
+                ):
                     if "input" in node.node_type_dict[function_name]:
                         node.node_type_dict[function_name] = [t_val]
                     if "3call" in node.node_type_dict[function_name]:
@@ -145,7 +154,10 @@ def infer_types(self):
                         continue
                     function1, function2 = call_pair
                     # Is this the same module
-                    if function1 in node.node_type_dict and function2 in node.node_type_dict:
+                    if (
+                        function1 in node.node_type_dict
+                        and function2 in node.node_type_dict
+                    ):
                         # They share with each other
                         function1_t_val = node.node_type_dict[function1]
                         function2_t_val = node.node_type_dict[function2]
@@ -157,7 +169,7 @@ def infer_types(self):
 
                 function_access_attribute_records = {}
                 for call_name in all_call_names:
-                    call_name_segments = call_name.split('.')
+                    call_name_segments = call_name.split(".")
                     if len(call_name_segments) < 2:
                         continue
 
@@ -165,13 +177,22 @@ def infer_types(self):
                         continue
 
                     if call_name_segments[0] not in function_access_attribute_records:
-                        function_access_attribute_records[call_name_segments[0]] = [call_name_segments[1]]
+                        function_access_attribute_records[call_name_segments[0]] = [
+                            call_name_segments[1]
+                        ]
                     else:
-                        function_access_attribute_records[call_name_segments[0]] += [call_name_segments[1]]
-
-                for function_name, access_attrs in function_access_attribute_records.items():
+                        function_access_attribute_records[call_name_segments[0]] += [
+                            call_name_segments[1]
+                        ]
+
+                for (
+                    function_name,
+                    access_attrs,
+                ) in function_access_attribute_records.items():
                     access_attrs = list(set(access_attrs))
-                    class_inferred = find_class_by_attr(list(node.node_type_dict.keys()), access_attrs)
+                    class_inferred = find_class_by_attr(
+                        list(node.node_type_dict.keys()), access_attrs
+                    )
                     if class_inferred is not None:
                         node.node_type_dict[function_name] = [class_inferred]
 
@@ -185,24 +206,35 @@ def infer_types(self):
                 # base: from the base class
                 # local: from the same module file
 
-                if from_where in ['self', 'base', 'local']:
+                if from_where in ["self", "base", "local"]:
                     from_name = rename_from_name(from_where, from_name, function_name)
-                    if from_name in node.node_type_dict and node.node_type_dict[from_name] is not None:
+                    if (
+                        from_name in node.node_type_dict
+                        and node.node_type_dict[from_name] is not None
+                    ):
                         t_vals_tmp = node.node_type_dict[from_name]
                         if is_valid_call_link(t_vals_tmp):
                             node.node_type_dict[function_name] += t_vals_tmp
 
                 # Might be from other modules
                 else:
-                    visit_path = from_where.split('.')
-                    if len(visit_path) == 1 and visit_path[0] == self.import_graph.root.name:
+                    visit_path = from_where.split(".")
+                    if (
+                        len(visit_path) == 1
+                        and visit_path[0] == self.import_graph.root.name
+                    ):
                         dst_node = self.import_graph.root
                     else:
-                        dst_node = self.import_graph.go_to_that_node(node, from_where.split('.')[0:-1])
+                        dst_node = self.import_graph.go_to_that_node(
+                            node, from_where.split(".")[0:-1]
+                        )
 
                     if dst_node is not None:
                         if dst_node.node_type_dict is not None:
-                            if from_name in dst_node.node_type_dict and dst_node.node_type_dict[from_name] is not None:
+                            if (
+                                from_name in dst_node.node_type_dict
+                                and dst_node.node_type_dict[from_name] is not None
+                            ):
                                 t_vals_tmp = dst_node.node_type_dict[from_name]
                                 if is_valid_call_link(t_vals_tmp):
                                     node.node_type_dict[function_name] += t_vals_tmp
@@ -219,77 +251,90 @@ def get_types(self) -> List[dict]:
         for node in self.leaves:
             # Function returns
             for function_name, type_values in node.node_type_dict.items():
-              
                 added = False  # Used to check whether type has already been added to list for the loop
 
                 if type_values is None or len(type_values) == 0:
                     continue
-                #TODO: why self is recognized as any type
+                # TODO: why self is recognized as any type
                 for value in type_values:
-                    if value in ['unknown', '3call', 'self']:
-                        type_list.append({
-                            'file': node.name,
-                            'line_number': node.line_numbers.get(function_name),
-                            'function': function_name,
-                            'type': {any.__name__}
-                        })
+                    if value in ["unknown", "3call", "self"]:
+                        type_list.append(
+                            {
+                                "file": node.name,
+                                "line_number": node.line_numbers.get(function_name),
+                                "function": function_name,
+                                "type": {any.__name__},
+                            }
+                        )
                         added = True
                         break
 
                 if is_done(type_values) and not added:
                     n_known += 1
-                    type_list.append({
-                        'file': node.name,
-                        'line_number': node.line_numbers.get(function_name),
-                        'function': function_name,
-                        'type': set(type_values)
-                    })
+                    type_list.append(
+                        {
+                            "file": node.name,
+                            "line_number": node.line_numbers.get(function_name),
+                            "function": function_name,
+                            "type": set(type_values),
+                        }
+                    )
                 else:
-                    type_list.append({
-                        'file': node.name,
-                        'line_number': node.line_numbers.get(function_name),
-                        'function': function_name,
-                        'type': {any.__name__}
-                    })
+                    type_list.append(
+                        {
+                            "file": node.name,
+                            "line_number": node.line_numbers.get(function_name),
+                            "function": function_name,
+                            "type": {any.__name__},
+                        }
+                    )
 
             # Static assignments
-    
+
             for assignment in node.static_assignments:
                 if assignment.is_arg:
-                    if isinstance(assignment.type,list):
-                        type_list.append({
-                            'file': node.name,
-                            'line_number': assignment.line,
-                            'parameter': assignment.name,
-                            'function': assignment.function,
-                            'type': set(assignment.type)
-                        })
+                    if isinstance(assignment.type, list):
+                        type_list.append(
+                            {
+                                "file": node.name,
+                                "line_number": assignment.line,
+                                "parameter": assignment.name,
+                                "function": assignment.function,
+                                "type": set(assignment.type),
+                            }
+                        )
                     else:
-                        type_list.append({
-                            'file': node.name,
-                            'line_number': assignment.line,
-                            'parameter': assignment.name,
-                            'function': assignment.function,
-                            'type': {assignment.type}
-                        })
+                        type_list.append(
+                            {
+                                "file": node.name,
+                                "line_number": assignment.line,
+                                "parameter": assignment.name,
+                                "function": assignment.function,
+                                "type": {assignment.type},
+                            }
+                        )
                 else:
                     if isinstance(assignment.type, list):
-                        type_list.append({
-                            'file': node.name,
-                            'line_number': assignment.line,
-                            'variable': assignment.name,
-                            'function': assignment.function,
-                            'type': set(assignment.type)
-                        })
+                        type_list.append(
+                            {
+                                "file": node.name,
+                                "line_number": assignment.line,
+                                "variable": assignment.name,
+                                "function": assignment.function,
+                                "type": set(assignment.type),
+                            }
+                        )
                     else:
-                        type_list.append({
-                            'file': node.name,
-                            'line_number': assignment.line,
-                            'variable': assignment.name,
-                            'function': assignment.function,
-                            'type': {assignment.type}
-                        })
-     
+                        type_list.append(
+                            {
+                                "file": node.name,
+                                "line_number": assignment.line,
+                                "variable": assignment.name,
+                                "function": assignment.function,
+                                "type": {assignment.type},
+                            }
+                        )
+
         return type_list
 
     @staticmethod
@@ -304,7 +349,7 @@ def process_file(source: str):
         class2base = {}
 
         # Generate AST from source
-        
+
         tree = generate_ast(source)
         if tree is None:
             return processed_file
@@ -321,9 +366,10 @@ def process_file(source: str):
         assign_records = split_visitor.assign_dict
 
         return_visitor.import_assign_records(assign_records)
-        
-        all_methods, all_classes, import_nodes = parse_module(tree)  # TODO: This doesn't need to be a function?
-        
+
+        all_methods, all_classes, import_nodes = parse_module(
+            tree
+        )  # TODO: This doesn't need to be a function?
 
         import_dict = get_api_ref_id(import_nodes)  # TODO: Replace with import map?
 
@@ -332,10 +378,11 @@ def process_file(source: str):
             function_name = function_node.name
             processed_file.line_numbers[function_name] = function_node.lineno
             function_source = astunparse.unparse(function_node)
-          
 
             # Variable assignments
-            assignments = VariableAssignmentMap(function_node, imports=import_mappings).map()
+            assignments = VariableAssignmentMap(
+                function_node, imports=import_mappings
+            ).map()
             for assignment in assignments:
                 assignment.function = function_node.name
             processed_file.static_assignments.extend(assignments)
@@ -344,7 +391,7 @@ def process_file(source: str):
             heuristics.heuristic_five(
                 import_mappings=import_mappings,
                 processed_file=processed_file,
-                function_node=function_node
+                function_node=function_node,
             )
 
             # Heuristic 8
@@ -352,52 +399,48 @@ def process_file(source: str):
             heuristics.heuristic_eight(
                 ast_tree=tree,
                 function_name=function_name,
-                function_params=function_params
+                function_params=function_params,
             )
             heuristics.heuristic_twelve(
-                function_node=function_node,
-                function_params=function_params
+                function_node=function_node, function_params=function_params
             )
 
-
             # Heuristic 7
             heuristics.heuristic_seven(
-                processed_file=processed_file,
-                function_node=function_node
+                processed_file=processed_file, function_node=function_node
             )
 
             # Heuristic 6
             heuristics.heuristic_six(
-                processed_file=processed_file,
-                function_node=function_node
+                processed_file=processed_file, function_node=function_node
             )
 
             # Heuristic 9
             heuristics.heuristic_nine(
                 import_mappings=import_mappings,
                 processed_file=processed_file,
-                function_node=function_node
+                function_node=function_node,
             )
 
             # # Heuristic 2
             heuristics.heuristic_two(
-                ast_tree=tree,
-                processed_file=processed_file,
-                assignments=assignments
+                ast_tree=tree, processed_file=processed_file, assignments=assignments
             )
 
             # Heuristic 5 once more to account for newly inferred types
             heuristics.heuristic_five(
                 import_mappings=import_mappings,
                 processed_file=processed_file,
-                function_node=function_node
+                function_node=function_node,
             )
 
             # Import resolved assignments to the return visitor
             return_visitor.import_assignments(assignments)
 
             # Get method header comment TODO: is this needed?
-            processed_file.node_type_comment[function_name] = get_function_comment(function_source)
+            processed_file.node_type_comment[function_name] = get_function_comment(
+                function_source
+            )
 
             return_visitor.clear()
             return_visitor.visit(function_node)
@@ -412,7 +455,6 @@ def process_file(source: str):
             processed_file.type_dict[function_name] = return_visitor.r_types
             stem_from_dict[function_name] = return_visitor.stem_from
             processed_file.type_gt[function_name] = function_node.returns
-            
 
         # Loop through class nodes
         class_assign_record_map = {}
@@ -435,7 +477,7 @@ def process_file(source: str):
                 assign_records = class_assign_record_map.get(superclass_name)
                 if assign_records:
                     for assignment_name, type_val in assign_records.items():
-                        super_assignment = assignment_name.replace('self', 'super')
+                        super_assignment = assignment_name.replace("self", "super")
                         class_assign_records[super_assignment] = type_val
 
             return_visitor.clear_all()
@@ -448,10 +490,14 @@ def process_file(source: str):
                 processed_file.line_numbers[function_name] = function_node.lineno
 
                 # Get method header comment TODO: Is this needed?
-                processed_file.node_type_comment[function_name] = get_function_comment(function_source)
+                processed_file.node_type_comment[function_name] = get_function_comment(
+                    function_source
+                )
 
                 # Variable assignments
-                assignments = VariableAssignmentMap(function_node, imports=import_mappings).map()
+                assignments = VariableAssignmentMap(
+                    function_node, imports=import_mappings
+                ).map()
                 for assignment in assignments:
                     assignment.function = function_node.name
                 processed_file.static_assignments.extend(assignments)
@@ -460,48 +506,48 @@ def process_file(source: str):
                 heuristics.heuristic_five(
                     import_mappings=import_mappings,
                     processed_file=processed_file,
-                    function_node=function_node
+                    function_node=function_node,
                 )
 
                 # Heuristic 8
-                function_params = [a for a in processed_file.static_assignments if a.is_arg]
+                function_params = [
+                    a for a in processed_file.static_assignments if a.is_arg
+                ]
                 heuristics.heuristic_eight(
                     ast_tree=tree,
                     function_name=function_name,
-                    function_params=function_params
+                    function_params=function_params,
                 )
 
                 # Heuristic 7
                 heuristics.heuristic_seven(
-                    processed_file=processed_file,
-                    function_node=function_node
+                    processed_file=processed_file, function_node=function_node
                 )
 
                 # Heuristic 6
                 heuristics.heuristic_six(
-                    processed_file=processed_file,
-                    function_node=function_node
+                    processed_file=processed_file, function_node=function_node
                 )
 
                 # Heuristic 9
                 heuristics.heuristic_nine(
                     import_mappings=import_mappings,
                     processed_file=processed_file,
-                    function_node=function_node
+                    function_node=function_node,
                 )
 
                 # # Heuristic 2
                 heuristics.heuristic_two(
                     ast_tree=tree,
                     processed_file=processed_file,
-                    assignments=assignments
+                    assignments=assignments,
                 )
 
                 # Heuristic 5 once more to account for newly inferred types
                 heuristics.heuristic_five(
                     import_mappings=import_mappings,
                     processed_file=processed_file,
-                    function_node=function_node
+                    function_node=function_node,
                 )
 
                 return_visitor.clear_all()
@@ -519,33 +565,40 @@ def process_file(source: str):
                 processed_file.type_gt[function_name] = function_node.returns
 
         # Loop through return statements that called another function -> see Heuristic 1
-     
+
         for function_name, stems in stem_from_dict.items():
             stems = list(dict.fromkeys(stems))
             for from_name in stems:
                 # From the same class
-                if from_name.find('self.') == 0:
-                    processed_file.type_stem_links[function_name] = ('self', from_name)
-                elif from_name.find('super.') == 0:
-                    class_name = function_name.split('.')[0]
+                if from_name.find("self.") == 0:
+                    processed_file.type_stem_links[function_name] = ("self", from_name)
+                elif from_name.find("super.") == 0:
+                    class_name = function_name.split(".")[0]
                     if class_name in class2base:
                         base_name = class2base[class_name]
                         if base_name in import_dict:
                             processed_file.type_stem_links[function_name] = (
-                                import_dict[base_name], base_name + from_name.lstrip('super'))
+                                import_dict[base_name],
+                                base_name + from_name.lstrip("super"),
+                            )
                         else:
                             processed_file.type_stem_links[function_name] = (
-                                'base', base_name + from_name.lstrip('super'))
+                                "base",
+                                base_name + from_name.lstrip("super"),
+                            )
                     else:
                         # TODO: Can be from other libraries too, check for imported classes
                         pass
                 elif from_name in processed_file.type_dict:
-                    processed_file.type_stem_links[function_name] = ('local', from_name)
+                    processed_file.type_stem_links[function_name] = ("local", from_name)
                 else:
                     import_path = is_imported_fun(from_name, import_dict)
                     if import_path is not None:
-                        processed_file.type_stem_links[function_name] = (import_path, from_name)
-   
+                        processed_file.type_stem_links[function_name] = (
+                            import_path,
+                            from_name,
+                        )
+
         return processed_file
 
     def print_types(self, functions=True, parameters=True, variables=True):
@@ -555,20 +608,26 @@ def print_types(self, functions=True, parameters=True, variables=True):
         self.infer_types()
         inferred_types = self.get_types()
         for case in inferred_types:
-            case_type = case.get('type')
-            file_name = case.get('file')
-            function = case.get('function')
-            line_no = case.get('line_number')
-            var_name = case.get('variable')
-            param_name = case.get('parameter')
+            case_type = case.get("type")
+            file_name = case.get("file")
+            function = case.get("function")
+            line_no = case.get("line_number")
+            var_name = case.get("variable")
+            param_name = case.get("parameter")
             if var_name:
                 if variables:
                     # We have a variable
-                    print(f"{file_name}:{line_no}: Variable {var_name} in function {function} has type {case_type}")
+                    print(
+                        f"{file_name}:{line_no}: Variable {var_name} in function"
+                        f" {function} has type {case_type}"
+                    )
             elif param_name:
                 if parameters:
                     # We have a parameter
-                    print(f"{file_name}:{line_no}: Parameter {param_name} of function {function} has type {case_type}")
+                    print(
+                        f"{file_name}:{line_no}: Parameter {param_name} of function"
+                        f" {function} has type {case_type}"
+                    )
             else:
                 # We have a function
                 if functions:
@@ -579,6 +638,7 @@ def print_types(self, functions=True, parameters=True, variables=True):
                             case_type = f"Union[{', '.join(case_type)}]"
                     else:
                         case_type = case_type.pop()
-                    print(f"{file_name}:{line_no}: Function {function} has return type {case_type}")
-
-
+                    print(
+                        f"{file_name}:{line_no}: Function {function} has return type"
+                        f" {case_type}"
+                    )
diff --git a/scalpel/typeinfer/utilities.py b/scalpel/typeinfer/utilities.py
index c3c851a4..8b0e5038 100644
--- a/scalpel/typeinfer/utilities.py
+++ b/scalpel/typeinfer/utilities.py
@@ -2,37 +2,37 @@
 Utilities for type inference module
 """
 
-import re
 import ast
-import sys
 import builtins
-from copy import deepcopy
+import re
+import sys
 from collections import deque
-from typing import Dict, Optional, Union, List
-
-
-# typeshed can be imported here 
-func_ret_types  = {
-                "dict": "dict",
-                "list": "list",
-                "tuple": "tuple",
-                "set": "set",
-                "str": "str",
-                "id": "int",
-                "sum": "num",
-                "float": "float",
-                "len": "int",
-                "ceil": "int",
-                "floor": "int",
-                "max": "num",
-                "min": "min",
-                "all":  "bool",
-                "any": "bool",
-                "assert": "bool",
-                "isinstance": "bool",
-                "bytes":"bytes",       
-                "default_dict": "dict"
-            }
+from copy import deepcopy
+from typing import Dict, List, Optional, Union
+
+# typeshed can be imported here
+func_ret_types = {
+    "dict": "dict",
+    "list": "list",
+    "tuple": "tuple",
+    "set": "set",
+    "str": "str",
+    "id": "int",
+    "sum": "num",
+    "float": "float",
+    "len": "int",
+    "ceil": "int",
+    "floor": "int",
+    "max": "num",
+    "min": "min",
+    "all": "bool",
+    "any": "bool",
+    "assert": "bool",
+    "isinstance": "bool",
+    "bytes": "bytes",
+    "default_dict": "dict",
+}
+
 
 def get_func_calls_type(tree):
     node = deepcopy(tree)
@@ -45,6 +45,7 @@ class TypeInferCallTransformer(ast.NodeTransformer):
     """
     A NodeTransformer class for getting function call information
     """
+
     def __init__(self):
         self.call_names = []
 
@@ -58,7 +59,6 @@ def visit_Attribute(self, node):
         return node
 
     def visit_Call(self, node):
-
         tmp_fun_node = deepcopy(node)
         tmp_fun_node.args = []
         tmp_fun_node.keywords = []
@@ -81,6 +81,7 @@ class FuncCallVisitor(ast.NodeVisitor):
     """
     A NodeVisitor class for getting function call information
     """
+
     def __init__(self):
         self._name = deque()
         self.call_names = []
@@ -91,7 +92,7 @@ def clear(self):
 
     @property
     def name(self):
-        return '.'.join(self._name)
+        return ".".join(self._name)
 
     @name.deleter
     def name(self):
@@ -101,7 +102,6 @@ def visit_Name(self, node):
         self._name.appendleft(node.id)
 
     def visit_Attribute(self, node):
-
         try:
             self._name.appendleft(node.attr)
             self._name.appendleft(node.value.id)
@@ -121,9 +121,13 @@ def get_built_in_types() -> Dict:
 
     :return: Python built in types in a dictionary
     """
-    builtin_types = [getattr(builtins, d).__name__ for d in dir(builtins) if isinstance(getattr(builtins, d), type)]
-    return { b.lower():b  for b in builtin_types}
-    
+    builtin_types = [
+        getattr(builtins, d).__name__
+        for d in dir(builtins)
+        if isinstance(getattr(builtins, d), type)
+    ]
+    return {b.lower(): b for b in builtin_types}
+
 
 def get_type(node, imports=None) -> str:
     """
@@ -151,25 +155,55 @@ def get_type(node, imports=None) -> str:
     elif isinstance(node, ast.BinOp):
         if isinstance(node.op, (ast.Div, ast.Mult)):
             return "float"
-        elif isinstance(node.op, ast.Mod) and isinstance(node.left, ast.Constant) and isinstance(node.left, ast.Str):
+        elif (
+            isinstance(node.op, ast.Mod)
+            and isinstance(node.left, ast.Constant)
+            and isinstance(node.left, ast.Str)
+        ):
             return "str"
-        elif isinstance(node.op, ast.Mod) and isinstance(node.left, ast.Name) and isinstance(node.right, ast.Dict):
+        elif (
+            isinstance(node.op, ast.Mod)
+            and isinstance(node.left, ast.Name)
+            and isinstance(node.right, ast.Dict)
+        ):
             return "str"
         elif isinstance(node.op, ast.Add):
-            if isinstance(node.left, (ast.Constant, ast.Num, ast.List, ast.ListComp,
-                                      ast.Set, ast.SetComp, ast.Dict, ast.DictComp)):
+            if isinstance(
+                node.left,
+                (
+                    ast.Constant,
+                    ast.Num,
+                    ast.List,
+                    ast.ListComp,
+                    ast.Set,
+                    ast.SetComp,
+                    ast.Dict,
+                    ast.DictComp,
+                ),
+            ):
                 return get_type(node.left)
 
-            if isinstance(node.right, (ast.Constant, ast.Num, ast.List, ast.ListComp,
-                                       ast.Set, ast.SetComp, ast.Dict, ast.DictComp)):
+            if isinstance(
+                node.right,
+                (
+                    ast.Constant,
+                    ast.Num,
+                    ast.List,
+                    ast.ListComp,
+                    ast.Set,
+                    ast.SetComp,
+                    ast.Dict,
+                    ast.DictComp,
+                ),
+            ):
                 return get_type(node.right)
 
     if isinstance(node, ast.Name) and node.id == "self":
         return "self"
-        
+
     if isinstance(node, ast.Num):
         return type(node.n).__name__
-        
+
     elif isinstance(node, ast.List) or isinstance(node, ast.Tuple):
         value_type = check_consistent_list_types(node.elts)
         return f"{type(node).__name__}[{value_type}]"
@@ -209,14 +243,14 @@ def get_type(node, imports=None) -> str:
                 return imported_type
         if isinstance(node.func, ast.Name):
             if node.func.id in func_ret_types:
-                return func_ret_types[node.func.id] 
+                return func_ret_types[node.func.id]
             elif is_camel_case(func_name):
                 return func_name
             else:
                 return "call"
         elif is_camel_case(func_name.split(".")[-1]):
             return func_name
-        elif func_name in ['join', 'format']:
+        elif func_name in ["join", "format"]:
             return "str"
         else:
             return "call"
@@ -242,7 +276,7 @@ def check_consistent_list_types(values) -> str:
         builtin_types_dict = get_built_in_types()
         builtin_first_type = builtin_types_dict.get(first_type.__name__.lower())
         # Not an AST constant so we can just compare AST types
-        for n in values[1:len(values)]:
+        for n in values[1 : len(values)]:
             # Compare type to first items type
             if not isinstance(n, first_type):
                 # No constant type, assign any
@@ -254,7 +288,7 @@ def check_consistent_list_types(values) -> str:
         # We have an AST constant so we will need to compare their types
         first_constant_type = type(values[0].value)
         assignment_type = first_constant_type.__name__
-        for n in values[1:len(values)]:
+        for n in values[1 : len(values)]:
             # Compare type to first items type
             if not isinstance(n, ast.Constant):
                 # Not a constant type, assign any
@@ -276,7 +310,7 @@ def is_camel_case(s: str) -> bool:
     Returns:
         True if the string is camel case, False otherwise
     """
-    pattern = '([A-Z][a-z]*)+'
+    pattern = "([A-Z][a-z]*)+"
     if re.search(pattern, s):
         return True
     return False
@@ -311,19 +345,19 @@ def get_api_ref_id(import_nodes):
         if isinstance(node, ast.Import):
             items = [nn.__dict__ for nn in node.names]
             for d in items:
-                if d['asname'] is None:  # alias name not found, use its imported name
-                    id2fullname[d['name']] = d['name']
+                if d["asname"] is None:  # alias name not found, use its imported name
+                    id2fullname[d["name"]] = d["name"]
                 else:
-                    id2fullname[d['asname']] = d['name']  # otherwise , use alias name
+                    id2fullname[d["asname"]] = d["name"]  # otherwise , use alias name
         if isinstance(node, ast.ImportFrom) and node.module is not None:
             # for import from statements
             # module names are the head of a API name
             items = [nn.__dict__ for nn in node.names]
             for d in items:
-                if d['asname'] is None:  # alias name not found
-                    id2fullname[d['name']] = node.module + '.' + d['name']
+                if d["asname"] is None:  # alias name not found
+                    id2fullname[d["name"]] = node.module + "." + d["name"]
                 else:
-                    id2fullname[d['asname']] = node.module + '.' + d['name']
+                    id2fullname[d["asname"]] = node.module + "." + d["name"]
     return id2fullname
 
 
@@ -337,23 +371,23 @@ def is_imported_fun(func_name: str, import_dict: dict) -> Optional[str]:
     Returns:
         The module that the function was import from or None if it was not imported
     """
-    name_parts = func_name.split('.')
+    name_parts = func_name.split(".")
     if name_parts[0] in import_dict:
         return import_dict[name_parts[0]]
     return None
 
 
 def rename_from_name(from_where, from_name, fun_name):
-    if from_where == 'self':
-        class_name = fun_name.split('.')[0]
-        from_name = class_name + "." + ".".join(from_name.split('.')[1:])
+    if from_where == "self":
+        class_name = fun_name.split(".")[0]
+        from_name = class_name + "." + ".".join(from_name.split(".")[1:])
         return from_name
-    elif from_where == 'local' or from_where == 'base':
+    elif from_where == "local" or from_where == "base":
         return from_name
 
 
 def is_valid_call_link(t_vals):
-    return all(x not in ['ID', 'call', 'unknown'] for x in t_vals)
+    return all(x not in ["ID", "call", "unknown"] for x in t_vals)
 
 
 def generate_ast(source: str):
@@ -361,13 +395,13 @@ def generate_ast(source: str):
     Generates ast from the source string
     """
     try:
-        if sys.version_info >= (3,8):
-            tree = ast.parse(source, mode='exec', type_comments=True)
-        elif sys.version_info >= (3,5) and sys.version_info < (3,8):
-            tree = ast.parse(source, mode='exec')
+        if sys.version_info >= (3, 8):
+            tree = ast.parse(source, mode="exec", type_comments=True)
+        elif sys.version_info >= (3, 5) and sys.version_info < (3, 8):
+            tree = ast.parse(source, mode="exec")
         else:
             raise Exception("Must use Python 3.5+ ")
-        #tree = ast.parse(source, mode='exec')
+        # tree = ast.parse(source, mode='exec')
         return tree
     except Exception as e:
         print(e)
@@ -397,23 +431,24 @@ def is_done(t_vals: List[str]) -> bool:
     Returns:
         True if in a finished state, False otherwise
     """
-    return all(x not in ['ID', 'call', 'unknown', 'input', '3call'] for x in t_vals)
+    return all(x not in ["ID", "call", "unknown", "input", "3call"] for x in t_vals)
 
 
 def find_class_by_attr(module_records, attrs):
-
     if len(attrs) < 5:
         return None
-    class_names = [item.split('.')[0] for item in module_records if len(item.split('.')) == 2]
+    class_names = [
+        item.split(".")[0] for item in module_records if len(item.split(".")) == 2
+    ]
     for c_name in class_names:
-        if all([(c_name + '.' + x) in module_records for x in attrs]):
+        if all([(c_name + "." + x) in module_records for x in attrs]):
             return c_name
 
     return None
 
 
 def get_attr_name(node):
-    #TODO  to remove this ufunction 
+    # TODO  to remove this ufunction
     if isinstance(node, ast.Call):
         return get_attr_name(node.func)
     if isinstance(node, ast.Name):
diff --git a/scalpel/typeinfer/visitors.py b/scalpel/typeinfer/visitors.py
index e8c645b4..d7473201 100644
--- a/scalpel/typeinfer/visitors.py
+++ b/scalpel/typeinfer/visitors.py
@@ -3,14 +3,15 @@
 """
 
 import ast
-from copy import deepcopy
 from collections import deque
+from copy import deepcopy
 
 
 class FuncCallVisitor(ast.NodeVisitor):
     """
     A NodeVisitor class for getting function call information
     """
+
     def __init__(self):
         self._name = deque()
         self.call_names = []
@@ -21,7 +22,7 @@ def clear(self):
 
     @property
     def name(self):
-        return '.'.join(self._name)
+        return ".".join(self._name)
 
     @name.deleter
     def name(self):
@@ -31,7 +32,6 @@ def visit_Name(self, node):
         self._name.appendleft(node.id)
 
     def visit_Attribute(self, node):
-
         try:
             self._name.appendleft(node.attr)
             self._name.appendleft(node.value.id)
@@ -49,6 +49,7 @@ class TypeInferCallTransformer(ast.NodeTransformer):
     """
     A NodeTransformer class for getting function call information
     """
+
     def __init__(self):
         self.call_names = []
 
diff --git a/scalpel/util.py b/scalpel/util.py
index f6db5b78..3a97297e 100644
--- a/scalpel/util.py
+++ b/scalpel/util.py
@@ -1,10 +1,18 @@
+import ast
+import builtins
+import math
+import operator
 import os
+
+
 # scan a folder recurisively and return all files ending with the flag
-def get_path_by_ext(root_dir, flag='.py'):
+def get_path_by_ext(root_dir, flag=".py"):
     paths = []
     for root, dirs, files in os.walk(root_dir):
-        files = [f for f in files if not f[0] == '.']  # skip hidden files such as git files
-        dirs[:] = [d for d in dirs if not d[0] == '.']
+        files = [
+            f for f in files if not f[0] == "."
+        ]  # skip hidden files such as git files
+        dirs[:] = [d for d in dirs if not d[0] == "."]
         for f in files:
             if f.endswith(flag):
                 paths.append(os.path.join(root, f))
@@ -22,19 +30,19 @@ def get_path_by_ext(root_dir, flag='.py'):
     ast.Div: operator.truediv,
     ast.Mod: operator.mod,
     ast.Pow: operator.pow,
-    ast.Call: checkfun,      # check all built-in functions
+    # ast.Call: checkfun, # check all built-in functions #APSV: Not implemented? commenting
     ast.BinOp: ast.BinOp,
     #  unary
     ast.USub: operator.neg,
     ast.UAdd: operator.pos,
     ast.UnaryOp: ast.UnaryOp,
-    ast.Not: operator.__not__
+    ast.Not: operator.__not__,
 }
 
 
 def ast_node_eval(node):
-    """"
+    """ "
     This is an implementation of expression node evaluation. The function serves as an alternative to ast.literal_eval()
     """
     ##TODO
-    pass 
+    pass
diff --git a/setup.py b/setup.py
index 4dc59169..c7338797 100644
--- a/setup.py
+++ b/setup.py
@@ -1,33 +1,33 @@
-from setuptools import setup, find_packages
-setup( 
-    name = "python-scalpel",
-    version = "1.0beta",
-    description = "Scalpel: The Python Program Analysis Framework",
-    author = "Jiawei Wang and Li Li and Haowei Quan",
-    author_email = "jiawei.wang1@monash.edu, li.li@monash.edu, haowei.quan@monash.edu",
-    url = "https://github.com/SMAT-Lab/Scalpel",
-    packages= find_packages(include=['scalpel', 'scalpel.*']),
-    install_requires = [
-        'astor~=0.8.1',
-        'graphviz~=0.17',
-        'networkx',
-        'astunparse~=1.6.3',
-        'typed-ast',
-        'typeshed-client',
-        'regex',
-        'setuptools',
-        'dataclasses',
-        'pycg',
-        ],
-    extras_require = {
-        'docs':
-            [
-            'sphinx', 
-            'myst_parser', 
-            'sphinx_rtd_theme', 
-            'sphinxcontrib-spelling', 
-            'pydoctor', 
-            ]
-        },
-    )
+# Principles from: https://blog.miguelgrinberg.com/post/the-package-dependency-blues
+from setuptools import find_packages, setup
 
+setup(
+    name="python-scalpel",
+    version="1.0beta",
+    description="Scalpel: The Python Program Analysis Framework",
+    author="Jiawei Wang and Li Li and Haowei Quan",
+    author_email="jiawei.wang1@monash.edu, li.li@monash.edu, haowei.quan@monash.edu",
+    url="https://github.com/SMAT-Lab/Scalpel",
+    packages=find_packages(include=["scalpel", "scalpel.*"]),
+    install_requires=[
+        "astor~=0.8.1",
+        "graphviz~=0.17",
+        "networkx",
+        "astunparse~=1.6.3",
+        "typed-ast~=1.5.4",
+        "typeshed-client~=2.2.0",
+        "regex",
+        "setuptools",
+        "dataclasses",
+        "pycg~=0.0.6",
+    ],
+    extras_require={
+        "docs": [
+            "sphinx",
+            "myst_parser",
+            "sphinx_rtd_theme",
+            "sphinxcontrib-spelling",
+            "pydoctor",
+        ]
+    },
+)
diff --git a/setup.sh b/setup.sh
new file mode 100644
index 00000000..0c7d0c74
--- /dev/null
+++ b/setup.sh
@@ -0,0 +1,191 @@
+#!/usr/bin/env bash
+#encoding=utf8
+
+function echo_block() {
+    echo "----------------------------"
+    echo $1
+    echo "----------------------------"
+}
+
+function check_installed_pip() {
+   ${PYTHON} -m pip > /dev/null
+   if [ $? -ne 0 ]; then
+        echo_block "Installing Pip for ${PYTHON}"
+        curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
+        ${PYTHON} get-pip.py
+        rm get-pip.py
+   fi
+}
+
+# Check which python version is installed
+function check_installed_python() {
+    if [ -n "${VIRTUAL_ENV}" ]; then
+        echo "Please deactivate your virtual environment before running setup.sh."
+        echo "You can do this by running 'deactivate'."
+        exit 2
+    fi
+
+    for v in 10 9 8
+    do
+        PYTHON="python3.${v}"
+        which $PYTHON
+        if [ $? -eq 0 ]; then
+            echo "using ${PYTHON}"
+            check_installed_pip
+            return
+        fi
+    done
+
+    echo "No usable python found. Please make sure to have python3.8 or newer installed."
+    exit 1
+}
+
+function updateenv() {
+    echo_block "Updating your virtual env"
+    if [ ! -f .env/bin/activate ]; then
+        echo "Something went wrong, no virtual environment found."
+        exit 1
+    fi
+    source .env/bin/activate
+    SYS_ARCH=$(uname -m)
+    echo "pip install in-progress. Please wait..."
+    ${PYTHON} -m pip install --upgrade pip wheel setuptools
+    REQUIREMENTS=requirements.txt
+
+    ${PYTHON} -m pip install --upgrade -r ${REQUIREMENTS}
+    if [ $? -ne 0 ]; then
+        echo "Failed installing dependencies"
+        exit 1
+    fi
+    ${PYTHON} -m pip install -e .
+    if [ $? -ne 0 ]; then
+        echo "Failed installing Scalpel"
+        exit 1
+    fi
+
+    echo_block "Updating pre_commit scripts"
+    ${PYTHON} -m pre_commit install
+    if [ $? -ne 0 ]; then
+        echo "Failed installing pre-commit"
+        exit 1
+    fi
+
+    echo "pip install completed"
+}
+
+# Install bot Debian_ubuntu
+function install_debian() {
+    sudo apt-get update
+    sudo apt-get install -y gcc build-essential autoconf libtool pkg-config make wget git curl $(echo lib${PYTHON}-dev ${PYTHON}-venv)
+}
+
+# Install bot RedHat_CentOS
+function install_redhat() {
+    sudo yum update
+    sudo yum install -y gcc gcc-c++ make autoconf libtool pkg-config wget git $(echo ${PYTHON}-devel | sed 's/\.//g')
+}
+
+# Upgrade Scalpel
+function update() {
+    git pull
+    updateenv
+}
+
+function check_git_changes() {
+    if [ -z "$(git status --porcelain)" ]; then
+        echo "No changes in git directory"
+        return 1
+    else
+        echo "Changes in git directory"
+        return 0
+    fi
+}
+
+# Reset Develop or Stable branch
+function reset() {
+    echo_block "Resetting branch and virtual env"
+
+    if [ "1" == $(git branch -vv |grep -cE "\* develop|\* stable") ]
+    then
+        if check_git_changes; then
+            read -p "Keep your local changes? (Otherwise will remove all changes you made!) [Y/n]? "
+            if [[ $REPLY =~ ^[Nn]$ ]]; then
+
+                git fetch -a
+
+                if [ "1" == $(git branch -vv | grep -c "* develop") ]
+                then
+                    echo "- Hard resetting of 'develop' branch."
+                    git reset --hard origin/develop
+                elif [ "1" == $(git branch -vv | grep -c "* stable") ]
+                then
+                    echo "- Hard resetting of 'stable' branch."
+                    git reset --hard origin/stable
+                fi
+            fi
+        fi
+    else
+        echo "Reset ignored because you are not on 'stable' or 'develop'."
+    fi
+
+    if [ -d ".env" ]; then
+        echo "- Deleting your previous virtual env"
+        rm -rf .env
+    fi
+    echo
+    ${PYTHON} -m venv .env
+    if [ $? -ne 0 ]; then
+        echo "Could not create virtual environment. Leaving now"
+        exit 1
+    fi
+    updateenv
+}
+
+function install() {
+
+    echo_block "Installing mandatory dependencies"
+
+    # if [ "$(uname -s)" == "Darwin" ]; then
+    #     echo "macOS detected. Setup for this system in-progress"
+    #     install_macos
+    if [ -x "$(command -v apt-get)" ]; then
+        echo "Debian/Ubuntu detected. Setup for this system in-progress"
+        install_debian
+    # elif [ -x "$(command -v yum)" ]; then
+    #     echo "Red Hat/CentOS detected. Setup for this system in-progress"
+    #     install_redhat
+    else
+        echo "This script does not support your OS."
+        echo "If you have Python version 3.8 - 3.10, pip, virtualenv you can continue."
+        echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell."
+        sleep 10
+    fi
+    echo
+    reset
+}
+
+function help() {
+    echo "usage:"
+    echo "	-i,--install    Install Scalpel from scratch"
+    echo "	-u,--update     Command git pull to update."
+    echo "	-r,--reset      Hard reset your develop/stable branch."
+}
+
+# Verify if 3.8+ is installed
+check_installed_python
+
+case $* in
+--install|-i)
+install
+;;
+--update|-u)
+update
+;;
+--reset|-r)
+reset
+;;
+*)
+help
+;;
+esac
+exit 0


Test patch:
diff --git a/examples/test_program_transformer.py b/examples/test_program_transformer.py
index 04019a1c..88308de0 100644
--- a/examples/test_program_transformer.py
+++ b/examples/test_program_transformer.py
@@ -1,9 +1,10 @@
+import ast
 import os
 import sys
-import ast
+
 import astor
-from scalpel.rewriter import Rewriter
 
+from scalpel.rewriter import Rewriter
 
 src1 = '''
 def get_vid_from_url(url):
@@ -157,12 +158,12 @@ def sina_download_by_vkey(vkey, title=None, output_dir='.', merge=True, info_onl
 
 '''
 
-src10 = '''
+src10 = """
 def sina_download_by_vkey(vkey, title=None, output_dir='.', merge=True, info_only=False):
     if not info_only:
         download_urls([url], title, 'flv', size, output_dir=output_dir, merge=merge)
 
-'''
+"""
 
 src11 = """
 def func(x,y):
@@ -172,15 +173,16 @@ def func(x,y):
     return z
 """
 
+
 def main():
     rewriter = Rewriter(src11)
     print(src11)
-    rewriter.random_var_renaming(new_name = "_renamed_one")
+    rewriter.random_var_renaming(new_name="_renamed_one")
     rewriter.unused_stmt_insertion()
     rewriter.loop_exchange()
     new_src = rewriter.get_src()
     print(new_src)
 
-if __name__ == '__main__':
-    main()
 
+if __name__ == "__main__":
+    main()
diff --git a/tests/test_ssa.py b/tests/test_ssa.py
index 16a22e4c..2d74a1eb 100644
--- a/tests/test_ssa.py
+++ b/tests/test_ssa.py
@@ -1,16 +1,16 @@
+import ast
 import os
 import sys
-import ast
-import astor
 import unittest
+
+import astor
+
 from scalpel.core.mnode import MNode
 from scalpel.SSA.const import SSA
 
 
 class BaseCaseTests(unittest.TestCase):
-
     def test_case_1(self):
-
         filename = "tests/test-cases/ssa_basecase/ssa_case6.py"
         source = open(filename).read()
         mnode = MNode("local")
@@ -19,17 +19,17 @@ def test_case_1(self):
         ast_node = mnode.ast
         cfg = mnode.gen_cfg()
         m_ssa = SSA()
-        ssa_results, const_dict = m_ssa.compute_SSA(cfg) 
+        ssa_results, const_dict = m_ssa.compute_SSA(cfg)
         # three blocks
-        assert len(ssa_results) == 3 
-        # 1st block, 3rd statement 
-        #print(ssa_results)
-        assert 'a' in ssa_results[1][2]
-        assert 'a' in ssa_results[1][6]
-        assert 'a' in ssa_results[1][10]
-        assert 'c' in ssa_results[2][0]
-        assert 'c' in ssa_results[3][0]
-        
+        assert len(ssa_results) == 3
+        # 1st block, 3rd statement
+        # print(ssa_results)
+        assert "a" in ssa_results[1][2]
+        assert "a" in ssa_results[1][6]
+        assert "a" in ssa_results[1][10]
+        assert "c" in ssa_results[2][0]
+        assert "c" in ssa_results[3][0]
+
     def test_case_2(self):
         filename = "tests/test-cases/ssa_basecase/ssa_case-5.py"
         source = open(filename).read()
@@ -39,11 +39,11 @@ def test_case_2(self):
         ast_node = mnode.ast
         cfg = mnode.gen_cfg()
         m_ssa = SSA()
-        ssa_results, const_dict = m_ssa.compute_SSA(cfg) 
+        ssa_results, const_dict = m_ssa.compute_SSA(cfg)
         print(ssa_results)
         assert len(ssa_results) == 10
-        assert ('a' in ssa_results[3][2] and len(ssa_results[3][2]['a'])==4)
-        assert ('b' in ssa_results[3][2] and len(ssa_results[3][2]['b'])==3)
+        assert "a" in ssa_results[3][2] and len(ssa_results[3][2]["a"]) == 4
+        assert "b" in ssa_results[3][2] and len(ssa_results[3][2]["b"]) == 3
 
     def test_case_3(self):
         filename = "tests/test-cases/ssa_basecase/ssa_case7.py"
@@ -54,12 +54,13 @@ def test_case_3(self):
         ast_node = mnode.ast
         cfg = mnode.gen_cfg()
         m_ssa = SSA()
-        ssa_results, const_dict = m_ssa.compute_SSA(cfg) 
+        ssa_results, const_dict = m_ssa.compute_SSA(cfg)
         print(ssa_results)
         assert len(ssa_results) == 4
-        assert ('c' in ssa_results[1][1] and len(ssa_results[1][1]['c'])==1)
-        assert ('c' in ssa_results[2][0] and len(ssa_results[2][0]['c'])==1)
-        assert ('t' in ssa_results[3][0] and len(ssa_results[3][0]['t'])==1)
+        assert "c" in ssa_results[1][1] and len(ssa_results[1][1]["c"]) == 1
+        assert "c" in ssa_results[2][0] and len(ssa_results[2][0]["c"]) == 1
+        assert "t" in ssa_results[3][0] and len(ssa_results[3][0]["t"]) == 1
+
     def test_case_4(self):
         filename = "tests/test-cases/ssa_basecase/ssa_case_4.py"
         source = open(filename).read()
@@ -69,9 +70,10 @@ def test_case_4(self):
         ast_node = mnode.ast
         cfg = mnode.gen_cfg()
         m_ssa = SSA()
-        ssa_results, const_dict = m_ssa.compute_SSA(cfg) 
-        assert ("count",0) in const_dict
-        assert ("count",1) in const_dict
+        ssa_results, const_dict = m_ssa.compute_SSA(cfg)
+        assert ("count", 0) in const_dict
+        assert ("count", 1) in const_dict
+
     def test_case_5(self):
         filename = "tests/test-cases/ssa_basecase/ssa_case_10.py"
         source = open(filename).read()
@@ -81,8 +83,9 @@ def test_case_5(self):
         ast_node = mnode.ast
         cfg = mnode.gen_cfg()
         m_ssa = SSA()
-        ssa_results, const_dict = m_ssa.compute_SSA(cfg) 
-        assert ('url', 0) in const_dict and const_dict[('url',0)] is None
+        ssa_results, const_dict = m_ssa.compute_SSA(cfg)
+        assert ("url", 0) in const_dict and const_dict[("url", 0)] is None
+
     def test_case_6(self):
         filename = "tests/test-cases/ssa_basecase/ssa_case_9.py"
         source = open(filename).read()
@@ -92,9 +95,9 @@ def test_case_6(self):
         ast_node = mnode.ast
         cfg = mnode.gen_cfg()
         m_ssa = SSA()
-        ssa_results, const_dict = m_ssa.compute_SSA(cfg) 
-        assert ('result',1) in const_dict
-        assert const_dict[('result',1)] is not None
+        ssa_results, const_dict = m_ssa.compute_SSA(cfg)
+        assert ("result", 1) in const_dict
+        assert const_dict[("result", 1)] is not None
 
     def test_case_7(self):
         filename = "tests/test-cases/ssa_basecase/ssa_case_11.py"
@@ -105,13 +108,12 @@ def test_case_7(self):
         ast_node = mnode.ast
         cfg = mnode.gen_cfg()
         m_ssa = SSA()
-        ssa_results, const_dict = m_ssa.compute_SSA(cfg) 
-        assert len(ssa_results) == 4 # 4 blocks
-        assert ssa_results[1] == [{}, {'isinstance': set(), 'a': {0}, 'int': set()}]
+        ssa_results, const_dict = m_ssa.compute_SSA(cfg)
+        assert len(ssa_results) == 4  # 4 blocks
+        assert ssa_results[1] == [{}, {"isinstance": set(), "a": {0}, "int": set()}]
         assert ssa_results[2] == [{}]
         assert ssa_results[4] == [{}]
-        assert ssa_results[3] == [{}, {'a': {0}, 'b': {2}}]
-
+        assert ssa_results[3] == [{}, {"a": {0}, "b": {2}}]
 
     def test_case_8(self):
         filename = "tests/test-cases/ssa_basecase/ssa_case_12.py"
@@ -121,14 +123,13 @@ def test_case_8(self):
         mnode.gen_ast()
         ast_node = mnode.ast
         cfg = mnode.gen_cfg()
-        graph = cfg.build_visual('pdf')
+        graph = cfg.build_visual("pdf")
         graph.render("example_cfg.pdf", view=False)
         m_ssa = SSA()
-        ssa_results, const_dict = m_ssa.compute_SSA(cfg) 
-        assert  ("a", 0) in const_dict
-        assert  ("b", 0) in const_dict
-
+        ssa_results, const_dict = m_ssa.compute_SSA(cfg)
+        assert ("a", 0) in const_dict
+        assert ("b", 0) in const_dict
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     unittest.main()
diff --git a/tests/test_typeinfer.py b/tests/test_typeinfer.py
index 4fe0a7a4..74e20860 100644
--- a/tests/test_typeinfer.py
+++ b/tests/test_typeinfer.py
@@ -4,326 +4,242 @@
 Unittests for base cases
 """
 
-import unittest
 import os
-import sys
-from scalpel.typeinfer.typeinfer import TypeInference
+import unittest
 from collections import OrderedDict
 
+import test_typeinfer_actual_data as ActualData
+
+from scalpel.typeinfer.typeinfer import TypeInference
+
 path_to_current_file = os.path.realpath(__file__)
 current_directory = os.path.split(path_to_current_file)[0]
-py_version_above_38 = True
-if sys.version_info >= (3, 8):
-    py_version_above_38 = True
-elif sys.version_info >= (3, 5) and sys.version_info < (3, 8):
-    py_version_above_38 = False
-else:
-    raise Exception("Must use Python 3.5+ ")
-
 
-class BaseCaseTests(unittest.TestCase):
 
+class DefaultTypeInferer(unittest.TestCase):
     def test_case_1(self):
-        infferer = TypeInference(name='case1.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case1.py')
+        infferer = TypeInference(
+            name="case1.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case1.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
-        if py_version_above_38:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([
-                {'file': 'case1.py', 'line_number': 10, 'function': 'my_function', 'type': {'Dict[str, str]'}},
-                {'file': 'case1.py', 'line_number': 14, 'variable': 'my_var', 'function': 'my_function',
-                 'type': {'Dict[str, str]'}}],key=lambda x: str(x))
-        else:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([
-                {'file': 'case1.py', 'line_number': 10, 'function': 'my_function', 'type': {'Dict[any, any]'}},
-                {'file': 'case1.py', 'line_number': 14, 'variable': 'my_var', 'function': 'my_function',
-                 'type': {'Dict[any, any]'}}],key=lambda x: str(x))
+        assert sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_1()
 
     def test_case_2(self):
-        infferer = TypeInference(name='case2.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case2.py')
+        infferer = TypeInference(
+            name="case2.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case2.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
-        assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case2.py', 'line_number': 6, 'function': 'my_function', 'type': {'str'}}],key=lambda x: str(x))
+        assert sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_2()
 
     def test_case_3(self):
-        infferer = TypeInference(name='case3.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case3.py')
+        infferer = TypeInference(
+            name="case3.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case3.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
-        if py_version_above_38:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([
-                {'file': 'case3.py', 'line_number': 8, 'function': 'my_function', 'type': {'str', 'int'}},
-                {'file': 'case3.py', 'line_number': 8, 'parameter': 'my_bool', 'function': 'my_function',
-                 'type': {'any'}},
-                {'file': 'case3.py', 'line_number': 10, 'variable': 'my_int', 'function': 'my_function', 'type': {'int'}},
-                {'file': 'case3.py', 'line_number': 13, 'variable': 'my_str', 'function': 'my_function', 'type': {'str'}}],key=lambda x: str(x))
-        else:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([
-                {'file': 'case3.py', 'line_number': 8, 'function': 'my_function', 'type': {'str', 'int'}},
-                {'file': 'case3.py', 'line_number': 8, 'parameter': 'my_bool', 'function': 'my_function',
-                 'type': {'any'}},
-                {'file': 'case3.py', 'line_number': 10, 'variable': 'my_int', 'function': 'my_function', 'type': {'any'}},
-                {'file': 'case3.py', 'line_number': 13, 'variable': 'my_str', 'function': 'my_function', 'type': {'any'}}],key=lambda x: str(x))
+        assert sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_3()
 
     def test_case_4(self):
-        infferer = TypeInference(name='case4.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case4.py')
+        infferer = TypeInference(
+            name="case4.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case4.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
-        assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case4.py', 'line_number': 12, 'function': 'MyClass.get_hash', 'type': {'Dict[any, any]'}}],key=lambda x: str(x))
+        assert sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_4()
 
     def test_case_5(self):
-        infferer = TypeInference(name='case5.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case5.py')
+        infferer = TypeInference(
+            name="case5.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case5.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
-        if py_version_above_38:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case5.py', 'line_number': 9, 'function': 'my_function', 'type': {'int'}},
-                                {'file': 'case5.py', 'line_number': 10, 'variable': 'x', 'function': 'my_function',
-                                 'type': {'int'}},
-                                {'file': 'case5.py', 'line_number': 11, 'variable': 'y', 'function': 'my_function',
-                                 'type': {'int'}},
-                                {'file': 'case5.py', 'line_number': 12, 'variable': 'z', 'function': 'my_function',
-                                 'type': {'int'}}],key=lambda x: str(x))
-        else:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case5.py', 'line_number': 9, 'function': 'my_function', 'type': {None}},
-                                {'file': 'case5.py', 'line_number': 10, 'variable': 'x', 'function': 'my_function',
-                                 'type': {'any'}},
-                                {'file': 'case5.py', 'line_number': 11, 'variable': 'y', 'function': 'my_function',
-                                 'type': {'any'}},
-                                {'file': 'case5.py', 'line_number': 12, 'variable': 'z', 'function': 'my_function',
-                                 'type': {'any'}}],key=lambda x: str(x))
+        assert sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_5()
 
     def test_case_6(self):
-        infferer = TypeInference(name='case6.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case6.py')
+        infferer = TypeInference(
+            name="case6.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case6.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
-        if py_version_above_38:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case6.py', 'line_number': 9, 'function': 'my_function', 'type': {'str'}},
-                                {'file': 'case6.py', 'line_number': 10, 'variable': 'x', 'function': 'my_function',
-                                 'type': {'int'}},
-                                {'file': 'case6.py', 'line_number': 11, 'variable': 'y', 'function': 'my_function',
-                                 'type': {'int'}},
-                                {'file': 'case6.py', 'line_number': 12, 'variable': 'z', 'function': 'my_function',
-                                 'type': {'int'}}],key=lambda x: str(x))
-        else:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case6.py', 'line_number': 9, 'function': 'my_function', 'type': {'str'}},
-                                {'file': 'case6.py', 'line_number': 10, 'variable': 'x', 'function': 'my_function',
-                                 'type': {'any'}},
-                                {'file': 'case6.py', 'line_number': 11, 'variable': 'y', 'function': 'my_function',
-                                 'type': {'any'}},
-                                {'file': 'case6.py', 'line_number': 12, 'variable': 'z', 'function': 'my_function',
-                                 'type': {'any'}}],key=lambda x: str(x))
+        assert sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_6()
 
     def test_case_7(self):
-        infferer = TypeInference(name='case7.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case7.py')
+        infferer = TypeInference(
+            name="case7.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case7.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
         assert inferred == []
 
     def test_case_8(self):
-        infferer = TypeInference(name='case8.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case8.py')
+        infferer = TypeInference(
+            name="case8.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case8.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
-        assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case8.py', 'line_number': 7, 'function': 'my_function', 'type': {'callable'}},
-             {'file': 'case8.py', 'line_number': 8, 'function': 'my_inner_function', 'type': {'int'}}],key=lambda x: str(x))
+        assert sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_8()
 
     def test_case_9(self):
-        infferer = TypeInference(name='case9.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case9.py')
+        infferer = TypeInference(
+            name="case9.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case9.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
-        if py_version_above_38:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([
-                {'file': 'case9.py', 'line_number': 13, 'function': 'ParentClass.my_function', 'type': {'List[int]'}},
-                {'file': 'case9.py', 'line_number': 21, 'function': 'ChildClass.my_function', 'type': {'List[int]'}}],key=lambda x: str(x))
-        else:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([
-                {'file': 'case9.py', 'line_number': 13, 'function': 'ParentClass.my_function', 'type': {'List[None]'}},
-                {'file': 'case9.py', 'line_number': 21, 'function': 'ChildClass.my_function', 'type': {'List[None]'}}],key=lambda x: str(x))
+        assert sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_9()
 
     def test_case_10(self):
-        infferer = TypeInference(name='case10.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case10.py')
+        infferer = TypeInference(
+            name="case10.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case10.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
-        assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case10.py', 'line_number': 10, 'function': 'my_function', 'type': {'defaultdict'}},
-             {'file': 'case10.py', 'line_number': 11, 'variable': 'my_default_dict', 'function': 'my_function',
-              'type': {'defaultdict'}}],key=lambda x: str(x))
+        assert (
+            sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_10()
+        )
 
     def test_case_11(self):
-        infferer = TypeInference(name='case11.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case11.py')
+        infferer = TypeInference(
+            name="case11.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case11.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
-        if py_version_above_38:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([
-                {'file': 'case11.py', 'line_number': 7, 'function': 'my_function', 'type': {'int', 'str'}},
-                {'file': 'case11.py', 'line_number': 7, 'parameter': 'my_bool', 'function': 'my_function',
-                 'type': {'any'}},
-                {'file': 'case11.py', 'line_number': 9, 'variable': 'x', 'function': 'my_function', 'type': {'int'}},
-                {'file': 'case11.py', 'line_number': 11, 'variable': 'x', 'function': 'my_function', 'type': {'str'}}],key=lambda x: str(x))
-        else:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([
-                {'file': 'case11.py', 'line_number': 7, 'function': 'my_function', 'type': {'int', 'str'}},
-                {'file': 'case11.py', 'line_number': 7, 'parameter': 'my_bool', 'function': 'my_function',
-                 'type': {'any'}},
-                {'file': 'case11.py', 'line_number': 9, 'variable': 'x', 'function': 'my_function', 'type': {'any'}},
-                {'file': 'case11.py', 'line_number': 11, 'variable': 'x', 'function': 'my_function', 'type': {'any'}}],key=lambda x: str(x))
-
+        assert (
+            sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_11()
+        )
 
     def test_case_12(self):
-        infferer = TypeInference(name='case12.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case12.py')
+        infferer = TypeInference(
+            name="case12.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case12.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
-        if py_version_above_38:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case12.py', 'line_number': 12, 'function': 'my_function', 'type': {'int'}},
-                                {'file': 'case12.py', 'line_number': 12, 'parameter': 'my_val',
-                                 'function': 'my_function', 'type': {'int'}},
-                                {'file': 'case12.py', 'line_number': 13, 'variable': 'x', 'function': 'my_function',
-                                 'type': {'int'}},
-                                {'file': 'case12.py', 'line_number': 14, 'variable': 'y', 'function': 'my_function',
-                                 'type': {'int'}},
-                                {'file': 'case12.py', 'line_number': 15, 'variable': 'g', 'function': 'my_function',
-                                 'type': {'int'}},
-                                {'file': 'case12.py', 'line_number': 16, 'variable': 'z', 'function': 'my_function',
-                                 'type': {'int'}}],key=lambda x: str(x))
-        else:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case12.py', 'line_number': 12, 'function': 'my_function', 'type': {None}},
-                                {'file': 'case12.py', 'line_number': 12, 'parameter': 'my_val',
-                                 'function': 'my_function', 'type': {'any'}},
-                                {'file': 'case12.py', 'line_number': 13, 'variable': 'x', 'function': 'my_function',
-                                 'type': {'any'}},
-                                {'file': 'case12.py', 'line_number': 14, 'variable': 'y', 'function': 'my_function',
-                                 'type': {'any'}},
-                                {'file': 'case12.py', 'line_number': 15, 'variable': 'g', 'function': 'my_function',
-                                 'type': {'any'}},
-                                {'file': 'case12.py', 'line_number': 16, 'variable': 'z', 'function': 'my_function',
-                                 'type': {'any'}}],key=lambda x: str(x))
+        assert (
+            sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_12()
+        )
 
     def test_case_13(self):
-        infferer = TypeInference(name='case13.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case13.py')
+        infferer = TypeInference(
+            name="case13.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case13.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
-        assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case13.py', 'line_number': 8, 'function': 'first_function', 'type': {'str'}},
-             {'file': 'case13.py', 'line_number': 12, 'function': 'second_function', 'type': {'str'}},
-             {'file': 'case13.py', 'line_number': 16, 'function': 'third_function', 'type': {'str'}}],key=lambda x: str(x))
+        assert (
+            sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_13()
+        )
 
     def test_case_14(self):
-        infferer = TypeInference(name='case14.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case14.py')
+        infferer = TypeInference(
+            name="case14.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case14.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
-        if py_version_above_38:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case14.py', 'line_number': 8, 'function': 'my_function', 'type': {'int'}},
-                                {'file': 'case14.py', 'line_number': 9, 'variable': 'x', 'function': 'my_function',
-                                 'type': {'int'}},
-                                {'file': 'case14.py', 'line_number': 10, 'variable': 'z', 'function': 'my_function',
-                                 'type': {'int'}}],key=lambda x: str(x))
-        else:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case14.py', 'line_number': 8, 'function': 'my_function', 'type': {None}},
-                                {'file': 'case14.py', 'line_number': 9, 'variable': 'x', 'function': 'my_function',
-                                 'type': {'any'}},
-                                {'file': 'case14.py', 'line_number': 10, 'variable': 'z', 'function': 'my_function',
-                                 'type': {'any'}}],key=lambda x: str(x))
+        assert (
+            sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_14()
+        )
 
     def test_case_15(self):
-        infferer = TypeInference(name='case15.py', entry_point=current_directory+'/test-cases/typeinfer_basecase/case15.py')
+        infferer = TypeInference(
+            name="case15.py",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case15.py",
+        )
         infferer.infer_types()
         inferred = infferer.get_types()
-
-        if py_version_above_38:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case15.py', 'line_number': 11, 'function': 'my_function', 'type': {'str'}},
-                                {'file': 'case15.py', 'line_number': 12, 'variable': 'x', 'function': 'my_function',
-                                 'type': {'str'}}],key=lambda x: str(x))
-        else:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case15.py', 'line_number': 11, 'function': 'my_function', 'type': {None}},
-                                {'file': 'case15.py', 'line_number': 12, 'variable': 'x', 'function': 'my_function',
-                                 'type': {'any'}}],key=lambda x: str(x))
+        assert (
+            sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_15()
+        )
 
     def test_case_16(self):
-        inferrer = TypeInference(name='case16', entry_point=current_directory+'/test-cases/typeinfer_basecase/case16')
+        inferrer = TypeInference(
+            name="case16",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case16",
+        )
         inferrer.infer_types()
         inferred = inferrer.get_types()
-        print(inferred)
-        if py_version_above_38:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([
-                {'file': 'file1.py', 'line_number': 7, 'function': 'class_method', 'type': {'Tuple[str]'}},
-                {'file': 'file1.py', 'line_number': 7, 'function': 'MyClass.class_method', 'type': {'Tuple[str]'}},
-                {'file': 'file2.py', 'line_number': 1, 'function': 'imported_function', 'type': {'Tuple[str]'}},
-                {'file': 'file2.py', 'line_number': 2, 'variable': 'return_value', 'function': 'imported_function',
-                 'type': {'Tuple[str]'}}],key=lambda x: str(x))
-        else:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([
-                {'file': 'file1.py', 'line_number': 6, 'function': 'class_method', 'type': {'Tuple[str]'}},
-                {'file': 'file1.py', 'line_number': 6, 'function': 'MyClass.class_method', 'type': {'Tuple[str]'}},
-                {'file': 'file2.py', 'line_number': 1, 'function': 'imported_function', 'type': {'Tuple[str]'}},
-                {'file': 'file2.py', 'line_number': 2, 'variable': 'return_value', 'function': 'imported_function',
-                 'type': {'Tuple[str]'}}],key=lambda x: str(x))
-
+        assert (
+            sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_16()
+        )
 
     def test_case_17(self):
-        inferrer = TypeInference(name='case17', entry_point=current_directory+'/test-cases/typeinfer_basecase/case17.py')
+        inferrer = TypeInference(
+            name="case17",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case17.py",
+        )
         inferrer.infer_types()
         inferred = inferrer.get_types()
-
-
-        if py_version_above_38:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case17.py', 'line_number': 1, 'function': 'fun1', 'type': {'float'}},
-                                {'file': 'case17.py', 'line_number': 5, 'function': 'fun2', 'type': {'float'}},
-                                {'file': 'case17.py', 'line_number': 1, 'parameter': 'a', 'function': 'fun1',
-                                 'type': {'float'}},
-                                {'file': 'case17.py', 'line_number': 5, 'parameter': 'a', 'function': 'fun2',
-                                 'type': {'any'}}],key=lambda x: str(x))
-        else:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case17.py', 'line_number': 1, 'function': 'fun1', 'type': {'any'}},
-                                {'file': 'case17.py', 'line_number': 5, 'function': 'fun2', 'type': {None}},
-                                {'file': 'case17.py', 'line_number': 1, 'parameter': 'a', 'function': 'fun1',
-                                 'type': {'any'}},
-                                {'file': 'case17.py', 'line_number': 5, 'parameter': 'a', 'function': 'fun2',
-                                 'type': {'any'}}],key=lambda x: str(x))
+        assert (
+            sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_17()
+        )
 
     def test_case_18(self):
-        inferrer = TypeInference(name='case18', entry_point=current_directory+'/test-cases/typeinfer_basecase/case18.py')
+        inferrer = TypeInference(
+            name="case18",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case18.py",
+        )
         inferrer.infer_types()
         inferred = inferrer.get_types()
-        assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case18.py', 'line_number': 5, 'function': 'MyParentClass.my_function', 'type': {'str'}},
-             {'file': 'case18.py', 'line_number': 11, 'function': 'ChildClass.my_function', 'type': {'List[str]'}}],key=lambda x: str(x))
+        assert (
+            sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_18()
+        )
 
     def test_case_19(self):
         # TODO: Resolve this test case
-        inferrer = TypeInference(name='case18', entry_point=current_directory+'/test-cases/typeinfer_basecase/case19.py')
+        inferrer = TypeInference(
+            name="case18",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case19.py",
+        )
         inferrer.infer_types()
         inferred = inferrer.get_types()
         # self.assertEqual(inferred, [])
 
     def test_case_20(self):
-        inferrer = TypeInference(name='case20', entry_point=current_directory+'/test-cases/typeinfer_basecase/case20.py')
+        inferrer = TypeInference(
+            name="case20",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case20.py",
+        )
         inferrer.infer_types()
         inferred = inferrer.get_types()
-        if py_version_above_38:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case20.py', 'line_number': 1, 'function': 'add', 'type': {'int', 'str'}},
-                                {'file': 'case20.py', 'line_number': 1, 'parameter': 'x', 'function': 'add',
-                                 'type': {'int', 'str'}},
-                                {'file': 'case20.py', 'line_number': 1, 'parameter': 'y', 'function': 'add',
-                                 'type': {'int', 'str'}}],key=lambda x: str(x))
-
-        else:
-            assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case20.py', 'line_number': 1, 'function': 'add', 'type': {None}},
-                                {'file': 'case20.py', 'line_number': 1, 'parameter': 'x', 'function': 'add',
-                                 'type': {'any'}},
-                                {'file': 'case20.py', 'line_number': 1, 'parameter': 'y', 'function': 'add',
-                                 'type': {'any'}}],key=lambda x: str(x))
+        assert (
+            sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_20()
+        )
 
     def test_case_23(self):
-        inferrer = TypeInference(name='case23', entry_point=current_directory+'/test-cases/typeinfer_basecase/case23.py')
+        inferrer = TypeInference(
+            name="case23",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case23.py",
+        )
         inferrer.infer_types()
         inferred = inferrer.get_types()
-        assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case23.py', 'line_number': 1, 'function': 'my_function',
-                             'type': {'int', 'str'}},
-                            {'file': 'case23.py', 'line_number': 1, 'parameter': 'x', 'function': 'my_function',
-                             'type': {'str', 'int'}}],key=lambda x: str(x))
+        assert (
+            sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_23()
+        )
 
     def test_case_24(self):
-        inferrer = TypeInference(name='case24', entry_point=current_directory+'/test-cases/typeinfer_basecase/case24.py')
+        inferrer = TypeInference(
+            name="case24",
+            entry_point=current_directory + "/test-cases/typeinfer_basecase/case24.py",
+        )
         inferrer.infer_types()
         inferred = inferrer.get_types()
-        assert sorted(inferred,key=lambda x: str(x)) == sorted([{'file': 'case24.py', 'line_number': 1, 'function': 'my_function', 'type': {'any'}},
-             {'file': 'case24.py', 'line_number': 1, 'parameter': 'x', 'function': 'my_function', 'type': {'callable'}},
-             {'file': 'case24.py', 'line_number': 2, 'variable': 'y', 'function': 'my_function', 'type': {'any'}}],key=lambda x: str(x))
+        assert (
+            sorted(inferred, key=lambda x: str(x)) == ActualData.actual_data_case_24()
+        )
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     unittest.main()
diff --git a/tests/test_typeinfer_actual_data.py b/tests/test_typeinfer_actual_data.py
new file mode 100644
index 00000000..a99b87b4
--- /dev/null
+++ b/tests/test_typeinfer_actual_data.py
@@ -0,0 +1,982 @@
+import sys
+
+py_version_above_38 = True
+if sys.version_info >= (3, 8):
+    py_version_above_38 = True
+elif sys.version_info >= (3, 5) and sys.version_info < (3, 8):
+    py_version_above_38 = False
+else:
+    raise Exception("Must use Python 3.5+ ")
+
+
+def actual_data_case_1():
+    if py_version_above_38:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case1.py",
+                    "line_number": 10,
+                    "function": "my_function",
+                    "type": {"Dict[str, str]"},
+                },
+                {
+                    "file": "case1.py",
+                    "line_number": 14,
+                    "variable": "my_var",
+                    "function": "my_function",
+                    "type": {"Dict[str, str]"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+    else:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case1.py",
+                    "line_number": 10,
+                    "function": "my_function",
+                    "type": {"Dict[any, any]"},
+                },
+                {
+                    "file": "case1.py",
+                    "line_number": 14,
+                    "variable": "my_var",
+                    "function": "my_function",
+                    "type": {"Dict[any, any]"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+
+    return actual_data
+
+
+def actual_data_case_2():
+    actual_data = sorted(
+        [
+            {
+                "file": "case2.py",
+                "line_number": 6,
+                "function": "my_function",
+                "type": {"str"},
+            }
+        ],
+        key=lambda x: str(x),
+    )
+
+    return actual_data
+
+
+def actual_data_case_3():
+    if py_version_above_38:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case3.py",
+                    "line_number": 8,
+                    "function": "my_function",
+                    "type": {"str", "int"},
+                },
+                {
+                    "file": "case3.py",
+                    "line_number": 8,
+                    "parameter": "my_bool",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case3.py",
+                    "line_number": 10,
+                    "variable": "my_int",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+                {
+                    "file": "case3.py",
+                    "line_number": 13,
+                    "variable": "my_str",
+                    "function": "my_function",
+                    "type": {"str"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+    else:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case3.py",
+                    "line_number": 8,
+                    "function": "my_function",
+                    "type": {"str", "int"},
+                },
+                {
+                    "file": "case3.py",
+                    "line_number": 8,
+                    "parameter": "my_bool",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case3.py",
+                    "line_number": 10,
+                    "variable": "my_int",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case3.py",
+                    "line_number": 13,
+                    "variable": "my_str",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+
+    return actual_data
+
+
+def actual_data_case_4():
+    actual_data = sorted(
+        [
+            {
+                "file": "case4.py",
+                "line_number": 12,
+                "function": "MyClass.get_hash",
+                "type": {"Dict[any, any]"},
+            }
+        ],
+        key=lambda x: str(x),
+    )
+
+    return actual_data
+
+
+def actual_data_case_5():
+    if py_version_above_38:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case5.py",
+                    "line_number": 9,
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+                {
+                    "file": "case5.py",
+                    "line_number": 10,
+                    "variable": "x",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+                {
+                    "file": "case5.py",
+                    "line_number": 11,
+                    "variable": "y",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+                {
+                    "file": "case5.py",
+                    "line_number": 12,
+                    "variable": "z",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+    else:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case5.py",
+                    "line_number": 9,
+                    "function": "my_function",
+                    "type": {None},
+                },
+                {
+                    "file": "case5.py",
+                    "line_number": 10,
+                    "variable": "x",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case5.py",
+                    "line_number": 11,
+                    "variable": "y",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case5.py",
+                    "line_number": 12,
+                    "variable": "z",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+
+    return actual_data
+
+
+def actual_data_case_6():
+    if py_version_above_38:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case6.py",
+                    "line_number": 9,
+                    "function": "my_function",
+                    "type": {"str"},
+                },
+                {
+                    "file": "case6.py",
+                    "line_number": 10,
+                    "variable": "x",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+                {
+                    "file": "case6.py",
+                    "line_number": 11,
+                    "variable": "y",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+                {
+                    "file": "case6.py",
+                    "line_number": 12,
+                    "variable": "z",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+    else:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case6.py",
+                    "line_number": 9,
+                    "function": "my_function",
+                    "type": {"str"},
+                },
+                {
+                    "file": "case6.py",
+                    "line_number": 10,
+                    "variable": "x",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case6.py",
+                    "line_number": 11,
+                    "variable": "y",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case6.py",
+                    "line_number": 12,
+                    "variable": "z",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+
+    return actual_data
+
+
+def actual_data_case_7():
+    actual_data = sorted(
+        [
+            {
+                "file": "case7.py",
+                "line_number": 8,
+                "variable": "_",
+                "function": "my_function",
+                "type": {"int"},
+            },
+        ],
+        key=lambda x: str(x),
+    )
+    return actual_data
+
+
+def actual_data_case_8():
+    actual_data = sorted(
+        [
+            {
+                "file": "case8.py",
+                "line_number": 7,
+                "function": "my_function",
+                "type": {"callable"},
+            },
+            {
+                "file": "case8.py",
+                "line_number": 8,
+                "function": "my_inner_function",
+                "type": {"int"},
+            },
+        ],
+        key=lambda x: str(x),
+    )
+
+    return actual_data
+
+
+def actual_data_case_9():
+    if py_version_above_38:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case9.py",
+                    "line_number": 13,
+                    "function": "ParentClass.my_function",
+                    "type": {"List[int]"},
+                },
+                {
+                    "file": "case9.py",
+                    "line_number": 21,
+                    "function": "ChildClass.my_function",
+                    "type": {"List[int]"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+    else:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case9.py",
+                    "line_number": 13,
+                    "function": "ParentClass.my_function",
+                    "type": {"List[None]"},
+                },
+                {
+                    "file": "case9.py",
+                    "line_number": 21,
+                    "function": "ChildClass.my_function",
+                    "type": {"List[None]"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+
+    return actual_data
+
+
+def actual_data_case_10():
+    actual_data = sorted(
+        [
+            {
+                "file": "case10.py",
+                "line_number": 10,
+                "function": "my_function",
+                "type": {"defaultdict"},
+            },
+            {
+                "file": "case10.py",
+                "line_number": 11,
+                "variable": "my_default_dict",
+                "function": "my_function",
+                "type": {"defaultdict"},
+            },
+        ],
+        key=lambda x: str(x),
+    )
+
+    return actual_data
+
+
+def actual_data_case_11():
+    if py_version_above_38:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case11.py",
+                    "line_number": 7,
+                    "function": "my_function",
+                    "type": {"int", "str"},
+                },
+                {
+                    "file": "case11.py",
+                    "line_number": 7,
+                    "parameter": "my_bool",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case11.py",
+                    "line_number": 9,
+                    "variable": "x",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+                {
+                    "file": "case11.py",
+                    "line_number": 11,
+                    "variable": "x",
+                    "function": "my_function",
+                    "type": {"str"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+    else:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case11.py",
+                    "line_number": 7,
+                    "function": "my_function",
+                    "type": {"int", "str"},
+                },
+                {
+                    "file": "case11.py",
+                    "line_number": 7,
+                    "parameter": "my_bool",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case11.py",
+                    "line_number": 9,
+                    "variable": "x",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case11.py",
+                    "line_number": 11,
+                    "variable": "x",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+
+    return actual_data
+
+
+def actual_data_case_12():
+    if py_version_above_38:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case12.py",
+                    "line_number": 12,
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+                {
+                    "file": "case12.py",
+                    "line_number": 12,
+                    "parameter": "my_val",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+                {
+                    "file": "case12.py",
+                    "line_number": 13,
+                    "variable": "x",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+                {
+                    "file": "case12.py",
+                    "line_number": 14,
+                    "variable": "y",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+                {
+                    "file": "case12.py",
+                    "line_number": 15,
+                    "variable": "g",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+                {
+                    "file": "case12.py",
+                    "line_number": 16,
+                    "variable": "z",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+    else:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case12.py",
+                    "line_number": 12,
+                    "function": "my_function",
+                    "type": {None},
+                },
+                {
+                    "file": "case12.py",
+                    "line_number": 12,
+                    "parameter": "my_val",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case12.py",
+                    "line_number": 13,
+                    "variable": "x",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case12.py",
+                    "line_number": 14,
+                    "variable": "y",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case12.py",
+                    "line_number": 15,
+                    "variable": "g",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case12.py",
+                    "line_number": 16,
+                    "variable": "z",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+
+    return actual_data
+
+
+def actual_data_case_13():
+    actual_data = sorted(
+        [
+            {
+                "file": "case13.py",
+                "line_number": 8,
+                "function": "first_function",
+                "type": {"str"},
+            },
+            {
+                "file": "case13.py",
+                "line_number": 12,
+                "function": "second_function",
+                "type": {"str"},
+            },
+            {
+                "file": "case13.py",
+                "line_number": 16,
+                "function": "third_function",
+                "type": {"str"},
+            },
+        ],
+        key=lambda x: str(x),
+    )
+
+    return actual_data
+
+
+def actual_data_case_14():
+    if py_version_above_38:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case14.py",
+                    "line_number": 8,
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+                {
+                    "file": "case14.py",
+                    "line_number": 9,
+                    "variable": "x",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+                {
+                    "file": "case14.py",
+                    "line_number": 10,
+                    "variable": "z",
+                    "function": "my_function",
+                    "type": {"int"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+    else:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case14.py",
+                    "line_number": 8,
+                    "function": "my_function",
+                    "type": {None},
+                },
+                {
+                    "file": "case14.py",
+                    "line_number": 9,
+                    "variable": "x",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case14.py",
+                    "line_number": 10,
+                    "variable": "z",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+
+    return actual_data
+
+
+def actual_data_case_15():
+    if py_version_above_38:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case15.py",
+                    "line_number": 11,
+                    "function": "my_function",
+                    "type": {"str"},
+                },
+                {
+                    "file": "case15.py",
+                    "line_number": 12,
+                    "variable": "x",
+                    "function": "my_function",
+                    "type": {"str"},
+                },
+                {
+                    "file": "case15.py",
+                    "line_number": 8,
+                    "function": "getcwd",
+                    "type": {"str"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+    else:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case15.py",
+                    "line_number": 11,
+                    "function": "my_function",
+                    "type": {None},
+                },
+                {
+                    "file": "case15.py",
+                    "line_number": 12,
+                    "variable": "x",
+                    "function": "my_function",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case15.py",
+                    "line_number": 8,
+                    "function": "getcwd",
+                    "type": {"str"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+
+    return actual_data
+
+
+def actual_data_case_16():
+    if py_version_above_38:
+        actual_data = sorted(
+            [
+                {
+                    "file": "file1.py",
+                    "line_number": 7,
+                    "function": "class_method",
+                    "type": {"Tuple[str]"},
+                },
+                {
+                    "file": "file1.py",
+                    "line_number": 7,
+                    "function": "MyClass.class_method",
+                    "type": {"Tuple[str]"},
+                },
+                {
+                    "file": "file2.py",
+                    "line_number": 1,
+                    "function": "imported_function",
+                    "type": {"Tuple[str]"},
+                },
+                {
+                    "file": "file2.py",
+                    "line_number": 2,
+                    "variable": "return_value",
+                    "function": "imported_function",
+                    "type": {"Tuple[str]"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+    else:
+        actual_data = sorted(
+            [
+                {
+                    "file": "file1.py",
+                    "line_number": 6,
+                    "function": "class_method",
+                    "type": {"Tuple[str]"},
+                },
+                {
+                    "file": "file1.py",
+                    "line_number": 6,
+                    "function": "MyClass.class_method",
+                    "type": {"Tuple[str]"},
+                },
+                {
+                    "file": "file2.py",
+                    "line_number": 1,
+                    "function": "imported_function",
+                    "type": {"Tuple[str]"},
+                },
+                {
+                    "file": "file2.py",
+                    "line_number": 2,
+                    "variable": "return_value",
+                    "function": "imported_function",
+                    "type": {"Tuple[str]"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+
+    return actual_data
+
+
+def actual_data_case_17():
+    if py_version_above_38:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case17.py",
+                    "line_number": 1,
+                    "function": "fun1",
+                    "type": {"float"},
+                },
+                {
+                    "file": "case17.py",
+                    "line_number": 5,
+                    "function": "fun2",
+                    "type": {"float"},
+                },
+                {
+                    "file": "case17.py",
+                    "line_number": 1,
+                    "parameter": "a",
+                    "function": "fun1",
+                    "type": {"float"},
+                },
+                {
+                    "file": "case17.py",
+                    "line_number": 5,
+                    "parameter": "a",
+                    "function": "fun2",
+                    "type": {"any"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+    else:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case17.py",
+                    "line_number": 1,
+                    "function": "fun1",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case17.py",
+                    "line_number": 5,
+                    "function": "fun2",
+                    "type": {None},
+                },
+                {
+                    "file": "case17.py",
+                    "line_number": 1,
+                    "parameter": "a",
+                    "function": "fun1",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case17.py",
+                    "line_number": 5,
+                    "parameter": "a",
+                    "function": "fun2",
+                    "type": {"any"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+
+    return actual_data
+
+
+def actual_data_case_18():
+    actual_data = sorted(
+        [
+            {
+                "file": "case18.py",
+                "line_number": 5,
+                "function": "MyParentClass.my_function",
+                "type": {"str"},
+            },
+            {
+                "file": "case18.py",
+                "line_number": 11,
+                "function": "ChildClass.my_function",
+                "type": {"List[str]"},
+            },
+        ],
+        key=lambda x: str(x),
+    )
+
+    return actual_data
+
+
+def actual_data_case_19():
+    pass
+
+
+def actual_data_case_20():
+    if py_version_above_38:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case20.py",
+                    "line_number": 1,
+                    "function": "add",
+                    "type": {"int", "str"},
+                },
+                {
+                    "file": "case20.py",
+                    "line_number": 1,
+                    "parameter": "x",
+                    "function": "add",
+                    "type": {"int", "str"},
+                },
+                {
+                    "file": "case20.py",
+                    "line_number": 1,
+                    "parameter": "y",
+                    "function": "add",
+                    "type": {"int", "str"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+
+    else:
+        actual_data = sorted(
+            [
+                {
+                    "file": "case20.py",
+                    "line_number": 1,
+                    "function": "add",
+                    "type": {None},
+                },
+                {
+                    "file": "case20.py",
+                    "line_number": 1,
+                    "parameter": "x",
+                    "function": "add",
+                    "type": {"any"},
+                },
+                {
+                    "file": "case20.py",
+                    "line_number": 1,
+                    "parameter": "y",
+                    "function": "add",
+                    "type": {"any"},
+                },
+            ],
+            key=lambda x: str(x),
+        )
+
+    return actual_data
+
+
+def actual_data_case_23():
+    actual_data = sorted(
+        [
+            {
+                "file": "case23.py",
+                "line_number": 1,
+                "function": "my_function",
+                "type": {"int", "str"},
+            },
+            {
+                "file": "case23.py",
+                "line_number": 1,
+                "parameter": "x",
+                "function": "my_function",
+                "type": {"any"},
+            },
+        ],
+        key=lambda x: str(x),
+    )
+
+    return actual_data
+
+
+def actual_data_case_24():
+    actual_data = sorted(
+        [
+            {
+                "file": "case24.py",
+                "line_number": 1,
+                "function": "my_function",
+                "type": {"any"},
+            },
+            {
+                "file": "case24.py",
+                "line_number": 1,
+                "parameter": "x",
+                "function": "my_function",
+                "type": {"callable"},
+            },
+            {
+                "file": "case24.py",
+                "line_number": 2,
+                "variable": "y",
+                "function": "my_function",
+                "type": {"any"},
+            },
+        ],
+        key=lambda x: str(x),
+    )
+
+    return actual_data