1 /*
2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package jdk.nashorn.internal.ir.debug;
27 
28 import static jdk.nashorn.internal.runtime.Source.sourceFor;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 import jdk.nashorn.internal.ir.AccessNode;
33 import jdk.nashorn.internal.ir.BinaryNode;
34 import jdk.nashorn.internal.ir.Block;
35 import jdk.nashorn.internal.ir.BlockStatement;
36 import jdk.nashorn.internal.ir.BreakNode;
37 import jdk.nashorn.internal.ir.CallNode;
38 import jdk.nashorn.internal.ir.CaseNode;
39 import jdk.nashorn.internal.ir.CatchNode;
40 import jdk.nashorn.internal.ir.ContinueNode;
41 import jdk.nashorn.internal.ir.DebuggerNode;
42 import jdk.nashorn.internal.ir.EmptyNode;
43 import jdk.nashorn.internal.ir.Expression;
44 import jdk.nashorn.internal.ir.ExpressionStatement;
45 import jdk.nashorn.internal.ir.ForNode;
46 import jdk.nashorn.internal.ir.FunctionNode;
47 import jdk.nashorn.internal.ir.IdentNode;
48 import jdk.nashorn.internal.ir.IfNode;
49 import jdk.nashorn.internal.ir.IndexNode;
50 import jdk.nashorn.internal.ir.JoinPredecessorExpression;
51 import jdk.nashorn.internal.ir.LabelNode;
52 import jdk.nashorn.internal.ir.LiteralNode;
53 import jdk.nashorn.internal.ir.Node;
54 import jdk.nashorn.internal.ir.ObjectNode;
55 import jdk.nashorn.internal.ir.PropertyNode;
56 import jdk.nashorn.internal.ir.ReturnNode;
57 import jdk.nashorn.internal.ir.RuntimeNode;
58 import jdk.nashorn.internal.ir.SplitNode;
59 import jdk.nashorn.internal.ir.Statement;
60 import jdk.nashorn.internal.ir.SwitchNode;
61 import jdk.nashorn.internal.ir.TernaryNode;
62 import jdk.nashorn.internal.ir.ThrowNode;
63 import jdk.nashorn.internal.ir.TryNode;
64 import jdk.nashorn.internal.ir.UnaryNode;
65 import jdk.nashorn.internal.ir.VarNode;
66 import jdk.nashorn.internal.ir.WhileNode;
67 import jdk.nashorn.internal.ir.WithNode;
68 import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor;
69 import jdk.nashorn.internal.parser.JSONParser;
70 import jdk.nashorn.internal.parser.Lexer.RegexToken;
71 import jdk.nashorn.internal.parser.Parser;
72 import jdk.nashorn.internal.parser.TokenType;
73 import jdk.nashorn.internal.runtime.Context;
74 import jdk.nashorn.internal.runtime.ParserException;
75 import jdk.nashorn.internal.runtime.Source;
76 
77 /**
78  * This IR writer produces a JSON string that represents AST as a JSON string.
79  */
80 public final class JSONWriter extends SimpleNodeVisitor {
81 
82     /**
83      * Returns AST as JSON compatible string.
84      *
85      * @param context context
86      * @param code code to be parsed
87      * @param name name of the code source (used for location)
88      * @param includeLoc tells whether to include location information for nodes or not
89      * @return JSON string representation of AST of the supplied code
90      */
parse(final Context context, final String code, final String name, final boolean includeLoc)91     public static String parse(final Context context, final String code, final String name, final boolean includeLoc) {
92         final Parser       parser     = new Parser(context.getEnv(), sourceFor(name, code), new Context.ThrowErrorManager(), context.getEnv()._strict, context.getLogger(Parser.class));
93         final JSONWriter   jsonWriter = new JSONWriter(includeLoc);
94         try {
95             final FunctionNode functionNode = parser.parse(); //symbol name is ":program", default
96             functionNode.accept(jsonWriter);
97             return jsonWriter.getString();
98         } catch (final ParserException e) {
99             e.throwAsEcmaException();
100             return null;
101         }
102     }
103 
104     @Override
enterJoinPredecessorExpression(final JoinPredecessorExpression joinPredecessorExpression)105     public boolean enterJoinPredecessorExpression(final JoinPredecessorExpression joinPredecessorExpression) {
106         final Expression expr = joinPredecessorExpression.getExpression();
107         if(expr != null) {
108             expr.accept(this);
109         } else {
110             nullValue();
111         }
112         return false;
113     }
114 
115     @Override
enterDefault(final Node node)116     protected boolean enterDefault(final Node node) {
117         objectStart();
118         location(node);
119 
120         return true;
121     }
122 
leave()123     private boolean leave() {
124         objectEnd();
125         return false;
126     }
127 
128     @Override
leaveDefault(final Node node)129     protected Node leaveDefault(final Node node) {
130         objectEnd();
131         return null;
132     }
133 
134     @Override
enterAccessNode(final AccessNode accessNode)135     public boolean enterAccessNode(final AccessNode accessNode) {
136         enterDefault(accessNode);
137 
138         type("MemberExpression");
139         comma();
140 
141         property("object");
142         accessNode.getBase().accept(this);
143         comma();
144 
145         property("property", accessNode.getProperty());
146         comma();
147 
148         property("computed", false);
149 
150         return leave();
151     }
152 
153     @Override
enterBlock(final Block block)154     public boolean enterBlock(final Block block) {
155         enterDefault(block);
156 
157         type("BlockStatement");
158         comma();
159 
160         array("body", block.getStatements());
161 
162         return leave();
163     }
164 
165     @Override
enterBinaryNode(final BinaryNode binaryNode)166     public boolean enterBinaryNode(final BinaryNode binaryNode) {
167         enterDefault(binaryNode);
168 
169         final String name;
170         if (binaryNode.isAssignment()) {
171             name = "AssignmentExpression";
172         } else if (binaryNode.isLogical()) {
173             name = "LogicalExpression";
174         } else {
175             name = "BinaryExpression";
176         }
177 
178         type(name);
179         comma();
180 
181         property("operator", binaryNode.tokenType().getName());
182         comma();
183 
184         property("left");
185         binaryNode.lhs().accept(this);
186         comma();
187 
188         property("right");
189         binaryNode.rhs().accept(this);
190 
191         return leave();
192     }
193 
194     @Override
enterBreakNode(final BreakNode breakNode)195     public boolean enterBreakNode(final BreakNode breakNode) {
196         enterDefault(breakNode);
197 
198         type("BreakStatement");
199         comma();
200 
201         final String label = breakNode.getLabelName();
202         if(label != null) {
203             property("label", label);
204         } else {
205             property("label");
206             nullValue();
207         }
208 
209         return leave();
210     }
211 
212     @Override
enterCallNode(final CallNode callNode)213     public boolean enterCallNode(final CallNode callNode) {
214         enterDefault(callNode);
215 
216         type("CallExpression");
217         comma();
218 
219         property("callee");
220         callNode.getFunction().accept(this);
221         comma();
222 
223         array("arguments", callNode.getArgs());
224 
225         return leave();
226     }
227 
228     @Override
enterCaseNode(final CaseNode caseNode)229     public boolean enterCaseNode(final CaseNode caseNode) {
230         enterDefault(caseNode);
231 
232         type("SwitchCase");
233         comma();
234 
235         final Node test = caseNode.getTest();
236         property("test");
237         if (test != null) {
238             test.accept(this);
239         } else {
240             nullValue();
241         }
242         comma();
243 
244         array("consequent", caseNode.getBody().getStatements());
245 
246         return leave();
247     }
248 
249     @Override
enterCatchNode(final CatchNode catchNode)250     public boolean enterCatchNode(final CatchNode catchNode) {
251         enterDefault(catchNode);
252 
253         type("CatchClause");
254         comma();
255 
256         property("param");
257         catchNode.getException().accept(this);
258         comma();
259 
260         final Node guard = catchNode.getExceptionCondition();
261         if (guard != null) {
262             property("guard");
263             guard.accept(this);
264             comma();
265         }
266 
267         property("body");
268         catchNode.getBody().accept(this);
269 
270         return leave();
271     }
272 
273     @Override
enterContinueNode(final ContinueNode continueNode)274     public boolean enterContinueNode(final ContinueNode continueNode) {
275         enterDefault(continueNode);
276 
277         type("ContinueStatement");
278         comma();
279 
280         final String label = continueNode.getLabelName();
281         if(label != null) {
282             property("label", label);
283         } else {
284             property("label");
285             nullValue();
286         }
287 
288         return leave();
289     }
290 
291     @Override
enterDebuggerNode(final DebuggerNode debuggerNode)292     public boolean enterDebuggerNode(final DebuggerNode debuggerNode) {
293         enterDefault(debuggerNode);
294         type("DebuggerStatement");
295         return leave();
296     }
297 
298     @Override
enterEmptyNode(final EmptyNode emptyNode)299     public boolean enterEmptyNode(final EmptyNode emptyNode) {
300         enterDefault(emptyNode);
301 
302         type("EmptyStatement");
303 
304         return leave();
305     }
306 
307     @Override
enterExpressionStatement(final ExpressionStatement expressionStatement)308     public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) {
309         // handle debugger statement
310         final Node expression = expressionStatement.getExpression();
311         if (expression instanceof RuntimeNode) {
312             assert false : "should not reach here: RuntimeNode";
313             return false;
314         }
315 
316         enterDefault(expressionStatement);
317 
318         type("ExpressionStatement");
319         comma();
320 
321         property("expression");
322         expression.accept(this);
323 
324         return leave();
325     }
326 
327     @Override
enterBlockStatement(final BlockStatement blockStatement)328     public boolean enterBlockStatement(final BlockStatement blockStatement) {
329         if (blockStatement.isSynthetic()) {
330             final Block blk = blockStatement.getBlock();
331             blk.getStatements().get(0).accept(this);
332             return false;
333         }
334 
335         enterDefault(blockStatement);
336 
337         type("BlockStatement");
338         comma();
339 
340         array("body", blockStatement.getBlock().getStatements());
341         return leave();
342     }
343 
344     @Override
enterForNode(final ForNode forNode)345     public boolean enterForNode(final ForNode forNode) {
346         enterDefault(forNode);
347 
348         if (forNode.isForIn() || (forNode.isForEach() && forNode.getInit() != null)) {
349             type("ForInStatement");
350             comma();
351 
352             final Node init = forNode.getInit();
353             assert init != null;
354             property("left");
355             init.accept(this);
356             comma();
357 
358             final Node modify = forNode.getModify();
359             assert modify != null;
360             property("right");
361             modify.accept(this);
362             comma();
363 
364             property("body");
365             forNode.getBody().accept(this);
366             comma();
367 
368             property("each", forNode.isForEach());
369         } else {
370             type("ForStatement");
371             comma();
372 
373             final Node init = forNode.getInit();
374             property("init");
375             if (init != null) {
376                 init.accept(this);
377             } else {
378                 nullValue();
379             }
380             comma();
381 
382             final Node test = forNode.getTest();
383             property("test");
384             if (test != null) {
385                 test.accept(this);
386             } else {
387                 nullValue();
388             }
389             comma();
390 
391             final Node update = forNode.getModify();
392             property("update");
393             if (update != null) {
394                 update.accept(this);
395             } else {
396                 nullValue();
397             }
398             comma();
399 
400             property("body");
401             forNode.getBody().accept(this);
402         }
403 
404         return leave();
405     }
406 
407     @Override
enterFunctionNode(final FunctionNode functionNode)408     public boolean enterFunctionNode(final FunctionNode functionNode) {
409         final boolean program = functionNode.isProgram();
410         if (program) {
411             return emitProgram(functionNode);
412         }
413 
414         enterDefault(functionNode);
415         final String name;
416         if (functionNode.isDeclared()) {
417             name = "FunctionDeclaration";
418         } else {
419             name = "FunctionExpression";
420         }
421         type(name);
422         comma();
423 
424         property("id");
425         final FunctionNode.Kind kind = functionNode.getKind();
426         if (functionNode.isAnonymous() || kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) {
427             nullValue();
428         } else {
429             functionNode.getIdent().accept(this);
430         }
431         comma();
432 
433         array("params", functionNode.getParameters());
434         comma();
435 
436         arrayStart("defaults");
437         arrayEnd();
438         comma();
439 
440         property("rest");
441         nullValue();
442         comma();
443 
444         property("body");
445         functionNode.getBody().accept(this);
446         comma();
447 
448         property("generator", false);
449         comma();
450 
451         property("expression", false);
452 
453         return leave();
454     }
455 
emitProgram(final FunctionNode functionNode)456     private boolean emitProgram(final FunctionNode functionNode) {
457         enterDefault(functionNode);
458         type("Program");
459         comma();
460 
461         // body consists of nested functions and statements
462         final List<Statement> stats = functionNode.getBody().getStatements();
463         final int size = stats.size();
464         int idx = 0;
465         arrayStart("body");
466 
467         for (final Node stat : stats) {
468             stat.accept(this);
469             if (idx != (size - 1)) {
470                 comma();
471             }
472             idx++;
473         }
474         arrayEnd();
475 
476         return leave();
477     }
478 
479     @Override
enterIdentNode(final IdentNode identNode)480     public boolean enterIdentNode(final IdentNode identNode) {
481         enterDefault(identNode);
482 
483         final String name = identNode.getName();
484         if ("this".equals(name)) {
485             type("ThisExpression");
486         } else {
487             type("Identifier");
488             comma();
489             property("name", identNode.getName());
490         }
491 
492         return leave();
493     }
494 
495     @Override
enterIfNode(final IfNode ifNode)496     public boolean enterIfNode(final IfNode ifNode) {
497         enterDefault(ifNode);
498 
499         type("IfStatement");
500         comma();
501 
502         property("test");
503         ifNode.getTest().accept(this);
504         comma();
505 
506         property("consequent");
507         ifNode.getPass().accept(this);
508         final Node elsePart = ifNode.getFail();
509         comma();
510 
511         property("alternate");
512         if (elsePart != null) {
513             elsePart.accept(this);
514         } else {
515             nullValue();
516         }
517 
518         return leave();
519     }
520 
521     @Override
enterIndexNode(final IndexNode indexNode)522     public boolean enterIndexNode(final IndexNode indexNode) {
523         enterDefault(indexNode);
524 
525         type("MemberExpression");
526         comma();
527 
528         property("object");
529         indexNode.getBase().accept(this);
530         comma();
531 
532         property("property");
533         indexNode.getIndex().accept(this);
534         comma();
535 
536         property("computed", true);
537 
538         return leave();
539     }
540 
541     @Override
enterLabelNode(final LabelNode labelNode)542     public boolean enterLabelNode(final LabelNode labelNode) {
543         enterDefault(labelNode);
544 
545         type("LabeledStatement");
546         comma();
547 
548         property("label", labelNode.getLabelName());
549         comma();
550 
551         property("body");
552         labelNode.getBody().accept(this);
553 
554         return leave();
555     }
556 
557     @SuppressWarnings("rawtypes")
558     @Override
enterLiteralNode(final LiteralNode literalNode)559     public boolean enterLiteralNode(final LiteralNode literalNode) {
560         enterDefault(literalNode);
561 
562         if (literalNode instanceof LiteralNode.ArrayLiteralNode) {
563             type("ArrayExpression");
564             comma();
565 
566             array("elements", ((LiteralNode.ArrayLiteralNode)literalNode).getElementExpressions());
567         } else {
568             type("Literal");
569             comma();
570 
571             property("value");
572             final Object value = literalNode.getValue();
573             if (value instanceof RegexToken) {
574                 // encode RegExp literals as Strings of the form /.../<flags>
575                 final RegexToken regex = (RegexToken)value;
576                 final StringBuilder regexBuf = new StringBuilder();
577                 regexBuf.append('/');
578                 regexBuf.append(regex.getExpression());
579                 regexBuf.append('/');
580                 regexBuf.append(regex.getOptions());
581                 buf.append(quote(regexBuf.toString()));
582             } else {
583                 final String str = literalNode.getString();
584                 // encode every String literal with prefix '$' so that script
585                 // can differentiate b/w RegExps as Strings and Strings.
586                 buf.append(literalNode.isString()? quote("$" + str) : str);
587             }
588         }
589 
590         return leave();
591     }
592 
593     @Override
enterObjectNode(final ObjectNode objectNode)594     public boolean enterObjectNode(final ObjectNode objectNode) {
595         enterDefault(objectNode);
596 
597         type("ObjectExpression");
598         comma();
599 
600         array("properties", objectNode.getElements());
601 
602         return leave();
603     }
604 
605     @Override
enterPropertyNode(final PropertyNode propertyNode)606     public boolean enterPropertyNode(final PropertyNode propertyNode) {
607         final Node key = propertyNode.getKey();
608 
609         final Node value = propertyNode.getValue();
610         if (value != null) {
611             objectStart();
612             location(propertyNode);
613 
614             property("key");
615             key.accept(this);
616             comma();
617 
618             property("value");
619             value.accept(this);
620             comma();
621 
622             property("kind", "init");
623 
624             objectEnd();
625         } else {
626             // getter
627             final Node getter = propertyNode.getGetter();
628             if (getter != null) {
629                 objectStart();
630                 location(propertyNode);
631 
632                 property("key");
633                 key.accept(this);
634                 comma();
635 
636                 property("value");
637                 getter.accept(this);
638                 comma();
639 
640                 property("kind", "get");
641 
642                 objectEnd();
643             }
644 
645             // setter
646             final Node setter = propertyNode.getSetter();
647             if (setter != null) {
648                 if (getter != null) {
649                     comma();
650                 }
651                 objectStart();
652                 location(propertyNode);
653 
654                 property("key");
655                 key.accept(this);
656                 comma();
657 
658                 property("value");
659                 setter.accept(this);
660                 comma();
661 
662                 property("kind", "set");
663 
664                 objectEnd();
665             }
666         }
667 
668         return false;
669     }
670 
671     @Override
enterReturnNode(final ReturnNode returnNode)672     public boolean enterReturnNode(final ReturnNode returnNode) {
673         enterDefault(returnNode);
674 
675         type("ReturnStatement");
676         comma();
677 
678         final Node arg = returnNode.getExpression();
679         property("argument");
680         if (arg != null) {
681             arg.accept(this);
682         } else {
683             nullValue();
684         }
685 
686         return leave();
687     }
688 
689     @Override
enterRuntimeNode(final RuntimeNode runtimeNode)690     public boolean enterRuntimeNode(final RuntimeNode runtimeNode) {
691         assert false : "should not reach here: RuntimeNode";
692         return false;
693     }
694 
695     @Override
enterSplitNode(final SplitNode splitNode)696     public boolean enterSplitNode(final SplitNode splitNode) {
697         assert false : "should not reach here: SplitNode";
698         return false;
699     }
700 
701     @Override
enterSwitchNode(final SwitchNode switchNode)702     public boolean enterSwitchNode(final SwitchNode switchNode) {
703         enterDefault(switchNode);
704 
705         type("SwitchStatement");
706         comma();
707 
708         property("discriminant");
709         switchNode.getExpression().accept(this);
710         comma();
711 
712         array("cases", switchNode.getCases());
713 
714         return leave();
715     }
716 
717     @Override
enterTernaryNode(final TernaryNode ternaryNode)718     public boolean enterTernaryNode(final TernaryNode ternaryNode) {
719         enterDefault(ternaryNode);
720 
721         type("ConditionalExpression");
722         comma();
723 
724         property("test");
725         ternaryNode.getTest().accept(this);
726         comma();
727 
728         property("consequent");
729         ternaryNode.getTrueExpression().accept(this);
730         comma();
731 
732         property("alternate");
733         ternaryNode.getFalseExpression().accept(this);
734 
735         return leave();
736     }
737 
738     @Override
enterThrowNode(final ThrowNode throwNode)739     public boolean enterThrowNode(final ThrowNode throwNode) {
740         enterDefault(throwNode);
741 
742         type("ThrowStatement");
743         comma();
744 
745         property("argument");
746         throwNode.getExpression().accept(this);
747 
748         return leave();
749     }
750 
751     @Override
enterTryNode(final TryNode tryNode)752     public boolean enterTryNode(final TryNode tryNode) {
753         enterDefault(tryNode);
754 
755         type("TryStatement");
756         comma();
757 
758         property("block");
759         tryNode.getBody().accept(this);
760         comma();
761 
762 
763         final List<? extends Node> catches = tryNode.getCatches();
764         final List<CatchNode> guarded = new ArrayList<>();
765         CatchNode unguarded = null;
766         if (catches != null) {
767             for (final Node n : catches) {
768                 final CatchNode cn = (CatchNode)n;
769                 if (cn.getExceptionCondition() != null) {
770                     guarded.add(cn);
771                 } else {
772                     assert unguarded == null: "too many unguarded?";
773                     unguarded = cn;
774                 }
775             }
776         }
777 
778         array("guardedHandlers", guarded);
779         comma();
780 
781         property("handler");
782         if (unguarded != null) {
783             unguarded.accept(this);
784         } else {
785             nullValue();
786         }
787         comma();
788 
789         property("finalizer");
790         final Node finallyNode = tryNode.getFinallyBody();
791         if (finallyNode != null) {
792             finallyNode.accept(this);
793         } else {
794             nullValue();
795         }
796 
797         return leave();
798     }
799 
800     @Override
enterUnaryNode(final UnaryNode unaryNode)801     public boolean enterUnaryNode(final UnaryNode unaryNode) {
802         enterDefault(unaryNode);
803 
804         final TokenType tokenType = unaryNode.tokenType();
805         if (tokenType == TokenType.NEW) {
806             type("NewExpression");
807             comma();
808 
809             final CallNode callNode = (CallNode)unaryNode.getExpression();
810             property("callee");
811             callNode.getFunction().accept(this);
812             comma();
813 
814             array("arguments", callNode.getArgs());
815         } else {
816             final String operator;
817             final boolean prefix;
818             switch (tokenType) {
819             case INCPOSTFIX:
820                 prefix = false;
821                 operator = "++";
822                 break;
823             case DECPOSTFIX:
824                 prefix = false;
825                 operator = "--";
826                 break;
827             case INCPREFIX:
828                 operator = "++";
829                 prefix = true;
830                 break;
831             case DECPREFIX:
832                 operator = "--";
833                 prefix = true;
834                 break;
835             default:
836                 prefix = true;
837                 operator = tokenType.getName();
838                 break;
839             }
840 
841             type(unaryNode.isAssignment()? "UpdateExpression" : "UnaryExpression");
842             comma();
843 
844             property("operator", operator);
845             comma();
846 
847             property("prefix", prefix);
848             comma();
849 
850             property("argument");
851             unaryNode.getExpression().accept(this);
852         }
853 
854         return leave();
855     }
856 
857     @Override
enterVarNode(final VarNode varNode)858     public boolean enterVarNode(final VarNode varNode) {
859         final Node init = varNode.getInit();
860         if (init instanceof FunctionNode && ((FunctionNode)init).isDeclared()) {
861             // function declaration - don't emit VariableDeclaration instead
862             // just emit FunctionDeclaration using 'init' Node.
863             init.accept(this);
864             return false;
865         }
866 
867         enterDefault(varNode);
868 
869         type("VariableDeclaration");
870         comma();
871 
872         arrayStart("declarations");
873 
874         // VariableDeclarator
875         objectStart();
876         location(varNode.getName());
877 
878         type("VariableDeclarator");
879         comma();
880 
881         property("id");
882         varNode.getName().accept(this);
883         comma();
884 
885         property("init");
886         if (init != null) {
887             init.accept(this);
888         } else {
889             nullValue();
890         }
891 
892         // VariableDeclarator
893         objectEnd();
894 
895         // declarations
896         arrayEnd();
897 
898         return leave();
899     }
900 
901     @Override
enterWhileNode(final WhileNode whileNode)902     public boolean enterWhileNode(final WhileNode whileNode) {
903         enterDefault(whileNode);
904 
905         type(whileNode.isDoWhile() ? "DoWhileStatement" : "WhileStatement");
906         comma();
907 
908         if (whileNode.isDoWhile()) {
909             property("body");
910             whileNode.getBody().accept(this);
911             comma();
912 
913             property("test");
914             whileNode.getTest().accept(this);
915         } else {
916             property("test");
917             whileNode.getTest().accept(this);
918             comma();
919 
920             property("body");
921             whileNode.getBody().accept(this);
922         }
923 
924         return leave();
925     }
926 
927     @Override
enterWithNode(final WithNode withNode)928     public boolean enterWithNode(final WithNode withNode) {
929         enterDefault(withNode);
930 
931         type("WithStatement");
932         comma();
933 
934         property("object");
935         withNode.getExpression().accept(this);
936         comma();
937 
938         property("body");
939         withNode.getBody().accept(this);
940 
941         return leave();
942    }
943 
944     // Internals below
945 
JSONWriter(final boolean includeLocation)946     private JSONWriter(final boolean includeLocation) {
947         this.buf             = new StringBuilder();
948         this.includeLocation = includeLocation;
949     }
950 
951     private final StringBuilder buf;
952     private final boolean includeLocation;
953 
getString()954     private String getString() {
955         return buf.toString();
956     }
957 
property(final String key, final String value, final boolean escape)958     private void property(final String key, final String value, final boolean escape) {
959         buf.append('"');
960         buf.append(key);
961         buf.append("\":");
962         if (value != null) {
963             if (escape) {
964                 buf.append('"');
965             }
966             buf.append(value);
967             if (escape) {
968                 buf.append('"');
969             }
970         }
971     }
972 
property(final String key, final String value)973     private void property(final String key, final String value) {
974         property(key, value, true);
975     }
976 
property(final String key, final boolean value)977     private void property(final String key, final boolean value) {
978         property(key, Boolean.toString(value), false);
979     }
980 
property(final String key, final int value)981     private void property(final String key, final int value) {
982         property(key, Integer.toString(value), false);
983     }
984 
property(final String key)985     private void property(final String key) {
986         property(key, null);
987     }
988 
type(final String value)989     private void type(final String value) {
990         property("type", value);
991     }
992 
objectStart(final String name)993     private void objectStart(final String name) {
994         buf.append('"');
995         buf.append(name);
996         buf.append("\":{");
997     }
998 
objectStart()999     private void objectStart() {
1000         buf.append('{');
1001     }
1002 
objectEnd()1003     private void objectEnd() {
1004         buf.append('}');
1005     }
1006 
array(final String name, final List<? extends Node> nodes)1007     private void array(final String name, final List<? extends Node> nodes) {
1008         // The size, idx comparison is just to avoid trailing comma..
1009         final int size = nodes.size();
1010         int idx = 0;
1011         arrayStart(name);
1012         for (final Node node : nodes) {
1013             if (node != null) {
1014                 node.accept(this);
1015             } else {
1016                 nullValue();
1017             }
1018             if (idx != (size - 1)) {
1019                 comma();
1020             }
1021             idx++;
1022         }
1023         arrayEnd();
1024     }
1025 
arrayStart(final String name)1026     private void arrayStart(final String name) {
1027         buf.append('"');
1028         buf.append(name);
1029         buf.append('"');
1030         buf.append(':');
1031         buf.append('[');
1032     }
1033 
arrayEnd()1034     private void arrayEnd() {
1035         buf.append(']');
1036     }
1037 
comma()1038     private void comma() {
1039         buf.append(',');
1040     }
1041 
nullValue()1042     private void nullValue() {
1043         buf.append("null");
1044     }
1045 
location(final Node node)1046     private void location(final Node node) {
1047         if (includeLocation) {
1048             objectStart("loc");
1049 
1050             // source name
1051             final Source src = lc.getCurrentFunction().getSource();
1052             property("source", src.getName());
1053             comma();
1054 
1055             // start position
1056             objectStart("start");
1057             final int start = node.getStart();
1058             property("line", src.getLine(start));
1059             comma();
1060             property("column", src.getColumn(start));
1061             objectEnd();
1062             comma();
1063 
1064             // end position
1065             objectStart("end");
1066             final int end = node.getFinish();
1067             property("line", src.getLine(end));
1068             comma();
1069             property("column", src.getColumn(end));
1070             objectEnd();
1071 
1072             // end 'loc'
1073             objectEnd();
1074 
1075             comma();
1076         }
1077     }
1078 
quote(final String str)1079     private static String quote(final String str) {
1080         return JSONParser.quote(str);
1081     }
1082 }
1083