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.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<Class<? extends AstNode>, Constructor<? extends EcmascriptNode<?>>>();
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<ParseProblem, TrailingCommaNode>();
137
138
139 private Stack<Node> nodes = new Stack<Node>();
140
141
142 private Stack<AstNode> parents = new Stack<AstNode>();
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")
154
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
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
185 EcmascriptNode<T> node = createNodeAdapter(astNode);
186
187
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
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
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
227
228 EcmascriptNode<? extends AstNode> currentNode = (EcmascriptNode<? extends AstNode>) 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);
244 }
245 };
246 node.jjtAccept(visitor, null);
247 }
248 }