blob: 71ecd8991ee27cdb2cf3307177ef34aa2f34d5da [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.royale.compiler.internal.codegen.js.royale;
import java.util.ArrayList;
import java.util.List;
import org.apache.royale.compiler.asdoc.royale.ASDocComment;
import org.apache.royale.compiler.codegen.js.IJSEmitter;
import org.apache.royale.compiler.common.ASModifier;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.constants.IASKeywordConstants;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IFunctionDefinition;
import org.apache.royale.compiler.definitions.ITypeDefinition;
import org.apache.royale.compiler.definitions.references.IReference;
import org.apache.royale.compiler.internal.codegen.as.ASEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.JSEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.JSSessionModel;
import org.apache.royale.compiler.internal.codegen.js.goog.JSGoogDocEmitter;
import org.apache.royale.compiler.internal.codegen.js.goog.JSGoogDocEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.jx.BindableEmitter;
import org.apache.royale.compiler.internal.projects.RoyaleJSProject;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.problems.PublicVarWarningProblem;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IClassNode;
import org.apache.royale.compiler.tree.as.IDefinitionNode;
import org.apache.royale.compiler.tree.as.IExpressionNode;
import org.apache.royale.compiler.tree.as.IFunctionNode;
import org.apache.royale.compiler.tree.as.IParameterNode;
import org.apache.royale.compiler.tree.as.IVariableNode;
import org.apache.royale.compiler.tree.metadata.IMetaTagNode;
import org.apache.royale.compiler.tree.metadata.IMetaTagsNode;
public class JSRoyaleDocEmitter extends JSGoogDocEmitter
{
private List<String> classIgnoreList;
private List<String> ignoreList;
private List<String> coercionList;
public boolean emitStringConversions = true;
private boolean emitExports = true;
public JSRoyaleDocEmitter(IJSEmitter emitter)
{
super(emitter);
}
public List<String> getClassIgnoreList()
{
return classIgnoreList;
}
public void setClassIgnoreList(List<String> value)
{
this.classIgnoreList = value;
}
@Override
protected String convertASTypeToJS(String name, String pname)
{
if (ignoreList != null)
{
if (ignoreList.contains(pname + "." + name))
return IASLanguageConstants.Object;
}
if (coercionList != null)
{
if (!coercionList.contains(pname + "." + name))
return IASLanguageConstants.Object;
}
if (classIgnoreList != null)
{
if (classIgnoreList.contains(pname + "." + name))
return IASLanguageConstants.Object;
}
if (name.matches("Vector\\.<.*>"))
return IASLanguageConstants.Array;
name = super.convertASTypeToJS(name, pname);
return formatQualifiedName(name);
}
private boolean usedNames = false;
@Override
protected String formatQualifiedName(String name)
{
return ((JSRoyaleEmitter)emitter).formatQualifiedName(name, !usedNames);
}
@Override
public void emitMethodDoc(IFunctionNode node, ICompilerProject project)
{
RoyaleJSProject fjp = (RoyaleJSProject)project;
boolean keepASDoc = fjp.config != null && fjp.config.getKeepASDoc();
if (fjp.config != null)
emitExports = fjp.config.getExportPublicSymbols();
coercionList = null;
ignoreList = null;
emitStringConversions = true;
IClassDefinition classDefinition = resolveClassDefinition(node);
ASDocComment asDoc = (ASDocComment) node.getASDocComment();
if (node instanceof IFunctionNode)
{
boolean hasDoc = false;
Boolean override = false;
if (node.isConstructor())
{
if (asDoc != null && keepASDoc)
write(changeAnnotations(asDoc.commentNoEnd()));
else
begin();
hasDoc = true;
emitJSDocLine(JSEmitterTokens.CONSTRUCTOR);
IClassDefinition parent = (IClassDefinition) node
.getDefinition().getParent();
IClassDefinition superClass = parent.resolveBaseClass(project);
String qname = (superClass != null) ? project.getActualPackageName(superClass.getQualifiedName()) : null;
//support implicit bindable implementation for 'Extends' EventDispatcher:
if (superClass == null || qname.equals(IASLanguageConstants.Object)) {
if (((JSRoyaleEmitter)emitter).getModel().getImplicitBindableImplementation()
== JSSessionModel.ImplicitBindableImplementation.EXTENDS) {
superClass = (IClassDefinition) project.resolveQNameToDefinition(BindableEmitter.DISPATCHER_CLASS_QNAME);
if (superClass == null) {
System.out.println(BindableEmitter.DISPATCHER_CLASS_QNAME+" not resolved for implicit super class in "+classDefinition.getQualifiedName());
} else qname = BindableEmitter.DISPATCHER_CLASS_QNAME;
}
}
usedNames = true;
if (superClass != null
&& !qname.equals(IASLanguageConstants.Object))
emitExtends(superClass, superClass.getPackageName());
IReference[] references = classDefinition
.getImplementedInterfaceReferences();
Boolean sawIEventDispatcher = false;
Boolean needsIEventDispatcher = ((JSRoyaleEmitter)emitter).getModel().getImplicitBindableImplementation()
== JSSessionModel.ImplicitBindableImplementation.IMPLEMENTS;
for (IReference iReference : references)
{
ITypeDefinition type = (ITypeDefinition) iReference
.resolve(project, (ASScope) classDefinition
.getContainingScope(),
DependencyType.INHERITANCE, true);
if (type == null) {
System.out.println(iReference.getDisplayString()
+ " not resolved in "
+ classDefinition.getQualifiedName());
} else {
emitImplements(type, project.getActualPackageName(type.getPackageName()));
}
if (type.getQualifiedName() == BindableEmitter.DISPATCHER_INTERFACE_QNAME)
sawIEventDispatcher=true;
}
//support implicit bindable implementation for 'implements' IEventDispatcher:
if (needsIEventDispatcher && !sawIEventDispatcher) {
ITypeDefinition type = (ITypeDefinition) project.resolveQNameToDefinition(BindableEmitter.DISPATCHER_INTERFACE_QNAME);
if (type == null) {
System.out.println(BindableEmitter.DISPATCHER_INTERFACE_QNAME+" not resolved for implicit implementation in "+classDefinition.getQualifiedName());
} else {
emitImplements(type, project.getActualPackageName(type.getPackageName()));
}
}
usedNames = false;
}
else
{
// @override
override = node.hasModifier(ASModifier.OVERRIDE);
String ns = node.getNamespace();
if (ns != null)
{
if (asDoc != null && keepASDoc)
{
String docText = asDoc.commentNoEnd();
String keepToken = JSRoyaleEmitterTokens.EMIT_COERCION
.getToken();
if (docText.contains(keepToken))
loadKeepers(docText);
String ignoreToken = JSRoyaleEmitterTokens.IGNORE_COERCION
.getToken();
if (docText.contains(ignoreToken))
loadIgnores(docText);
String noStringToken = JSRoyaleEmitterTokens.IGNORE_STRING_COERCION
.getToken();
if (docText.contains(noStringToken))
emitStringConversions = false;
write(changeAnnotations(asDoc.commentNoEnd()));
}
else
begin();
emitMethodAccess(node);
hasDoc = true;
}
}
if (!override)
{
// @param
IParameterNode[] parameters = node.getParameterNodes();
for (IParameterNode pnode : parameters)
{
if (!hasDoc)
{
if (asDoc != null && keepASDoc)
write(changeAnnotations(asDoc.commentNoEnd()));
else
begin();
emitMethodAccess(node);
hasDoc = true;
}
IExpressionNode enode = pnode.getNameExpressionNode();
ITypeDefinition tdef = enode.resolveType(project);
if (tdef == null)
continue;
emitParam(pnode, project.getActualPackageName(tdef.getPackageName()));
}
}
if (!node.isConstructor())
{
if (!override)
{
// @return
String returnType = node.getReturnType();
if (returnType != ""
&& returnType != ASEmitterTokens.VOID.getToken())
{
if (!hasDoc)
{
if (asDoc != null && keepASDoc)
write(changeAnnotations(asDoc.commentNoEnd()));
else
begin();
emitMethodAccess(node);
hasDoc = true;
}
ITypeDefinition tdef = ((IFunctionDefinition) node
.getDefinition()).resolveReturnType(project);
String packageName = "";
packageName = tdef != null ? tdef.getPackageName() : "";
emitReturn(node, project.getActualPackageName(packageName));
}
}
if (override)
{
if (!hasDoc)
{
if (asDoc != null && keepASDoc)
write(changeAnnotations(asDoc.commentNoEnd()));
else
begin();
emitMethodAccess(node);
hasDoc = true;
}
emitOverride(node);
}
}
if (hasDoc)
end();
}
}
private void loadIgnores(String doc)
{
ignoreList = new ArrayList<String>();
String ignoreToken = JSRoyaleEmitterTokens.IGNORE_COERCION.getToken();
int index = doc.indexOf(ignoreToken);
while (index != -1)
{
String ignore = doc.substring(index + ignoreToken.length());
int endIndex = ignore.indexOf("\n");
ignore = ignore.substring(0, endIndex);
ignore = ignore.trim();
ignoreList.add(ignore);
index = doc.indexOf(ignoreToken, index + endIndex);
}
}
private void loadKeepers(String doc)
{
coercionList = new ArrayList<String>();
String keepToken = JSRoyaleEmitterTokens.EMIT_COERCION.getToken();
int index = doc.indexOf(keepToken);
while (index != -1)
{
String keeper = doc.substring(index + keepToken.length());
int endIndex = keeper.indexOf("\n");
keeper = keeper.substring(0, endIndex);
keeper = keeper.trim();
coercionList.add(keeper);
index = doc.indexOf(keepToken, index + endIndex);
}
}
private String changeAnnotations(String doc)
{
// rename these tags so they don't conflict with generated
// jsdoc tags
String pass1 = doc.replaceAll("@param", "@asparam");
String pass2 = pass1.replaceAll("@return", "@asreturn");
String pass3 = pass2.replaceAll("@private", "@asprivate");
return pass3;
}
public void emitInterfaceMemberDoc(IDefinitionNode node,
ICompilerProject project)
{
RoyaleJSProject fjp = (RoyaleJSProject)project;
boolean keepASDoc = fjp.config != null && fjp.config.getKeepASDoc();
boolean hasDoc = false;
ASDocComment asDoc = (ASDocComment) ((IFunctionNode) node)
.getASDocComment();
String returnType = ((IFunctionNode) node).getReturnType();
if (returnType != "" && returnType != ASEmitterTokens.VOID.getToken()) // has return
{
if (asDoc != null && keepASDoc)
write(changeAnnotations(asDoc.commentNoEnd()));
else
begin();
hasDoc = true;
ITypeDefinition tdef = ((IFunctionDefinition) node.getDefinition())
.resolveReturnType(project);
emitReturn((IFunctionNode) node, tdef.getPackageName());
}
IParameterNode[] parameters = ((IFunctionNode) node)
.getParameterNodes();
for (IParameterNode pnode : parameters)
{
if (!hasDoc)
{
if (asDoc != null && keepASDoc)
write(changeAnnotations(asDoc.commentNoEnd()));
else
begin();
hasDoc = true;
}
IExpressionNode enode = pnode.getNameExpressionNode();
emitParam(pnode, enode.resolveType(project).getPackageName());
}
if (hasDoc)
end();
}
@Override
public void emitMethodAccess(IFunctionNode node)
{
String ns = node.getNamespace();
if (ns == IASKeywordConstants.PRIVATE)
{
emitPrivate(node);
}
else if (ns == IASKeywordConstants.PROTECTED)
{
emitProtected(node);
}
else if (ns != null && ns == IASKeywordConstants.PUBLIC)
{
emitPublic(node);
}
}
@Override
public void emitFieldDoc(IVariableNode node, IDefinition def, ICompilerProject project)
{
begin();
String ns = node.getNamespace();
if (ns == IASKeywordConstants.PRIVATE)
{
emitPrivate(node);
}
else if (ns == IASKeywordConstants.PROTECTED)
{
emitProtected(node);
}
else
{
RoyaleJSProject fjp = (RoyaleJSProject)project;
boolean warnPublicVars = fjp.config != null && fjp.config.getWarnPublicVars();
IMetaTagsNode meta = node.getMetaTags();
boolean bindable = false;
if (meta != null)
{
IMetaTagNode tag = meta.getTagByName("Bindable");
if (tag != null)
bindable = true;
}
if (warnPublicVars && !node.isConst() && !bindable)
{
if (!suppressedWarning(node, fjp))
fjp.getProblems().add(new PublicVarWarningProblem(node));
}
emitPublic(node);
}
if (node.isConst())
emitConst(node);
String packageName = "";
if (def != null)
packageName = def.getPackageName();
emitType(node, project.getActualPackageName(packageName));
end();
}
@Override
public void emitPublic(IASNode node)
{
if (emitExports)
super.emitPublic(node);
}
private boolean suppressedWarning(IVariableNode node, RoyaleJSProject fjp)
{
boolean suppressed = false;
ASDocComment asDoc = (ASDocComment) node.getASDocComment();
boolean keepASDoc = fjp.config != null && fjp.config.getKeepASDoc();
String suppressToken = JSRoyaleEmitterTokens.SUPPRESS_PUBLIC_VAR_WARNING
.getToken();
if (asDoc != null && keepASDoc)
{
String docText = asDoc.commentNoEnd();
if (docText.contains(suppressToken))
return true;
}
IASNode classNode = node.getParent().getParent();
if (classNode == null)
return false;
if (classNode.getNodeID() == ASTNodeID.ClassID)
{
asDoc = (ASDocComment) ((IClassNode)classNode).getASDocComment();
if (asDoc != null && keepASDoc)
{
String docText = asDoc.commentNoEnd();
if (docText.contains(suppressToken))
return true;
}
IClassDefinition cdef = ((IClassNode)classNode).getDefinition();
if (cdef.isBindable())
return true;
}
return false;
}
}