All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.newtypes.Namespace Maven / Gradle / Ivy

/*
 * Copyright 2014 The Closure Compiler Authors.
 *
 * 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.javascript.jscomp.newtypes;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.javascript.rhino.Node;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

/**
 * An object whose properties can be types (nominal types, enums, typedefs).
 * Constructor/interface functions, enums and object literals can be namespaces.
 *
 * @author [email protected] (Ben Lickly)
 * @author [email protected] (Dimitris Vardoulakis)
 */
public abstract class Namespace {
  private Map namespaces = ImmutableMap.of();
  // Finalize the namespace after GTI and null-out the typedefs.
  private Map typedefs = ImmutableMap.of();
  // "Simple type" properties (i.e. represented as JSTypes rather than something
  // more specific).
  protected PersistentMap otherProps = PersistentMap.create();
  protected final String name;
  protected final JSTypes commonTypes;
  // Represents the namespace as an ObjectType wrapped in a JSType.
  // The namespace field of the ObjectType contains the namespace instance.
  // In addition,
  // For a function/constructor namespace, the fn field is the function type.
  // For window as namespace, the nominalType field is Window.
  protected JSType namespaceType;
  // Used to detect recursion when computing the type of circular namespaces.
  private boolean duringComputeJSType = false;
  // The node that defines this namespace.
  protected final Node defSite;

  protected Namespace(JSTypes commonTypes, String name, Node defSite) {
    this.name = name;
    this.commonTypes = commonTypes;
    this.defSite = Preconditions.checkNotNull(defSite);
  }

  protected abstract JSType computeJSType();

  public final String getName() {
    return name;
  }

  public Node getDefSite() {
    return this.defSite;
  }

  private boolean isDefined(String name) {
    return namespaces.containsKey(name)
        || typedefs.containsKey(name)
        || otherProps.containsKey(name);
  }

  public final boolean isDefined(QualifiedName qname) {
    String name;
    Namespace ns;
    if (qname.isIdentifier()) {
      name = qname.getLeftmostName();
      ns = this;
    } else {
      name = qname.getRightmostName();
      ns = getSubnamespace(qname.getAllButRightmost());
      if (ns == null) {
        return false;
      }
    }
    return ns.isDefined(name);
  }

  public void addNamespace(QualifiedName qname, Namespace ns) {
    Preconditions.checkState(!isDefined(qname));
    Preconditions.checkState(this.namespaceType == null);
    Namespace subns = getReceiverNamespace(qname);
    if (subns.namespaces.isEmpty()) {
      subns.namespaces = new LinkedHashMap<>();
    }
    String name = qname.getRightmostName();
    Preconditions.checkState(!subns.namespaces.containsKey(name));
    subns.namespaces.put(name, ns);
  }

  // For a function namespace, when we compute the function summary during NTI,
  // we update the type here for more precision.
  void updateNamespaceType(JSType t) {
    Preconditions.checkNotNull(t);
    Preconditions.checkNotNull(this.namespaceType);
    this.namespaceType = t;
  }

  public final Declaration getDeclaration(QualifiedName qname) {
    Namespace recv = getReceiverNamespace(qname);
    if (recv == null) {
      return null;
    }
    String name = qname.getRightmostName();
    if (!recv.isDefined(name)) {
      return null;
    }
    Namespace ns = recv.namespaces.get(name);
    DeclaredTypeRegistry scope = ns instanceof FunctionNamespace
        ? ((FunctionNamespace) ns).getScope() : null;
    return new Declaration(
        recv.getPropDeclaredType(name),
        recv.typedefs.get(name),
        ns, scope, false, false);
  }

  public final void addTypedef(QualifiedName qname, Typedef td) {
    Preconditions.checkState(!isDefined(qname));
    Preconditions.checkState(this.namespaceType == null);
    Namespace ns = getReceiverNamespace(qname);
    if (ns.typedefs.isEmpty()) {
      ns.typedefs = new LinkedHashMap<>();
    }
    String name = qname.getRightmostName();
    ns.typedefs.put(name, td);
  }

  private Namespace getReceiverNamespace(QualifiedName qname) {
    if (qname.isIdentifier()) {
      return this;
    } else {
      return getSubnamespace(qname.getAllButRightmost());
    }
  }

  public final Namespace getSubnamespace(QualifiedName qname) {
    String leftmost = qname.getLeftmostName();
    Namespace firstNamespace = this.namespaces.get(leftmost);
    if (firstNamespace == null || qname.isIdentifier()) {
      return firstNamespace;
    } else {
      return firstNamespace.getSubnamespace(qname.getAllButLeftmost());
    }
  }

  public final boolean hasSubnamespace(QualifiedName qname) {
    return getSubnamespace(qname) != null;
  }

  // Static properties

  public final boolean hasProp(String pname) {
    Property prop = otherProps.get(pname);
    if (prop == null) {
      return false;
    }
    Preconditions.checkState(!prop.isOptional());
    return true;
  }

  /** Add a new non-optional declared property to this namespace */
  public final void addProperty(
      String pname, Node defSite, JSType type, boolean isConstant) {
    Preconditions.checkState(this.namespaceType == null);
    if (type == null && isConstant) {
      type = this.commonTypes.UNKNOWN;
    }
    otherProps = otherProps.with(pname, isConstant
        ? Property.makeConstant(defSite, type, type)
        : Property.makeWithDefsite(defSite, type, type));
  }

  /** Add a new undeclared property to this namespace */
  public final void addUndeclaredProperty(
      String pname, Node defSite, JSType t, boolean isConstant) {
    Preconditions.checkState(this.namespaceType == null);
    if (otherProps.containsKey(pname)
        && !otherProps.get(pname).getType().isUnknown()) {
      return;
    }
    otherProps = otherProps.with(pname, isConstant
        ? Property.makeConstant(defSite, t, null)
        : Property.makeWithDefsite(defSite, t, null));
  }

  public final JSType getPropDeclaredType(String pname) {
    Property p = otherProps.get(pname);
    return p == null ? null : p.getDeclaredType();
  }

  final Property getNsProp(String pname) {
    if (this.namespaces.containsKey(pname)) {
      Namespace subns = this.namespaces.get(pname);
      Preconditions.checkState(subns.namespaceType != null);
      return Property.makeWithDefsite(subns.getDefSite(), subns.namespaceType, subns.namespaceType);
    }
    if (this.otherProps.containsKey(pname)) {
      return this.otherProps.get(pname);
    }
    // Do instanceof check instead of making the method non-final, because it
    // should only be overriden by NamespaceLit, not by the other subclasses.
    if (this instanceof NamespaceLit) {
      NominalType maybeWin = ((NamespaceLit) this).getWindowType();
      if (maybeWin != null) {
        return maybeWin.getProp(pname);
      }
    }
    return null;
  }

  final Set getAllPropsOfNamespace() {
    Set s = new LinkedHashSet<>();
    s.addAll(this.namespaces.keySet());
    s.addAll(this.otherProps.keySet());
    return s;
  }

  public final JSType toJSType() {
    if (this.namespaceType == null) {
      Preconditions.checkNotNull(commonTypes);
      for (Namespace ns : this.namespaces.values()) {
        if (this.duringComputeJSType) {
          return null;
        }
        this.duringComputeJSType = true;
        ns.toJSType();
        this.duringComputeJSType = false;
      }
      this.namespaceType = Preconditions.checkNotNull(computeJSType());
    }
    return this.namespaceType;
  }

  // Copy properties from window to Window.prototype, because in rare cases
  // people pass window around rather than using it directly.
  public final void copyWindowProperties(JSTypes commonTypes, RawNominalType win) {
    Preconditions.checkArgument(win.getName().equals("Window"));
    Preconditions.checkNotNull(this.namespaces,
        "The built-in types are missing from window. "
        + "Perhaps you forgot to run DeclaredGlobalExternsOnWindow?");

    for (Map.Entry entry : this.namespaces.entrySet()) {
      Namespace ns = entry.getValue();
      if (ns instanceof RawNominalType) {
        RawNominalType rawType = (RawNominalType) ns;
        // Hack: circular namespace here, skip adding Window.
        if (!rawType.isFinalized()) {
          Preconditions.checkState(rawType.getName().equals("Window"),
              "Unexpected unfinalized type %s", rawType.getName());
          continue;
        }
      }
      win.addProtoProperty(entry.getKey(), null, ns.toJSType(), true);
    }
    for (Map.Entry entry : this.otherProps.entrySet()) {
      win.addProtoProperty(
          entry.getKey(), null, entry.getValue().getType(), true);
    }
  }

  @Override
  public String toString() {
    return this.name;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy