View Javadoc

    1   /**
    2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
    3    */
    4   package net.sourceforge.pmd.lang.ecmascript.ast;
    5
    6   import java.lang.reflect.Constructor;
    7   import java.lang.reflect.InvocationTargetException;
    8   import java.util.HashMap;
    9   import java.util.List;
    10  import java.util.Map;
    11  import java.util.Stack;
    12
    13  import net.sourceforge.pmd.lang.ast.Node;
    14
    15  import org.mozilla.javascript.ast.ArrayComprehension;
    16  import org.mozilla.javascript.ast.ArrayComprehensionLoop;
    17  import org.mozilla.javascript.ast.ArrayLiteral;
    18  import org.mozilla.javascript.ast.Assignment;
    19  import org.mozilla.javascript.ast.AstNode;
    20  import org.mozilla.javascript.ast.AstRoot;
    21  import org.mozilla.javascript.ast.Block;
    22  import org.mozilla.javascript.ast.BreakStatement;
    23  import org.mozilla.javascript.ast.CatchClause;
    24  import org.mozilla.javascript.ast.Comment;
    25  import org.mozilla.javascript.ast.ConditionalExpression;
    26  import org.mozilla.javascript.ast.ContinueStatement;
    27  import org.mozilla.javascript.ast.DoLoop;
    28  import org.mozilla.javascript.ast.ElementGet;
    29  import org.mozilla.javascript.ast.EmptyExpression;
    30  import org.mozilla.javascript.ast.ExpressionStatement;
    31  import org.mozilla.javascript.ast.ForInLoop;
    32  import org.mozilla.javascript.ast.ForLoop;
    33  import org.mozilla.javascript.ast.FunctionCall;
    34  import org.mozilla.javascript.ast.FunctionNode;
    35  import org.mozilla.javascript.ast.IfStatement;
    36  import org.mozilla.javascript.ast.InfixExpression;
    37  import org.mozilla.javascript.ast.KeywordLiteral;
    38  import org.mozilla.javascript.ast.Label;
    39  import org.mozilla.javascript.ast.LabeledStatement;
    40  import org.mozilla.javascript.ast.LetNode;
    41  import org.mozilla.javascript.ast.Name;
    42  import org.mozilla.javascript.ast.NewExpression;
    43  import org.mozilla.javascript.ast.NodeVisitor;
    44  import org.mozilla.javascript.ast.NumberLiteral;
    45  import org.mozilla.javascript.ast.ObjectLiteral;
    46  import org.mozilla.javascript.ast.ObjectProperty;
    47  import org.mozilla.javascript.ast.ParenthesizedExpression;
    48  import org.mozilla.javascript.ast.ParseProblem;
    49  import org.mozilla.javascript.ast.PropertyGet;
    50  import org.mozilla.javascript.ast.RegExpLiteral;
    51  import org.mozilla.javascript.ast.ReturnStatement;
    52  import org.mozilla.javascript.ast.Scope;
    53  import org.mozilla.javascript.ast.StringLiteral;
    54  import org.mozilla.javascript.ast.SwitchCase;
    55  import org.mozilla.javascript.ast.SwitchStatement;
    56  import org.mozilla.javascript.ast.ThrowStatement;
    57  import org.mozilla.javascript.ast.TryStatement;
    58  import org.mozilla.javascript.ast.UnaryExpression;
    59  import org.mozilla.javascript.ast.VariableDeclaration;
    60  import org.mozilla.javascript.ast.VariableInitializer;
    61  import org.mozilla.javascript.ast.WhileLoop;
    62  import org.mozilla.javascript.ast.WithStatement;
    63  import org.mozilla.javascript.ast.XmlDotQuery;
    64  import org.mozilla.javascript.ast.XmlExpression;
    65  import org.mozilla.javascript.ast.XmlMemberGet;
    66  import org.mozilla.javascript.ast.XmlString;
    67
    68  public final class EcmascriptTreeBuilder implements NodeVisitor {
    69
    70      private static final Map<Class<? extends AstNode>, Constructor<? extends EcmascriptNode<?>>> NODE_TYPE_TO_NODE_ADAPTER_TYPE = new HashMap<Class<? extends AstNode>, Constructor<? extends EcmascriptNode<?>>>();
    71      static {
    72  	register(ArrayComprehension.class, ASTArrayComprehension.class);
    73  	register(ArrayComprehensionLoop.class, ASTArrayComprehensionLoop.class);
    74  	register(ArrayLiteral.class, ASTArrayLiteral.class);
    75  	register(Assignment.class, ASTAssignment.class);
    76  	register(AstRoot.class, ASTAstRoot.class);
    77  	register(Block.class, ASTBlock.class);
    78  	register(BreakStatement.class, ASTBreakStatement.class);
    79  	register(CatchClause.class, ASTCatchClause.class);
    80  	register(Comment.class, ASTComment.class);
    81  	register(ConditionalExpression.class, ASTConditionalExpression.class);
    82  	register(ContinueStatement.class, ASTContinueStatement.class);
    83  	register(DoLoop.class, ASTDoLoop.class);
    84  	register(ElementGet.class, ASTElementGet.class);
    85  	register(EmptyExpression.class, ASTEmptyExpression.class);
    86  	register(ExpressionStatement.class, ASTExpressionStatement.class);
    87  	register(ForInLoop.class, ASTForInLoop.class);
    88  	register(ForLoop.class, ASTForLoop.class);
    89  	register(FunctionCall.class, ASTFunctionCall.class);
    90  	register(FunctionNode.class, ASTFunctionNode.class);
    91  	register(IfStatement.class, ASTIfStatement.class);
    92  	register(InfixExpression.class, ASTInfixExpression.class);
    93  	register(KeywordLiteral.class, ASTKeywordLiteral.class);
    94  	register(Label.class, ASTLabel.class);
    95  	register(LabeledStatement.class, ASTLabeledStatement.class);
    96  	register(LetNode.class, ASTLetNode.class);
    97  	register(Name.class, ASTName.class);
    98  	register(NewExpression.class, ASTNewExpression.class);
    99  	register(NumberLiteral.class, ASTNumberLiteral.class);
    100 	register(ObjectLiteral.class, ASTObjectLiteral.class);
    101 	register(ObjectProperty.class, ASTObjectProperty.class);
    102 	register(ParenthesizedExpression.class, ASTParenthesizedExpression.class);
    103 	register(PropertyGet.class, ASTPropertyGet.class);
    104 	register(RegExpLiteral.class, ASTRegExpLiteral.class);
    105 	register(ReturnStatement.class, ASTReturnStatement.class);
    106 	register(Scope.class, ASTScope.class);
    107 	register(StringLiteral.class, ASTStringLiteral.class);
    108 	register(SwitchCase.class, ASTSwitchCase.class);
    109 	register(SwitchStatement.class, ASTSwitchStatement.class);
    110 	register(ThrowStatement.class, ASTThrowStatement.class);
    111 	register(TryStatement.class, ASTTryStatement.class);
    112 	register(UnaryExpression.class, ASTUnaryExpression.class);
    113 	register(VariableDeclaration.class, ASTVariableDeclaration.class);
    114 	register(VariableInitializer.class, ASTVariableInitializer.class);
    115 	register(WhileLoop.class, ASTWhileLoop.class);
    116 	register(WithStatement.class, ASTWithStatement.class);
    117 	register(XmlDotQuery.class, ASTXmlDotQuery.class);
    118 	register(XmlExpression.class, ASTXmlExpression.class);
    119 	register(XmlMemberGet.class, ASTXmlMemberGet.class);
    120 	register(XmlString.class, ASTXmlString.class);
    121     }
    122
    123     private static <T extends AstNode> void register(Class<T> nodeType, Class<? extends EcmascriptNode<T>> nodeAdapterType) {
    124 	try {
    125 	    NODE_TYPE_TO_NODE_ADAPTER_TYPE.put(nodeType, nodeAdapterType.getConstructor(nodeType));
    126 	} catch (SecurityException e) {
    127 	    throw new RuntimeException(e);
    128 	} catch (NoSuchMethodException e) {
    129 	    throw new RuntimeException(e);
    130 	}
    131     }
    132
    133     protected List<ParseProblem> parseProblems;
    134     protected Map<ParseProblem, TrailingCommaNode> parseProblemToNode = new HashMap<ParseProblem, TrailingCommaNode>();
    135
    136     // The nodes having children built.
    137     protected Stack<Node> nodes = new Stack<Node>();
    138
    139     // The Rhino nodes with children to build.
    140     protected Stack<AstNode> parents = new Stack<AstNode>();
    141
    142     private final SourceCodePositioner sourceCodePositioner;
    143
    144     public EcmascriptTreeBuilder(String sourceCode, List<ParseProblem> parseProblems) {
    145 	this.sourceCodePositioner = new SourceCodePositioner(sourceCode);
    146 	this.parseProblems = parseProblems;
    147     }
    148
    149     private <T extends AstNode> EcmascriptNode<T> createNodeAdapter(T node) {
    150 	try {
    151 	    @SuppressWarnings("unchecked") // the register function makes sure only EcmascriptNode<T> can be added,
    152 	    // where T is "T extends AstNode".
    153 	    Constructor<? extends EcmascriptNode<T>> constructor = (Constructor<? extends EcmascriptNode<T>>) NODE_TYPE_TO_NODE_ADAPTER_TYPE.get(node.getClass());
    154 	    if (constructor == null) {
    155 		throw new IllegalArgumentException("There is no Node adapter class registered for the Node class: "
    156 			+ node.getClass());
    157 	    }
    158 	    return constructor.newInstance(node);
    159 	} catch (InstantiationException e) {
    160 	    throw new RuntimeException(e);
    161 	} catch (IllegalAccessException e) {
    162 	    throw new RuntimeException(e);
    163 	} catch (InvocationTargetException e) {
    164 	    throw new RuntimeException(e.getTargetException());
    165 	}
    166     }
    167
    168     public <T extends AstNode> EcmascriptNode<T> build(T astNode) {
    169 	EcmascriptNode<T> node = buildInternal(astNode);
    170
    171 	calculateLineNumbers(node);
    172
    173 	// Set all the trailing comma nodes
    174 	for (TrailingCommaNode trailingCommaNode : parseProblemToNode.values()) {
    175 	    trailingCommaNode.setTrailingComma(true);
    176 	}
    177
    178 	return node;
    179     }
    180
    181     private <T extends AstNode> EcmascriptNode<T> buildInternal(T astNode) {
    182 	// Create a Node
    183 	EcmascriptNode<T> node = createNodeAdapter(astNode);
    184
    185 	// Append to parent
    186 	Node parent = nodes.isEmpty() ? null : nodes.peek();
    187 	if (parent != null) {
    188 	    parent.jjtAddChild(node, parent.jjtGetNumChildren());
    189 	    node.jjtSetParent(parent);
    190 	}
    191
    192 	handleParseProblems(node);
    193
    194 	// Build the children...
    195 	nodes.push(node);
    196 	parents.push(astNode);
    197 	astNode.visit(this);
    198 	nodes.pop();
    199 	parents.pop();
    200
    201 	return node;
    202     }
    203
    204     public boolean visit(AstNode node) {
    205 	if (parents.peek() == node) {
    206 	    return true;
    207 	} else {
    208 	    buildInternal(node);
    209 	    return false;
    210 	}
    211     }
    212
    213     private void handleParseProblems(EcmascriptNode<? extends AstNode> node) {
    214 	if (node instanceof TrailingCommaNode) {
    215 	    TrailingCommaNode trailingCommaNode = (TrailingCommaNode) node;
    216 	    int nodeStart = node.getNode().getAbsolutePosition();
    217 	    int nodeEnd = nodeStart + node.getNode().getLength() - 1;
    218 	    for (ParseProblem parseProblem : parseProblems) {
    219 		// The node overlaps the comma (i.e. end of the problem)?
    220 		int problemStart = parseProblem.getFileOffset();
    221 		int commaPosition = parseProblem.getFileOffset() + parseProblem.getLength() - 1;
    222 		if (nodeStart <= commaPosition && commaPosition <= nodeEnd) {
    223 		    if ("Trailing comma is not legal in an ECMA-262 object initializer".equals(parseProblem.getMessage())) {
    224 			// Report on the shortest code block containing the
    225 			// problem (i.e. inner most code in nested structures).
    226 			EcmascriptNode<? extends AstNode> currentNode = (EcmascriptNode<? extends AstNode>) parseProblemToNode.get(parseProblem);
    227 			if (currentNode == null || node.getNode().getLength() < currentNode.getNode().getLength()) {
    228 			    parseProblemToNode.put(parseProblem, trailingCommaNode);
    229 			}
    230 		    }
    231 		}
    232 	    }
    233 	}
    234     }
    235
    236     private void calculateLineNumbers(EcmascriptNode<?> node) {
    237 	EcmascriptParserVisitorAdapter visitor = new EcmascriptParserVisitorAdapter() {
    238 	    @Override
    239 	    public Object visit(EcmascriptNode node, Object data) {
    240 	        ((AbstractEcmascriptNode<?>)node).calculateLineNumbers(sourceCodePositioner);
    241 	        return super.visit(node, data); // also visit the children
    242 	    }
    243 	};
    244 	node.jjtAccept(visitor, null);
    245     }
    246 }