Package com.google.caja.ancillary.linter

Source Code of com.google.caja.ancillary.linter.ScopeAnalyzer$Use

// Copyright (C) 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.caja.ancillary.linter;

import com.google.caja.parser.AncestorChain;
import com.google.caja.parser.ParseTreeNode;
import com.google.caja.parser.js.AssignOperation;
import com.google.caja.parser.js.CatchStmt;
import com.google.caja.parser.js.Declaration;
import com.google.caja.parser.js.FunctionConstructor;
import com.google.caja.parser.js.Identifier;
import com.google.caja.parser.js.Operation;
import com.google.caja.parser.js.Operator;
import com.google.caja.parser.js.Reference;
import com.google.caja.parser.js.WithStmt;
import com.google.caja.util.SyntheticAttributeKey;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
* Given a DOM tree, associates lexical scopes with nodes and identifies uses.
*
* @author mikesamuel@gmail.com
*/
class ScopeAnalyzer {
  private static final SyntheticAttributeKey<LexicalScope> CONTAINING_SCOPE
      = new SyntheticAttributeKey<LexicalScope>(
        LexicalScope.class, "containingScope");

  private static final SyntheticAttributeKey<LexicalScope> DEFINING_SCOPE
      = new SyntheticAttributeKey<LexicalScope>(
        LexicalScope.class, "definingScope");

  static final Collection<String> ECMASCRIPT_BUILTINS
      = Collections.unmodifiableCollection(ImmutableSet.of(
          "Array",
          "Boolean",
          "Date",
          "decodeURI",
          "decodeURIComponent",
          "encodeURI",
          "encodeURIComponent",
          "Error",
          "EvalError",
          "Function",
          "Infinity",
          "isFinite",
          "isNaN",
          "Number",
          "Object",
          "parseFloat",
          "parseInt",
          "Math",
          "NaN",
          "RangeError",
          "ReferenceError",
          "RegExp",
          "String",
          "SyntaxError",
          "TypeError",
          "URIError",
          "undefined"));

  /**
   * May be overridden to make different decisions about when to introduce
   * a new lexical scope.
   * @return true whether the given node introduces a new lexical scope.
   */
  @SuppressWarnings("static-method")
  protected boolean introducesScope(AncestorChain<?> ac) {
    ParseTreeNode node = ac.node;
    return node instanceof FunctionConstructor || node instanceof CatchStmt
        || node instanceof WithStmt;
  }

  /**
   * May be overridden to implement hoisting differently.
   * @return true iff the given declaration should be hoisted out of the given
   *      scope.
   */
  @SuppressWarnings("static-method")
  protected boolean hoist(AncestorChain<?> d, LexicalScope s) {
    if (d.node instanceof Declaration && s.isCatchScope()) {
      return d.parent != null && !(d.parent.node instanceof CatchStmt);
    }
    return s.isWithScope();
  }

  /**
   * Initializes a scope's symbol table.  This is important for the builtins
   * in the global scope, and for state visible to a function.
   * <p>
   * This method may be overridden to initialize scope symbols differently.
   * It is not limited to operating only on the input scope -- to simulate
   * JScript quirks, an implementation might introducing a binding into an
   * ancestor scope for a named {@link FunctionConstructor}.
   *
   * @param scope to have its {@link LexicalScope#symbols symbol table}
   *     modified.
   */
  @SuppressWarnings("static-method")
  protected void initScope(LexicalScope scope) {
    if (scope.isFunctionScope()) {
      AncestorChain<FunctionConstructor> fn
          = scope.root.cast(FunctionConstructor.class);
      if (fn.node.getIdentifierName() != null) {
        scope.symbols.declare(fn.node.getIdentifierName(), fn);
      }
      scope.symbols.declare("this", fn);
      scope.symbols.declare("arguments", fn);
    } else if (scope.parent == null) {  // The global scope
      for (String builtin : ECMASCRIPT_BUILTINS) {
        scope.symbols.declare(builtin, scope.root);
      }
    }
  }

  /**
   * Computes lexical scopes for the given parse tree, attaching information
   * to the parse tree nodes.  This assumes that each node under root appears
   * at most once in the tree.
   * @return the scopes created.  The global scope will be the zero-th element
   *     in the list.
   */
  final List<LexicalScope> computeLexicalScopes(AncestorChain<?> root) {
    LexicalScope globalScope = new LexicalScope(root, null);
    initScope(globalScope);
    List<LexicalScope> scopes = Lists.newArrayList(globalScope);
    computeLexicalScopes(root, globalScope, scopes);
    return scopes;
  }

  /**
   * @param ac the parse tree to whom lexical scoping rules are to be applied.
   * @param parent the scope of ac's parent.
   * @param scopes a list that receives newly created scopes.
   */
  private void computeLexicalScopes(
      AncestorChain<?> ac, LexicalScope parent, List<LexicalScope> scopes) {
    // Compute the scope for the current node.
    // Since we create a global scope in the original caller, avoid creating
    // two scopes for the same object here.
    LexicalScope scope = parent;
    if (introducesScope(ac) && scope.root != ac) {
      scope = new LexicalScope(ac, parent);
      scopes.add(scope);
      // Sets up the symbol table.
      initScope(scope);
    }
    assert (
        ac.node instanceof Identifier
        || !ac.node.getAttributes().containsKey(CONTAINING_SCOPE))
        : "Scope already attached to node";
    ac.node.getAttributes().set(CONTAINING_SCOPE, scope);
    // initScope may have set up some symbols, but if this is a declaration,
    // do the appropriate hoisting and declarations.
    if (ac.node instanceof Declaration) {
      AncestorChain<Declaration> d = ac.cast(Declaration.class);
      LexicalScope definingScope = scope;
      while (definingScope.parent != null && hoist(d, definingScope)) {
        definingScope = definingScope.parent;
      }
      ac.node.getAttributes().set(DEFINING_SCOPE, definingScope);
      definingScope.symbols.declare(ac.cast(Declaration.class));
    }
    // recurse to children
    for (ParseTreeNode child : ac.node.children()) {
      computeLexicalScopes(AncestorChain.instance(ac, child), scope, scopes);
    }
  }

  /** Returns all the uses of symbols in the given AST. */
  static List<Use> getUses(AncestorChain<?> root) {
    List<Use> uses = Lists.newArrayList();
    findUses(root, uses);
    return uses;
  }

  private static void findUses(AncestorChain<?> ac, List<Use> out) {
    if (ac.node instanceof Reference) {
      out.add(new Use(ac.cast(Reference.class)));
      return;
    }

    if (ac.node instanceof Operation) {
      Operator op = ac.cast(Operation.class).node.getOperator();
      if (op == Operator.MEMBER_ACCESS) {
        findUses(AncestorChain.instance(ac, ac.node.children().get(0)), out);
        // Do not recurse to member name
        return;
      }
    }
    for (ParseTreeNode child : ac.node.children()) {
      findUses(AncestorChain.instance(ac, child), out);
    }
  }

  /**
   * The scope containing the node.  This can only be called after
   * {@link #computeLexicalScopes} has been called on an ancestor node.
   */
  static LexicalScope containingScopeForNode(ParseTreeNode node) {
    return node.getAttributes().get(CONTAINING_SCOPE);
  }

  /**
   * The scope containing the node.  This can only be called after
   * {@link #computeLexicalScopes} has been called on an ancestor node.
   */
  static LexicalScope definingScopeForNode(Declaration decl) {
    return decl.getAttributes().get(DEFINING_SCOPE);
  }

  /** A use of a particular symbol. */
  static final class Use {
    final AncestorChain<? extends Reference> ref;

    Use(AncestorChain<? extends Reference> usage) { this.ref = usage; }

    boolean isLeftHandSideExpression() {
      AncestorChain<?> ac = ref;
      while (isObjectInMemberAccess(ac)) { ac = ac.parent; }
      return ac.parent != null && ac.parent.node instanceof AssignOperation
          && ac.node == ac.parent.node.children().get(0);
    }

    boolean isMemberAccess() {
      return isObjectInMemberAccess(ref);
    }

    private static boolean isObjectInMemberAccess(AncestorChain<?> ac) {
      if (ac.parent == null || !(ac.parent.node instanceof Operation)) {
        return false;
      }
      Operator op = ac.parent.cast(Operation.class).node.getOperator();
      return op == Operator.MEMBER_ACCESS || op == Operator.SQUARE_BRACKET
          && ac.node == ac.parent.node.children().get(0);
    }

    String getSymbolName() {
      return ref.node.getIdentifierName();
    }
  }
}
TOP

Related Classes of com.google.caja.ancillary.linter.ScopeAnalyzer$Use

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.