Skip to content

Commit

Permalink
Support resolving legacy goog.module namespaces in PolymerBehaviorExt…
Browse files Browse the repository at this point in the history
…ractor

Also memoizes behavior resolution, as this CL increases the amount of work it does (and the followup CL will do even more)

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=263166727
  • Loading branch information
lauraharker committed Aug 14, 2019
1 parent 4aa1a8c commit 5f85520
Show file tree
Hide file tree
Showing 14 changed files with 460 additions and 50 deletions.
5 changes: 5 additions & 0 deletions src/com/google/javascript/jscomp/AbstractCompiler.java
Expand Up @@ -21,6 +21,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.MustBeClosed;
import com.google.javascript.jscomp.deps.ModuleLoader;
Expand Down Expand Up @@ -265,6 +266,10 @@ public abstract class AbstractCompiler implements SourceExcerptProvider, Compile
@VisibleForTesting
abstract Node parseTestCode(String code);

/** Parses code for testing. */
@VisibleForTesting
abstract Node parseTestCode(ImmutableList<String> code);

/**
* Prints a node to source code.
*/
Expand Down
23 changes: 23 additions & 0 deletions src/com/google/javascript/jscomp/Compiler.java
Expand Up @@ -30,6 +30,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.debugging.sourcemap.SourceMapConsumerV3;
import com.google.debugging.sourcemap.proto.Mapping.OriginalMapping;
import com.google.javascript.jscomp.CompilerOptions.DevMode;
Expand Down Expand Up @@ -90,6 +91,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -2090,12 +2092,33 @@ Node parseTestCode(String js) {
return parseCodeHelper(SourceFile.fromCode("[testcode]", js));
}

@Override
@VisibleForTesting
Node parseTestCode(ImmutableList<String> code) {
initCompilerOptionsIfTesting();
initBasedOnOptions();
return parseCodeHelper(
Streams.mapWithIndex(
code.stream(), (value, index) -> SourceFile.fromCode("testcode" + index, value))
.collect(Collectors.toList()));
}

private Node parseCodeHelper(SourceFile src) {
CompilerInput input = new CompilerInput(src);
putCompilerInput(input.getInputId(), input);
return checkNotNull(input.getAstRoot(this));
}

private Node parseCodeHelper(List<SourceFile> srcs) {
Node root = IR.root();
for (SourceFile src : srcs) {
CompilerInput input = new CompilerInput(src);
putCompilerInput(input.getInputId(), input);
root.addChildToBack(checkNotNull(input.getAstRoot(this)));
}
return root;
}

@Override
ErrorReporter getDefaultErrorReporter() {
return oldErrorReporter;
Expand Down
5 changes: 4 additions & 1 deletion src/com/google/javascript/jscomp/GlobalNamespace.java
Expand Up @@ -540,7 +540,10 @@ private void collect(JSModule module, Scope scope, Node n) {
// because they use the term 'global' in an ES5, pre-block-scoping sense.
Scope hoistScope = scope.getClosestHoistScope();
// Consider a set to be 'global' if it is in the hoist scope in which the name is defined.
if (hoistScope.isGlobal() || hoistScope.getRootNode() == curModuleRoot) {
// For example, a global name set in a module scope is a 'local' set, but a module-level
// name set in a module scope is a 'global' set.
if (hoistScope.isGlobal()
|| (root != globalRoot && hoistScope.getRootNode() == curModuleRoot)) {
handleSetFromGlobal(module, scope, n, parent, name, type, nameMetadata);
} else {
handleSetFromLocal(module, scope, n, parent, name, nameMetadata);
Expand Down
144 changes: 117 additions & 27 deletions src/com/google/javascript/jscomp/PolymerBehaviorExtractor.java
Expand Up @@ -15,17 +15,25 @@
*/
package com.google.javascript.jscomp;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Table;
import com.google.javascript.jscomp.GlobalNamespace.Name;
import com.google.javascript.jscomp.GlobalNamespace.Ref;
import com.google.javascript.jscomp.PolymerPass.MemberDefinition;
import com.google.javascript.jscomp.modules.Module;
import com.google.javascript.jscomp.modules.ModuleMetadataMap;
import com.google.javascript.jscomp.modules.ModuleMetadataMap.ModuleMetadata;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;

/**
Expand All @@ -38,22 +46,35 @@ final class PolymerBehaviorExtractor {
"created", "attached", "detached", "attributeChanged", "configure", "ready",
"properties", "listeners", "observers", "hostAttributes");

private static final String GOOG_MODULE_EXPORTS = "exports";

private final AbstractCompiler compiler;
private final GlobalNamespace globalNames;
private final ModuleMetadataMap moduleMetadataMap;

private final Table<String, ModuleMetadata, ResolveBehaviorNameResult> resolveMemoized =
HashBasedTable.create();
private final Map<String, ResolveBehaviorNameResult> globalResolveMemoized = new HashMap<>();

PolymerBehaviorExtractor(AbstractCompiler compiler, GlobalNamespace globalNames) {
PolymerBehaviorExtractor(
AbstractCompiler compiler, GlobalNamespace globalNames, ModuleMetadataMap moduleMetadataMap) {
this.compiler = compiler;
this.globalNames = globalNames;
this.moduleMetadataMap = moduleMetadataMap;
}

/**
* Extracts all Behaviors from an array literal, recursively. Entries in the array can be
* object literals or array literals (of other behaviors). Behavior names must be
* global, fully qualified names.
* Extracts all Behaviors from an array literal, recursively. Entries in the array can be object
* literals or array literals (of other behaviors). Behavior names must be global, fully qualified
* names.
*
* @see https://github.com/Polymer/polymer/blob/0.8-preview/PRIMER.md#behaviors
* @param moduleMetadata The module in which these behaviors are being resolved, or null if not in
* a module.
* @return A list of all {@code BehaviorDefinitions} in the array.
*/
ImmutableList<BehaviorDefinition> extractBehaviors(Node behaviorArray) {
ImmutableList<BehaviorDefinition> extractBehaviors(
Node behaviorArray, @Nullable ModuleMetadata moduleMetadata) {
if (behaviorArray == null) {
return ImmutableList.of();
}
Expand All @@ -78,25 +99,25 @@ ImmutableList<BehaviorDefinition> extractBehaviors(Node behaviorArray) {
behaviorName,
PolymerClassDefinition.DefinitionType.ObjectLiteral,
compiler,
/** constructor= */
null),
/* constructor= */ null),
getBehaviorFunctionsToCopy(behaviorName),
getNonPropertyMembersToCopy(behaviorName),
!NodeUtil.isInFunction(behaviorName),
(FeatureSet) NodeUtil.getEnclosingScript(behaviorName).getProp(Node.FEATURE_SET)));
continue;
}

ResolveBehaviorNameResult resolveResult = resolveBehaviorName(behaviorName);
if (resolveResult == null) {
ResolveBehaviorNameResult resolveResult =
resolveBehaviorName(getQualifiedNameThroughCast(behaviorName), moduleMetadata);
if (resolveResult.equals(FAILED_RESOLVE_RESULT)) {
compiler.report(JSError.make(behaviorName, PolymerPassErrors.POLYMER_UNQUALIFIED_BEHAVIOR));
continue;
}
Node behaviorValue = resolveResult.node;

if (behaviorValue.isArrayLit()) {
// Individual behaviors can also be arrays of behaviors. Parse them recursively.
behaviors.addAll(extractBehaviors(behaviorValue));
behaviors.addAll(extractBehaviors(behaviorValue, resolveResult.moduleMetadata));
} else if (behaviorValue.isObjectLit()) {
PolymerPassStaticUtils.switchDollarSignPropsToBrackets(behaviorValue, compiler);
PolymerPassStaticUtils.quoteListenerAndHostAttributeKeys(behaviorValue, compiler);
Expand All @@ -109,8 +130,7 @@ ImmutableList<BehaviorDefinition> extractBehaviors(Node behaviorArray) {
behaviorValue,
PolymerClassDefinition.DefinitionType.ObjectLiteral,
compiler,
/** constructor= */
null),
/* constructor= */ null),
getBehaviorFunctionsToCopy(behaviorValue),
getNonPropertyMembersToCopy(behaviorValue),
resolveResult.isGlobalDeclaration,
Expand All @@ -126,32 +146,74 @@ ImmutableList<BehaviorDefinition> extractBehaviors(Node behaviorArray) {
private static class ResolveBehaviorNameResult {
final Node node;
final boolean isGlobalDeclaration;
final ModuleMetadata moduleMetadata;

public ResolveBehaviorNameResult(Node node, boolean isGlobalDeclaration) {
ResolveBehaviorNameResult(
Node node, boolean isGlobalDeclaration, ModuleMetadata moduleMetadata) {
this.node = node;
this.isGlobalDeclaration = isGlobalDeclaration;
this.moduleMetadata = moduleMetadata;
}
}

private static final ResolveBehaviorNameResult FAILED_RESOLVE_RESULT =
new ResolveBehaviorNameResult(null, false, null);

/**
* Resolve an identifier, which is presumed to refer to a Polymer Behavior declaration, using the
* global namespace. Recurses to resolve assignment chains of any length.
*
* @param nameNode The NAME, GETPROP, or CAST node containing the identifier.
* @return The behavior declaration node, or null if it couldn't be resolved.
* <p>This method memoizes {@link #resolveBehaviorNameInternal(String, ModuleMetadata)}
*
* @param name the name of the identifier, which may be qualified.
* @param moduleMetadata the module (ES module or goog.module) this name is resolved in, or null
* if not in a module.
* @return The behavior declaration node, or {@link #FAILED_RESOLVE_RESULT} if it couldn't be
* resolved.
*/
@Nullable
private ResolveBehaviorNameResult resolveBehaviorName(Node nameNode) {
String name = getQualifiedNameThroughCast(nameNode);
private ResolveBehaviorNameResult resolveBehaviorName(
@Nullable String name, ModuleMetadata moduleMetadata) {
if (name == null) {
return null;
return FAILED_RESOLVE_RESULT;
}
Name globalName = globalNames.getSlot(name);
ResolveBehaviorNameResult memoized =
moduleMetadata != null
? resolveMemoized.get(name, moduleMetadata)
: globalResolveMemoized.get(name);
if (memoized == null) {
memoized = checkNotNull(resolveBehaviorNameInternal(name, moduleMetadata));
if (moduleMetadata != null) {
resolveMemoized.put(name, moduleMetadata, memoized);
} else {
globalResolveMemoized.put(name, memoized);
}
}
return memoized;
}

/**
* Implements behavior resolution. Call {@link #resolveBehaviorName(String, ModuleMetadata)}}
* instead.
*/
private ResolveBehaviorNameResult resolveBehaviorNameInternal(
String name, ModuleMetadata moduleMetadata) {
// Check if this name is possibly from a legacy goog.module
ResolveBehaviorNameResult legacyResolve = resolveReferenceToLegacyGoogModule(name);
if (legacyResolve != null) {
return legacyResolve;
}

// If not, look it up within the current module.
Name moduleLevelName =
moduleMetadata != null ? globalNames.getNameFromModule(moduleMetadata, name) : null;
Name globalName = moduleLevelName == null ? globalNames.getSlot(name) : moduleLevelName;
if (globalName == null) {
return null;
return FAILED_RESOLVE_RESULT;
}

boolean isGlobalDeclaration = true;
// Whether the declaration of this node is in the top-level global scope, as opposed to a module
// or an IIFE.
boolean isGlobalDeclaration = moduleLevelName == null;

// Use any set as a backup declaration, even if it's local.
Ref declarationRef = globalName.getDeclaration();
Expand All @@ -165,21 +227,26 @@ private ResolveBehaviorNameResult resolveBehaviorName(Node nameNode) {
}
}
if (declarationRef == null) {
return null;
return FAILED_RESOLVE_RESULT;
}

Node declarationNode = declarationRef.getNode();
if (declarationNode == null) {
return null;
return FAILED_RESOLVE_RESULT;
}
Node rValue = NodeUtil.getRValueOfLValue(declarationNode);
if (rValue == null) {
return null;
return FAILED_RESOLVE_RESULT;
}

if (rValue.isQualifiedName()) {
// Another identifier; recurse.
return resolveBehaviorName(rValue);
Scope declarationScope = declarationRef.scope.getClosestHoistScope();
Module m =
ModuleImportResolver.getModuleFromScopeRoot(
compiler.getModuleMap(), compiler, declarationScope.getRootNode());
return resolveBehaviorName(
getQualifiedNameThroughCast(rValue), m != null ? m.metadata() : null);
}

JSDocInfo behaviorInfo = NodeUtil.getBestJSDocInfo(declarationNode);
Expand All @@ -188,7 +255,30 @@ private ResolveBehaviorNameResult resolveBehaviorName(Node nameNode) {
JSError.make(declarationNode, PolymerPassErrors.POLYMER_UNANNOTATED_BEHAVIOR));
}

return new ResolveBehaviorNameResult(rValue, isGlobalDeclaration);
return new ResolveBehaviorNameResult(rValue, isGlobalDeclaration, moduleMetadata);
}

/**
* Handles resolving behaviors if they are references to legacy modules
*
* <p>Returns null if the name is not from a legacy module, and resolution should continue
* normally.
*/
private ResolveBehaviorNameResult resolveReferenceToLegacyGoogModule(String name) {
int dot = name.length();
while (dot >= 0) {
String subNamespace = name.substring(0, dot);
ModuleMetadata metadata = moduleMetadataMap.getModulesByGoogNamespace().get(subNamespace);

if (metadata == null || !metadata.isLegacyGoogModule()) {
dot = name.lastIndexOf('.', dot - 1);
continue;
}

String rest = dot == name.length() ? "" : name.substring(dot);
return resolveBehaviorName(GOOG_MODULE_EXPORTS + rest, metadata);
}
return null;
}

/**
Expand Down
14 changes: 9 additions & 5 deletions src/com/google/javascript/jscomp/PolymerClassDefinition.java
Expand Up @@ -22,6 +22,7 @@
import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.PolymerBehaviorExtractor.BehaviorDefinition;
import com.google.javascript.jscomp.PolymerPass.MemberDefinition;
import com.google.javascript.jscomp.modules.ModuleMetadataMap.ModuleMetadata;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.JSDocInfo;
Expand Down Expand Up @@ -99,8 +100,12 @@ static enum DefinitionType {
* Validates the class definition and if valid, destructively extracts the class definition from
* the AST.
*/
@Nullable static PolymerClassDefinition extractFromCallNode(
Node callNode, AbstractCompiler compiler, GlobalNamespace globalNames) {
@Nullable
static PolymerClassDefinition extractFromCallNode(
Node callNode,
AbstractCompiler compiler,
ModuleMetadata moduleMetadata,
PolymerBehaviorExtractor behaviorExtractor) {
Node descriptor = NodeUtil.getArgumentForCallOrNew(callNode, 0);
if (descriptor == null || !descriptor.isObjectLit()) {
// report bad class definition
Expand Down Expand Up @@ -151,9 +156,8 @@ static enum DefinitionType {
String nativeBaseElement = baseClass == null ? null : baseClass.getString();

Node behaviorArray = NodeUtil.getFirstPropMatchingKey(descriptor, "behaviors");
PolymerBehaviorExtractor behaviorExtractor =
new PolymerBehaviorExtractor(compiler, globalNames);
ImmutableList<BehaviorDefinition> behaviors = behaviorExtractor.extractBehaviors(behaviorArray);
ImmutableList<BehaviorDefinition> behaviors =
behaviorExtractor.extractBehaviors(behaviorArray, moduleMetadata);
List<MemberDefinition> allProperties = new ArrayList<>();
for (BehaviorDefinition behavior : behaviors) {
overwriteMembersIfPresent(allProperties, behavior.props);
Expand Down

0 comments on commit 5f85520

Please sign in to comment.