Skip to content

Commit

Permalink
Move goog.require processing out of ClosureModuleProcessor
Browse files Browse the repository at this point in the history
A followup CL will make EsModuleProcessor also use this logic.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=237312066
  • Loading branch information
lauraharker authored and blickly committed Mar 7, 2019
1 parent 605a9b3 commit 701e9b2
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 114 deletions.
13 changes: 9 additions & 4 deletions src/com/google/javascript/jscomp/modules/Binding.java
Expand Up @@ -79,7 +79,14 @@ enum CreatedBy {
* const x = goog.forwardDeclare();
* const {x} = goog.forwardDeclare();
*/
GOOG_FORWARD_DECLARE,
GOOG_FORWARD_DECLARE;

/** Whether this is some goog.* dependency import */
boolean isClosureImport() {
return this.equals(GOOG_REQUIRE)
|| this.equals(GOOG_REQUIRE_TYPE)
|| this.equals(GOOG_FORWARD_DECLARE);
}
}

/** Binding for an exported value that is not a module namespace object. */
Expand All @@ -97,9 +104,7 @@ static Binding from(Export boundExport, Node sourceNode) {
static Binding from(
ModuleMetadata metadata, Node sourceNode, String closureNamespace, CreatedBy createdBy) {
Preconditions.checkArgument(
createdBy == CreatedBy.GOOG_REQUIRE
|| createdBy == CreatedBy.GOOG_FORWARD_DECLARE
|| createdBy == CreatedBy.GOOG_REQUIRE_TYPE,
createdBy.isClosureImport(),
"Expected goog.require(Type) or goog.forwardDeclare, got %s",
createdBy);
return new AutoValue_Binding(
Expand Down
127 changes: 17 additions & 110 deletions src/com/google/javascript/jscomp/modules/ClosureModuleProcessor.java
Expand Up @@ -26,7 +26,7 @@
import com.google.javascript.jscomp.NodeTraversal;
import com.google.javascript.jscomp.NodeTraversal.AbstractPreOrderCallback;
import com.google.javascript.jscomp.deps.ModuleLoader.ModulePath;
import com.google.javascript.jscomp.modules.Binding.CreatedBy;
import com.google.javascript.jscomp.modules.ClosureRequireProcessor.Require;
import com.google.javascript.jscomp.modules.ModuleMapCreator.ModuleProcessor;
import com.google.javascript.jscomp.modules.ModuleMetadataMap.ModuleMetadata;
import com.google.javascript.rhino.Node;
Expand All @@ -47,16 +47,6 @@
*/
final class ClosureModuleProcessor implements ModuleProcessor {

private static class Require {
private final Import i;
private final Binding.CreatedBy createdBy;

private Require(Import i, Binding.CreatedBy createdBy) {
this.i = i;
this.createdBy = createdBy;
}
}

private static class UnresolvedGoogModule extends UnresolvedModule {

private final ModuleMetadata metadata;
Expand Down Expand Up @@ -142,41 +132,42 @@ Map<String, Binding> getAllResolvedImports(ModuleRequestResolver moduleRequestRe

ResolveExportResult resolveImport(ModuleRequestResolver moduleRequestResolver, String name) {
Require require = requiresByLocalName.get(name);
Import i = require.i;
Import importRecord = require.importRecord();

UnresolvedModule requested = moduleRequestResolver.resolve(i);
UnresolvedModule requested = moduleRequestResolver.resolve(importRecord);

if (requested == null) {
return ResolveExportResult.ERROR;
} else if (i.importName().equals(Export.NAMESPACE)) {
} else if (importRecord.importName().equals(Export.NAMESPACE)) {
// Return a binding based on the other module's metadata.
return ResolveExportResult.of(
Binding.from(
requested.metadata(),
requested.metadata().rootNode(),
i.moduleRequest(),
require.createdBy));
importRecord.moduleRequest(),
require.createdBy()));

} else {
ResolveExportResult result =
requested.resolveExport(
moduleRequestResolver,
i.moduleRequest(),
i.importName(),
importRecord.moduleRequest(),
importRecord.importName(),
new HashSet<>(),
new HashSet<>());
if (!result.found() && !result.hadError()) {
compiler.report(
JSError.make(
srcFileName,
i.importNode().getLineno(),
i.importNode().getCharno(),
importRecord.importNode().getLineno(),
importRecord.importNode().getCharno(),
DOES_NOT_HAVE_EXPORT,
i.importName()));
importRecord.importName()));
return ResolveExportResult.ERROR;
}
Node forSourceInfo = i.nameNode() == null ? i.importNode() : i.nameNode();
return result.copy(forSourceInfo, require.createdBy);
Node forSourceInfo =
importRecord.nameNode() == null ? importRecord.importNode() : importRecord.nameNode();
return result.copy(forSourceInfo, require.createdBy());
}
}

Expand Down Expand Up @@ -364,68 +355,10 @@ private void addPropertyExport(String exportedId, Node propNode) {
propNode));
}

/**
* Adds a goog.require to the list of imports
*
* @param nameDeclaration a VAR, LET, or CONST
*/
/** Adds a goog.require(Type) or forwardDeclare to the list of {@code requiresByLocalName} */
private void maybeInitializeRequire(Node nameDeclaration) {
// TODO(b/123598058): These requires can also be in ES modules. Move this code to a common
// location.
Node rhs =
nameDeclaration.getFirstChild().isDestructuringLhs()
? nameDeclaration.getFirstChild().getSecondChild()
: nameDeclaration.getFirstFirstChild();
// this may be a require, requireType, or forwardDeclare
Binding.CreatedBy requireKind = getModuleDependencyType(rhs);
if (requireKind == null) {
return;
}
String namespace = rhs.getSecondChild().getString();
if (nameDeclaration.getFirstChild().isName()) {
// const modA = goog.require('modA');
Node lhs = nameDeclaration.getFirstChild();
requiresByLocalName.putIfAbsent(
lhs.getString(),
new Require(
Import.builder()
.moduleRequest(namespace)
.localName(lhs.getString())
.importName(Export.NAMESPACE)
.importNode(nameDeclaration)
.nameNode(lhs)
.build(),
requireKind));
} else {
// const {x, y} = goog.require('modA');
Node objectPattern = nameDeclaration.getFirstFirstChild();
if (!objectPattern.isObjectPattern()) {
// bad JS, ignore
return;
}
for (Node key : objectPattern.children()) {
if (!key.isStringKey()) {
// Bad code, just ignore. We warn elsewhere.
continue;
}
Node lhs = key.getOnlyChild();
if (!lhs.isName()) {
// Bad code ( e.g. `const {a = 0} = goog.require(...)`). We warn elsewhere.
continue;
}

requiresByLocalName.putIfAbsent(
lhs.getString(),
new Require(
Import.builder()
.moduleRequest(namespace)
.localName(lhs.getString())
.importName(key.getString())
.importNode(nameDeclaration)
.nameNode(lhs)
.build(),
requireKind));
}
for (Require require : ClosureRequireProcessor.getAllRequires(nameDeclaration)) {
requiresByLocalName.putIfAbsent(require.localName(), require);
}
}
}
Expand Down Expand Up @@ -453,30 +386,4 @@ private static boolean isNamedExportsLiteral(Node objLit) {
}
return true;
}

// TODO(b/123598058): move this code to a common location.
private static final ImmutableMap<String, Binding.CreatedBy> GOOG_DEPENDENCY_CALLS =
ImmutableMap.of(
"require",
CreatedBy.GOOG_REQUIRE,
"requireType",
CreatedBy.GOOG_REQUIRE_TYPE,
"forwardDeclare",
CreatedBy.GOOG_FORWARD_DECLARE);

@Nullable
private static CreatedBy getModuleDependencyType(Node value) {
if (value == null || !value.isCall()) {
return null;
}
Node callee = value.getFirstChild();
if (!callee.isGetProp()) {
return null;
}
Node owner = callee.getFirstChild();
if (!owner.isName() || !owner.getString().equals("goog")) {
return null;
}
return GOOG_DEPENDENCY_CALLS.get(callee.getSecondChild().getString());
}
}
174 changes: 174 additions & 0 deletions src/com/google/javascript/jscomp/modules/ClosureRequireProcessor.java
@@ -0,0 +1,174 @@
/*
* Copyright 2019 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.modules;

import static com.google.common.base.Preconditions.checkArgument;

import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.javascript.jscomp.NodeUtil;
import com.google.javascript.jscomp.modules.Binding.CreatedBy;
import com.google.javascript.rhino.Node;
import javax.annotation.Nullable;

/**
* Handles creating an {@link Import} from goog.require(Type) or goog.forwardDeclare.
*
* <p>This logic can be used by both goog.modules and ES modules
*/
final class ClosureRequireProcessor {
private final Node nameDeclaration;
private final CreatedBy requireKind;

/** Represents a goog.require(Type) or goog.forwardDeclare */
@AutoValue
abstract static class Require {
/** The name local to the module with the require; e.g. `b` in `const b = goog.require('a');` */
abstract String localName();
/** An {@link Import} containing all metadata about this require */
abstract Import importRecord();
/** Whether this is a goog.require, goog.requireType, or goog.forwardDeclare */
abstract Binding.CreatedBy createdBy();

private static Require create(
String localName, Import importRecord, Binding.CreatedBy createdBy) {
checkArgument(createdBy.isClosureImport());
return new AutoValue_ClosureRequireProcessor_Require(localName, importRecord, createdBy);
}
}

private ClosureRequireProcessor(Node nameDeclaration, Binding.CreatedBy requireKind) {
checkArgument(NodeUtil.isNameDeclaration(nameDeclaration));
this.nameDeclaration = nameDeclaration;
this.requireKind = requireKind;
}

/**
* Returns all Require built from the given statement, or null if it is not a require
*
* @param nameDeclaration a VAR, LET, or CONST
* @return all Requires contained in this declaration
*/
static ImmutableList<Require> getAllRequires(Node nameDeclaration) {
Node rhs =
nameDeclaration.getFirstChild().isDestructuringLhs()
? nameDeclaration.getFirstChild().getSecondChild()
: nameDeclaration.getFirstFirstChild();
// This may be a require, requireType, or forwardDeclare.
Binding.CreatedBy requireKind = getModuleDependencyTypeFromRhs(rhs);
if (requireKind == null) {
return ImmutableList.of();
}

return new ClosureRequireProcessor(nameDeclaration, requireKind).getAllRequiresInDeclaration();
}

private static final ImmutableMap<String, CreatedBy> GOOG_DEPENDENCY_CALLS =
ImmutableMap.of(
"require",
CreatedBy.GOOG_REQUIRE,
"requireType",
CreatedBy.GOOG_REQUIRE_TYPE,
"forwardDeclare",
CreatedBy.GOOG_FORWARD_DECLARE);

/**
* Checks if the given rvalue is a goog.require(Type) or goog.forwardDeclare call, and if so
* returns which one.
*
* @return A Closure require (where {@link CreatedBy#isClosureImport()} is true) or null.
*/
@Nullable
private static CreatedBy getModuleDependencyTypeFromRhs(@Nullable Node value) {
if (value == null || !value.isCall()) {
return null;
}
Node callee = value.getFirstChild();
if (!callee.isGetProp()) {
return null;
}
Node owner = callee.getFirstChild();
if (!owner.isName() || !owner.getString().equals("goog")) {
return null;
}
return GOOG_DEPENDENCY_CALLS.get(callee.getSecondChild().getString());
}

/** Returns a new list of all required names in {@link #nameDeclaration} */
private ImmutableList<Require> getAllRequiresInDeclaration() {
Node rhs =
nameDeclaration.getFirstChild().isDestructuringLhs()
? nameDeclaration.getFirstChild().getSecondChild()
: nameDeclaration.getFirstFirstChild();

String namespace = rhs.getSecondChild().getString();

if (nameDeclaration.getFirstChild().isName()) {
// const modA = goog.require('modA');
Node lhs = nameDeclaration.getFirstChild();
return ImmutableList.of(
Require.create(
lhs.getString(),
Import.builder()
.moduleRequest(namespace)
.localName(lhs.getString())
.importName(Export.NAMESPACE)
.importNode(nameDeclaration)
.nameNode(lhs)
.build(),
requireKind));
} else {
// const {x, y} = goog.require('modA');
Node objectPattern = nameDeclaration.getFirstFirstChild();
if (!objectPattern.isObjectPattern()) {
// bad JS, ignore
return ImmutableList.of();
}
return getAllRequiresFromDestructuring(objectPattern, namespace);
}
}

/** Returns all requires from destructruring, like `const {x, y, z} = goog.require('a');` */
private ImmutableList<Require> getAllRequiresFromDestructuring(
Node objectPattern, String namespace) {
ImmutableList.Builder<Require> requireBuilder = ImmutableList.builder();
for (Node key : objectPattern.children()) {
if (!key.isStringKey()) {
// Bad code, just ignore. We warn elsewhere.
continue;
}
Node lhs = key.getOnlyChild();
if (!lhs.isName()) {
// Bad code ( e.g. `const {a = 0} = goog.require(...)`). We warn elsewhere.
continue;
}

requireBuilder.add(
Require.create(
lhs.getString(),
Import.builder()
.moduleRequest(namespace)
.localName(lhs.getString())
.importName(key.getString())
.importNode(nameDeclaration)
.nameNode(lhs)
.build(),
requireKind));
}
return requireBuilder.build();
}
}

0 comments on commit 701e9b2

Please sign in to comment.