scan(ast) {
    if (!ast) return;
    if (Array.isArray(ast)) {
      for (let i = 0; i < ast.length; i++) {
        this.scan(ast[i]);
      }
      return;
    }
    switch (ast.type) {
      case 'Program':
        this.useFunctionContext(() => {
          this.scan(ast.body);
        });
        break;
      case 'BlockStatement':
        this.newContext(() => {
          this.scan(ast.body);
        });
        break;
      case 'AssignmentExpression':
      case 'LogicalExpression':
        this.scan(ast.left);
        this.scan(ast.right);
        break;
      case 'BinaryExpression':
        this.scan(ast.left);
        this.scan(ast.right);
        break;
      case 'UpdateExpression':
        if (ast.operator === '++') {
          const declaration = this.getDeclaration(ast.argument.name);
          if (declaration) {
            declaration.suggestedType = 'Integer';
          }
        }
        this.scan(ast.argument);
        break;
      case 'UnaryExpression':
        this.scan(ast.argument);
        break;
      case 'VariableDeclaration':
        if (ast.kind === 'var') {
          this.useFunctionContext(() => {
            ast.declarations = utils.normalizeDeclarations(ast);
            this.scan(ast.declarations);
          });
        } else {
          ast.declarations = utils.normalizeDeclarations(ast);
          this.scan(ast.declarations);
        }
        break;
      case 'VariableDeclarator': {
        const { currentContext } = this;
        const inForLoopInit = this.hasState(states.inForLoopInit);
        const declaration = {
          ast: ast,
          context: currentContext,
          name: ast.id.name,
          origin: 'declaration',
          inForLoopInit,
          inForLoopTest: null,
          assignable: currentContext === this.currentFunctionContext || (!inForLoopInit && !currentContext.hasOwnProperty(ast.id.name)),
          suggestedType: null,
          valueType: null,
          dependencies: null,
          isSafe: null,
        };
        if (!currentContext[ast.id.name]) {
          currentContext[ast.id.name] = declaration;
        }
        this.declarations.push(declaration);
        this.scan(ast.id);
        this.scan(ast.init);
        break;
      }
      case 'FunctionExpression':
      case 'FunctionDeclaration':
        if (this.runningContexts.length === 0) {
          this.scan(ast.body);
        } else {
          this.functions.push(ast);
        }
        break;
      case 'IfStatement':
        this.scan(ast.test);
        this.scan(ast.consequent);
        if (ast.alternate) this.scan(ast.alternate);
        break;
      case 'ForStatement': {
        let testIdentifiers;
        const context = this.newContext(() => {
          this.pushState(states.inForLoopInit);
          this.scan(ast.init);
          this.popState(states.inForLoopInit);

          testIdentifiers = this.getIdentifiers(() => {
            this.scan(ast.test);
          });

          this.scan(ast.update);
          this.newContext(() => {
            this.scan(ast.body);
          });
        });

        if (testIdentifiers) {
          for (const p in context) {
            if (p === '@contextType') continue;
            if (testIdentifiers.indexOf(p) > -1) {
              context[p].inForLoopTest = true;
            }
          }
        }
        break;
      }
      case 'DoWhileStatement':
      case 'WhileStatement':
        this.newContext(() => {
          this.scan(ast.body);
          this.scan(ast.test);
        });
        break;
      case 'Identifier': {
        if (this.isState(states.trackIdentifiers)) {
          this.trackedIdentifiers.push(ast.name);
        }
        this.identifiers.push({
          context: this.currentContext,
          declaration: this.getDeclaration(ast.name),
          ast,
        });
        break;
      }
      case 'ReturnStatement':
        this.returnStatements.push(ast);
        this.scan(ast.argument);
        break;
      case 'MemberExpression':
        this.pushState(states.memberExpression);
        this.scan(ast.object);
        this.scan(ast.property);
        this.popState(states.memberExpression);
        break;
      case 'ExpressionStatement':
        this.scan(ast.expression);
        break;
      case 'SequenceExpression':
        this.scan(ast.expressions);
        break;
      case 'CallExpression':
        this.functionCalls.push({
          context: this.currentContext,
          ast,
        });
        this.scan(ast.arguments);
        break;
      case 'ArrayExpression':
        this.scan(ast.elements);
        break;
      case 'ConditionalExpression':
        this.scan(ast.test);
        this.scan(ast.alternate);
        this.scan(ast.consequent);
        break;
      case 'SwitchStatement':
        this.scan(ast.discriminant);
        this.scan(ast.cases);
        break;
      case 'SwitchCase':
        this.scan(ast.test);
        this.scan(ast.consequent);
        break;

      case 'ThisExpression':
      case 'Literal':
      case 'DebuggerStatement':
      case 'EmptyStatement':
      case 'BreakStatement':
      case 'ContinueStatement':
        break;
      default:
        throw new Error(`unhandled type "${ast.type}"`);
    }
  }
Recursively scans AST for declarations and functions, and add them to their respective context
@param ast