blob: f8174eb6bb77965889c16bfcd6b34ca5bba8ba8f [file] [log] [blame]
/*
* Copyright 2015 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.gwt.dev.jjs.impl;
import com.google.gwt.dev.javac.JsInteropUtil;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.ast.HasName;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JMember;
import com.google.gwt.dev.js.JsUtils;
import com.google.gwt.dev.js.ast.JsExpression;
import com.google.gwt.dev.js.ast.JsInvocation;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsStatement;
import com.google.gwt.dev.js.ast.JsStringLiteral;
import com.google.gwt.thirdparty.guava.common.base.Joiner;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Responsible for handling @JsExport code generation for Closure formatted code.
* <p>
* In closure formatted mode, there are additional restrictions due to the way goog.provide() works:
* <li> You can't goog.provide something more than once
* <li> Enclosing namespaces must be setup and handled before enclosed namespaces
* <li> If the exported namespace is a @JsType with methods, than the namespace will need to have
* a function assigned to it, instead of an object literal returned by goog.provide.
* <p>
* In general, this implies code like the following:
* <pre>
* goog.provide('dotted.parent.namespace')
* dotted.parent.namespace = constructor (synthesized one or the exported one)
* dotted.parent.namespace.member = blah;
* goog.provide('dotted.parent.namespace.enclosed')
* dotted.parent.namespace.enclosed = (synthesized one or the exported one)
* dotted.parent.namespace.enclosed.member = blah;
* </pre>
*/
class ClosureJsInteropExportsGenerator implements JsInteropExportsGenerator {
private final List<JsStatement> exportStmts;
private final Map<HasName, JsName> names;
private final Set<String> providedNamespaces = new HashSet<String>();
public ClosureJsInteropExportsGenerator(List<JsStatement> exportStmts,
Map<HasName, JsName> names) {
this.exportStmts = exportStmts;
this.names = names;
}
/**
* Ensures that a @JsType without exported constructor is still defined by goog.provide using the
* synthesized constructor so that type declarations like
* {@code /* @returns {number} * / Foo.prototype.method;} that are added by linker works.
*/
@Override
public void exportType(JDeclaredType x) {
assert !x.isJsNative() && !x.isJsFunction();
// Note that synthesized constructors use the name of the declared types.
generateExport(x.getQualifiedJsName(), x.getQualifiedJsName(),
names.get(x).makeRef(x.getSourceInfo()), x.getSourceInfo());
}
/*
* Exports a member as:
* goog.provide('foo.bar');
* foo.bar.memberName = <obfuscated-member-name>;
* or constructor as:
* goog.provide('foo.bar.ClassSimpleName');
* foo.bar.ClassSimpleName = <obfuscated-ctor-name>;
*/
@Override
public void exportMember(JMember member, JsExpression bridgeMethodOrAlias) {
generateExport(member.getJsNamespace(), member.getQualifiedJsName(),
bridgeMethodOrAlias, member.getSourceInfo());
}
private void generateExport(String exportNamespace, String qualifiedExportName,
JsExpression bridgeOrAlias, SourceInfo sourceInfo) {
assert !JsInteropUtil.isWindow(exportNamespace);
// goog.provide("a.b.c")
ensureGoogProvide(exportNamespace, sourceInfo);
// a.b.c = a_b_c_obf
generateAssignment(bridgeOrAlias, qualifiedExportName, sourceInfo);
}
private void ensureGoogProvide(String namespace, SourceInfo info) {
if (JsInteropUtil.isGlobal(namespace) || !providedNamespaces.add(namespace)) {
return;
}
JsNameRef provideFuncRef = JsUtils.createQualifiedNameRef("goog.provide", info);
JsInvocation provideCall = new JsInvocation(info);
provideCall.setQualifier(provideFuncRef);
provideCall.getArguments().add(new JsStringLiteral(info, namespace));
exportStmts.add(provideCall.makeStmt());
}
private void generateAssignment(JsExpression rhs, String exportName, SourceInfo sourceInfo) {
JsExpression lhs = createExportQualifier(exportName, sourceInfo);
exportStmts.add(JsUtils.createAssignment(lhs, rhs).makeStmt());
}
private static JsExpression createExportQualifier(String namespace, SourceInfo sourceInfo) {
String components[] = namespace.split("\\.");
if (JsInteropUtil.isGlobal(components[0])) {
assert components.length != 0;
components[0] = null;
}
return JsUtils.createQualifiedNameRef(Joiner.on(".").skipNulls().join(components), sourceInfo);
}
}