1
2
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
137 protected Stack<Node> nodes = new Stack<Node>();
138
139
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")
152
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
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
183 EcmascriptNode<T> node = createNodeAdapter(astNode);
184
185
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
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
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
225
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);
242 }
243 };
244 node.jjtAccept(visitor, null);
245 }
246 }