blob: 28a60cff56e6203eb40bf57513944e7e110341af [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.jx;
import org.apache.royale.compiler.codegen.ISubEmitter;
import org.apache.royale.compiler.codegen.js.IJSEmitter;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IPackageDefinition;
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.JSSubEmitter;
import org.apache.royale.compiler.internal.codegen.js.royale.JSRoyaleDocEmitter;
import org.apache.royale.compiler.internal.codegen.js.royale.JSRoyaleEmitter;
import org.apache.royale.compiler.internal.codegen.js.royale.JSRoyaleEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.goog.JSGoogEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.jx.BinaryOperatorEmitter.DatePropertiesGetters;
import org.apache.royale.compiler.internal.definitions.AccessorDefinition;
import org.apache.royale.compiler.internal.definitions.FunctionDefinition;
import org.apache.royale.compiler.internal.projects.RoyaleJSProject;
import org.apache.royale.compiler.internal.tree.as.*;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.*;
import org.apache.royale.compiler.tree.as.IOperatorNode.OperatorType;
import org.apache.royale.compiler.utils.ASNodeUtils;
public class MemberAccessEmitter extends JSSubEmitter implements
ISubEmitter<IMemberAccessExpressionNode>
{
public MemberAccessEmitter(IJSEmitter emitter)
{
super(emitter);
}
@Override
public void emit(IMemberAccessExpressionNode node)
{
if (ASNodeUtils.hasParenOpen(node))
write(ASEmitterTokens.PAREN_OPEN);
IASNode leftNode = node.getLeftOperandNode();
IASNode rightNode = node.getRightOperandNode();
JSRoyaleEmitter fjs = (JSRoyaleEmitter)getEmitter();
if (fjs.isDateProperty(node, false))
{
writeLeftSide(node, leftNode, rightNode);
String rightName = ((IIdentifierNode)rightNode).getName();
DatePropertiesGetters propGetter = DatePropertiesGetters.valueOf(rightName.toUpperCase());
write(ASEmitterTokens.MEMBER_ACCESS);
write(propGetter.getFunctionName());
write(ASEmitterTokens.PAREN_OPEN);
write(ASEmitterTokens.PAREN_CLOSE);
return;
}
IDefinition def = node.resolve(getProject());
if (def == null)
{
IASNode parentNode = node.getParent();
// could be XML
boolean isXML = false;
boolean isProxy = false;
if (leftNode instanceof MemberAccessExpressionNode)
isXML = fjs.isLeftNodeXMLish((MemberAccessExpressionNode)leftNode);
else if (leftNode instanceof IExpressionNode)
isXML = fjs.isXML((IExpressionNode)leftNode);
if (leftNode instanceof MemberAccessExpressionNode)
isProxy = fjs.isProxy((MemberAccessExpressionNode)leftNode);
else if (leftNode instanceof IExpressionNode)
isProxy = fjs.isProxy((IExpressionNode)leftNode);
if (isXML)
{
boolean descendant = (node.getOperator() == OperatorType.DESCENDANT_ACCESS);
boolean child = (node.getOperator() == OperatorType.MEMBER_ACCESS) &&
(!(parentNode instanceof FunctionCallNode)) &&
rightNode.getNodeID() != ASTNodeID.Op_AtID &&
!((rightNode.getNodeID() == ASTNodeID.ArrayIndexExpressionID) &&
(((DynamicAccessNode)rightNode).getLeftOperandNode().getNodeID() == ASTNodeID.Op_AtID));
if (descendant || child) {
writeLeftSide(node, leftNode, rightNode);
if (descendant)
write(".descendants(");
if (child)
write(".child(");
String closeMethodCall = "')";
String s = "";
if (rightNode instanceof INamespaceAccessExpressionNode) {
//use a QName to support the namespace access
write("new QName(");
NamespaceIdentifierNode namespaceIdentifierNode = (NamespaceIdentifierNode) ((INamespaceAccessExpressionNode) rightNode).getLeftOperandNode();
s = fjs.stringifyNode(namespaceIdentifierNode);
write(s + ", '");
rightNode = ((INamespaceAccessExpressionNode) rightNode).getRightOperandNode();
closeMethodCall = "'))";
} else write("'"); //normal string name for child
s = fjs.stringifyNode(rightNode);
int dot = s.indexOf('.');
if (dot != -1) {
String name = s.substring(0, dot);
String afterDot = s.substring(dot);
write(name);
write(closeMethodCall);
write(afterDot);
} else {
write(s);
write(closeMethodCall);
}
return;
}
}
else if (isProxy)
{
boolean child = (node.getOperator() == OperatorType.MEMBER_ACCESS) &&
(!(parentNode instanceof FunctionCallNode)) &&
rightNode.getNodeID() != ASTNodeID.Op_AtID;
if (child)
{
writeLeftSide(node, leftNode, rightNode);
if (child)
write(".getProperty('");
String s = fjs.stringifyNode(rightNode);
int dot = s.indexOf('.');
if (dot != -1)
{
String name = s.substring(0, dot);
String afterDot = s.substring(dot);
write(name);
write("')");
write(afterDot);
}
else
{
write(s);
write("')");
}
return;
}
}
else if (rightNode instanceof NamespaceAccessExpressionNode)
{
// if you define a local variable with the same URI as a
// namespace that defines a namespaced property
// it doesn't resolve above so we handle it here
NamespaceAccessExpressionNode naen = (NamespaceAccessExpressionNode)rightNode;
IDefinition d = naen.getLeftOperandNode().resolve(getProject());
IdentifierNode r = (IdentifierNode)(naen.getRightOperandNode());
// output bracket access with QName
writeLeftSide(node, leftNode, rightNode);
write(ASEmitterTokens.SQUARE_OPEN);
write(ASEmitterTokens.NEW);
write(ASEmitterTokens.SPACE);
write(IASLanguageConstants.QName);
write(ASEmitterTokens.PAREN_OPEN);
write(fjs.formatQualifiedName(d.getQualifiedName()));
write(ASEmitterTokens.COMMA);
write(ASEmitterTokens.SPACE);
write(ASEmitterTokens.SINGLE_QUOTE);
write(r.getName());
write(ASEmitterTokens.SINGLE_QUOTE);
write(ASEmitterTokens.PAREN_CLOSE);
write(".objectAccessFormat()");
write(ASEmitterTokens.SQUARE_CLOSE);
return;
}
}
else if(def.getParent() instanceof IPackageDefinition)
{
//this is a fully qualified name, and we should output it directly
//because we don't want it to be treated as dynamic access
write(fjs.formatQualifiedName(def.getQualifiedName()));
return;
}
else if (def.getParent() != null &&
def.getParent().getQualifiedName().equals("Array"))
{
if (def.getBaseName().equals("removeAt"))
{
writeLeftSide(node, leftNode, rightNode);
write(".splice");
return;
}
else if (def.getBaseName().equals("insertAt"))
{
writeLeftSide(node, leftNode, rightNode);
write(".splice");
return;
}
}
else if (rightNode instanceof NamespaceAccessExpressionNode)
{
boolean isStatic = false;
if (def != null && def.isStatic())
isStatic = true;
boolean needClosure = false;
if (def instanceof FunctionDefinition && (!(def instanceof AccessorDefinition))
&& !def.getBaseName().equals("constructor")) // don't wrap references to obj.constructor
{
IASNode parentNode = node.getParent();
if (parentNode != null)
{
ASTNodeID parentNodeId = parentNode.getNodeID();
// we need a closure if this MAE is the top-level in a chain
// of MAE and not in a function call.
needClosure = !isStatic && parentNodeId != ASTNodeID.FunctionCallID &&
parentNodeId != ASTNodeID.MemberAccessExpressionID &&
parentNodeId != ASTNodeID.ArrayIndexExpressionID;
}
}
if (needClosure
&& getEmitter().getDocEmitter() instanceof JSRoyaleDocEmitter
&& ((JSRoyaleDocEmitter)getEmitter().getDocEmitter()).getSuppressClosure())
needClosure = false;
if (needClosure)
getEmitter().emitClosureStart();
NamespaceAccessExpressionNode naen = (NamespaceAccessExpressionNode)rightNode;
IDefinition d = naen.getLeftOperandNode().resolve(getProject());
IdentifierNode r = (IdentifierNode)(naen.getRightOperandNode());
// output bracket access with QName
writeLeftSide(node, leftNode, rightNode);
if (!d.getBaseName().equals(ASEmitterTokens.PRIVATE.getToken()))
{
write(ASEmitterTokens.SQUARE_OPEN);
write(ASEmitterTokens.NEW);
write(ASEmitterTokens.SPACE);
write(IASLanguageConstants.QName);
write(ASEmitterTokens.PAREN_OPEN);
write(fjs.formatQualifiedName(d.getQualifiedName()));
write(ASEmitterTokens.COMMA);
write(ASEmitterTokens.SPACE);
write(ASEmitterTokens.SINGLE_QUOTE);
write(r.getName());
write(ASEmitterTokens.SINGLE_QUOTE);
write(ASEmitterTokens.PAREN_CLOSE);
write(".objectAccessFormat()");
write(ASEmitterTokens.SQUARE_CLOSE);
}
else
{
write(node.getOperator().getOperatorText());
write(r.getName());
}
if (needClosure)
{
write(ASEmitterTokens.COMMA);
write(ASEmitterTokens.SPACE);
if (leftNode.getNodeID() == ASTNodeID.SuperID)
write(ASEmitterTokens.THIS);
else
writeLeftSide(node, leftNode, rightNode);
getEmitter().emitClosureEnd(leftNode, def);
}
return;
}
boolean isCustomNamespace = false;
if (def instanceof FunctionDefinition && node.getOperator() == OperatorType.MEMBER_ACCESS)
isCustomNamespace = fjs.isCustomNamespace((FunctionDefinition)def);
boolean isStatic = false;
if (def != null && def.isStatic())
isStatic = true;
boolean needClosure = false;
if (def instanceof FunctionDefinition && (!(def instanceof AccessorDefinition))
&& !def.getBaseName().equals("constructor")) // don't wrap references to obj.constructor
{
IASNode parentNode = node.getParent();
if (parentNode != null)
{
ASTNodeID parentNodeId = parentNode.getNodeID();
// we need a closure if this MAE is the top-level in a chain
// of MAE and not in a function call.
needClosure = !isStatic && parentNodeId != ASTNodeID.FunctionCallID &&
parentNodeId != ASTNodeID.MemberAccessExpressionID &&
parentNodeId != ASTNodeID.ArrayIndexExpressionID;
if (needClosure
&& getEmitter().getDocEmitter() instanceof JSRoyaleDocEmitter
&& ((JSRoyaleDocEmitter)getEmitter().getDocEmitter()).getSuppressClosure())
needClosure = false;
}
}
boolean continueWalk = true;
if (!isStatic)
{
if (needClosure)
getEmitter().emitClosureStart();
continueWalk = writeLeftSide(node, leftNode, rightNode);
}
if (continueWalk)
{
boolean emitDynamicAccess = false;
boolean dynamicAccessUnknownMembers = false;
ICompilerProject project = getProject();
if(project instanceof RoyaleJSProject)
{
RoyaleJSProject fjsProject = (RoyaleJSProject) project;
if(fjsProject.config != null)
{
dynamicAccessUnknownMembers = fjsProject.config.getJsDynamicAccessUnknownMembers();
}
}
if (dynamicAccessUnknownMembers && rightNode instanceof IIdentifierNode)
{
IIdentifierNode identifierNode = (IIdentifierNode) node.getRightOperandNode();
IDefinition resolvedDefinition = identifierNode.resolve(getProject());
emitDynamicAccess = resolvedDefinition == null;
}
if (emitDynamicAccess)
{
IIdentifierNode identifierNode = (IIdentifierNode) node.getRightOperandNode();
startMapping(node, rightNode);
write(ASEmitterTokens.SQUARE_OPEN);
write(ASEmitterTokens.DOUBLE_QUOTE);
write(identifierNode.getName());
write(ASEmitterTokens.DOUBLE_QUOTE);
write(ASEmitterTokens.SQUARE_CLOSE);
endMapping(node);
}
else
{
if (!isStatic && !isCustomNamespace)
{
startMapping(node, node.getLeftOperandNode());
write(node.getOperator().getOperatorText());
endMapping(node);
}
getWalker().walk(node.getRightOperandNode());
}
}
if (needClosure)
{
write(ASEmitterTokens.COMMA);
write(ASEmitterTokens.SPACE);
if (leftNode.getNodeID() == ASTNodeID.SuperID)
write(ASEmitterTokens.THIS);
else
writeLeftSide(node, leftNode, rightNode);
getEmitter().emitClosureEnd(node, def);
}
if (ASNodeUtils.hasParenClose(node))
write(ASEmitterTokens.PAREN_CLOSE);
}
private boolean writeLeftSide(IMemberAccessExpressionNode node, IASNode leftNode, IASNode rightNode)
{
if (!(leftNode instanceof ILanguageIdentifierNode && ((ILanguageIdentifierNode) leftNode)
.getKind() == ILanguageIdentifierNode.LanguageIdentifierKind.THIS))
{
IDefinition rightDef = null;
if (rightNode instanceof IIdentifierNode)
rightDef = ((IIdentifierNode) rightNode)
.resolve(getProject());
if (leftNode.getNodeID() != ASTNodeID.SuperID)
{
getWalker().walk(node.getLeftOperandNode());
}
else if (leftNode.getNodeID() == ASTNodeID.SuperID
&& (rightNode.getNodeID() == ASTNodeID.GetterID || (rightDef != null && rightDef instanceof AccessorDefinition)))
{
write(getEmitter().formatQualifiedName(
getEmitter().getModel().getCurrentClass().getQualifiedName()));
write(ASEmitterTokens.MEMBER_ACCESS);
write(JSGoogEmitterTokens.SUPERCLASS);
write(ASEmitterTokens.MEMBER_ACCESS);
write(JSRoyaleEmitterTokens.GETTER_PREFIX);
if (rightDef != null)
write(rightDef.getBaseName());
else
write(((GetterNode) rightNode).getName());
write(ASEmitterTokens.MEMBER_ACCESS);
write(JSEmitterTokens.APPLY);
write(ASEmitterTokens.PAREN_OPEN);
write(ASEmitterTokens.THIS);
write(ASEmitterTokens.PAREN_CLOSE);
return false;
}
else if (leftNode.getNodeID() == ASTNodeID.SuperID
&& (rightDef != null && rightDef instanceof FunctionDefinition))
{
write(getEmitter().formatQualifiedName(
getEmitter().getModel().getCurrentClass().getQualifiedName()));
write(ASEmitterTokens.MEMBER_ACCESS);
write(JSGoogEmitterTokens.SUPERCLASS);
write(ASEmitterTokens.MEMBER_ACCESS);
write(rightDef.getBaseName());
return false;
}
}
else
{
startMapping(leftNode);
write(ASEmitterTokens.THIS);
endMapping(leftNode);
}
return true;
}
}