Skip to content

Commit

Permalink
Add namespaces from goog.provides to the GlobalNamespace
Browse files Browse the repository at this point in the history
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=261349974
  • Loading branch information
lauraharker authored and tjgq committed Aug 3, 2019
1 parent 4dfd145 commit 532072c
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 1 deletion.
34 changes: 33 additions & 1 deletion src/com/google/javascript/jscomp/GlobalNamespace.java
Expand Up @@ -28,6 +28,7 @@
import com.google.javascript.jscomp.CodingConvention.SubclassRelationship;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.QualifiedName;
import com.google.javascript.rhino.StaticRef;
import com.google.javascript.rhino.StaticScope;
import com.google.javascript.rhino.StaticSlot;
Expand Down Expand Up @@ -63,6 +64,7 @@ class GlobalNamespace
private SourceKind sourceKind;
private Scope externsScope;
private boolean generated = false;
private static final QualifiedName GOOG_PROVIDE = QualifiedName.of("goog.provide");

enum SourceKind {
EXTERN,
Expand Down Expand Up @@ -303,7 +305,12 @@ private boolean isGlobalVarReference(String name, Scope s) {
if (v == null && externsScope != null) {
v = externsScope.getVar(name);
}
return v != null && !v.isLocal();
if (v == null) {
Name providedName = nameMap.get(name);
return providedName != null && providedName.isProvided;
} else {
return !v.isLocal();
}
}

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -463,6 +470,14 @@ private void collect(JSModule module, Scope scope, Node n) {
String qname = n.getFirstFirstChild().getQualifiedName();
Name globalName = getOrCreateName(qname);
globalName.usedHasOwnProperty = true;
} else if (parent.isExprResult()
&& GOOG_PROVIDE.matches(n.getFirstChild())
&& n.getSecondChild().isString()) {
// goog.provide goes through a different code path than regular sets because it can
// create multiple names, e.g. `goog.provide('a.b.c');` creates the global names
// a, a.b, and a.b.c. Other sets only create a single global name.
createNamesFromProvide(n.getSecondChild().getString());
return;
}
return;
default:
Expand Down Expand Up @@ -492,6 +507,22 @@ private void collect(JSModule module, Scope scope, Node n) {
}
}

private void createNamesFromProvide(String namespace) {
Name name;
int dot = 0;

while (dot >= 0) {
dot = namespace.indexOf('.', dot + 1);
String subNamespace = dot < 0 ? namespace : namespace.substring(0, dot);
checkState(!subNamespace.isEmpty());
name = getOrCreateName(subNamespace);
name.isProvided = true;
}

Name newName = getOrCreateName(namespace);
newName.isProvided = true;
}

/**
* Gets the fully qualified name corresponding to an object pattern key, as long as it is not in
* a nested pattern and is destructuring an qualified name.
Expand Down Expand Up @@ -1051,6 +1082,7 @@ private enum Type {
private boolean declaredType = false;
private boolean isDeclared = false;
private boolean isModuleProp = false;
private boolean isProvided = false; // If this name was in any goog.provide() calls.
private boolean usedHasOwnProperty = false;
private int globalSets = 0;
private int localSets = 0;
Expand Down
65 changes: 65 additions & 0 deletions test/com/google/javascript/jscomp/GlobalNamespaceTest.java
Expand Up @@ -35,6 +35,7 @@
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSTypeExpression;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.JSType;
import java.util.Collection;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -755,6 +756,70 @@ public void testCanCollapse_objectLitProperty_declaredBeforeASpread() {
assertThat(fooProp.canCollapse()).isFalse();
}

@Test
public void testGoogProvideName() {
GlobalNamespace namespace = parse("goog.provide('a'); var a = {};");

Name a = namespace.getSlot("a");
assertThat(a).isNotNull();
assertThat(a.getGlobalSets()).isEqualTo(1);
// The VAR, not the goog.provide, is considered the 'declaration' of `a`.
assertNode(a.getDeclaration().getNode().getParent()).hasToken(Token.VAR);
}

@Test
public void testGoogProvideNamespace_noExplicitAssignment() {
GlobalNamespace namespace = parse("goog.provide('a.b');");

Name a = namespace.getSlot("a");
assertThat(a).isNotNull();
assertThat(a.getGlobalSets()).isEqualTo(0);
Name ab = namespace.getSlot("a.b");
assertThat(ab).isNotNull();
assertThat(ab.getGlobalSets()).isEqualTo(0);
assertThat(a.getDeclaration()).isNull();
assertThat(ab.getDeclaration()).isNull();
assertThat(ab.getParent()).isEqualTo(a);
}

@Test
public void testGoogProvideLongNamespace() {
GlobalNamespace namespace = parse("goog.provide('a.b.c.d');");

assertThat(namespace.getSlot("a.b.c.d")).isNotNull();
}

@Test
public void testGoogProvideNamespace_explicitAssignment() {
GlobalNamespace namespace = parse("goog.provide('a.b'); /** @const */ a.b = {};");

Name a = namespace.getSlot("a");
assertThat(a).isNotNull();
assertThat(a.getGlobalSets()).isEqualTo(0);
Name ab = namespace.getSlot("a.b");
assertThat(ab).isNotNull();
assertThat(ab.getGlobalSets()).isEqualTo(1);
}

@Test
public void testGoogProvideNamespace_assignmentToProperty() {
GlobalNamespace namespace = parse("goog.provide('a.b'); a.b.Class = class {};");

Name abClass = namespace.getSlot("a.b.Class");
assertThat(abClass).isNotNull();
assertThat(abClass.getGlobalSets()).isEqualTo(1);
assertThat(abClass.getParent()).isEqualTo(namespace.getSlot("a.b"));
}

@Test
public void testGoogProvideName_multipleProvidesForName() {
GlobalNamespace namespace = parse("goog.provide('a.b'); goog.provide('a.c');");

Name a = namespace.getSlot("a");
assertThat(a).isNotNull();
assertThat(a.getGlobalSets()).isEqualTo(0);
}

private GlobalNamespace parse(String js) {
Compiler compiler = new Compiler();
CompilerOptions options = new CompilerOptions();
Expand Down
Expand Up @@ -86,6 +86,51 @@ public void testArrayBehavior() {
// TODO(jlklein): Actually verify the properties of the BehaviorDefinitions.
}

@Test
public void testArrayBehavior_fromGoogProvide() {
parseAndInitializeExtractor(
lines(
"goog.provide('my.project.RadBehavior');",
"goog.provide('my.project.FunBehavior');",
"/** @polymerBehavior */",
"my.project.FunBehavior = {",
" properties: {",
" isFun: Boolean",
" },",
" /** @param {string} funAmount */",
" doSomethingFun: function(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
" /** @override */",
" created: function() {}",
"};",
"/** @polymerBehavior */",
"my.project.RadBehavior = {",
" properties: {",
" howRad: Number",
" },",
" /** @param {number} radAmount */",
" doSomethingRad: function(radAmount) { alert('Something ' + radAmount + ' rad!'); },",
" /** @override */",
" ready: function() {}",
"};",
"/** @polymerBehavior */",
"var SuperCoolBehaviors = [my.project.FunBehavior, my.project.RadBehavior];",
"/** @polymerBehavior */",
"var BoringBehavior = {",
" properties: {",
" boringString: String",
" },",
" /** @param {boolean} boredYet */",
" doSomething: function(boredYet) { alert(boredYet + ' ' + this.boringString); },",
"};",
"var A = Polymer({",
" is: 'x-element',",
" behaviors: [ SuperCoolBehaviors, BoringBehavior ],",
"});"));

ImmutableList<BehaviorDefinition> defs = extractor.extractBehaviors(behaviorArray);
assertThat(defs).hasSize(3);
}

@Test
public void testInlineLiteralBehavior() {
parseAndInitializeExtractor(
Expand Down

0 comments on commit 532072c

Please sign in to comment.