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