diff --git a/rhino/src/main/java/org/mozilla/javascript/IRFactory.java b/rhino/src/main/java/org/mozilla/javascript/IRFactory.java index 4081909c15..3902f95bf3 100644 --- a/rhino/src/main/java/org/mozilla/javascript/IRFactory.java +++ b/rhino/src/main/java/org/mozilla/javascript/IRFactory.java @@ -279,22 +279,24 @@ private Node transformArrayComp(ArrayComprehension node) { // createName(tmp1) // } - int lineno = node.getLineno(); - Scope scopeNode = parser.createScopeNode(Token.ARRAYCOMP, lineno); + int lineno = node.getLineno(), column = node.getColumn(); + Scope scopeNode = parser.createScopeNode(Token.ARRAYCOMP, lineno, column); String arrayName = parser.currentScriptOrFn.getNextTempName(); parser.pushScope(scopeNode); try { astNodePos.push(node); try { parser.defineSymbol(Token.LET, arrayName, false); - Node block = new Node(Token.BLOCK, lineno); + Node block = new Node(Token.BLOCK); + block.setLineColumnNumber(lineno, column); Node newArray = createCallOrNew(Token.NEW, parser.createName("Array")); Node init = new Node( Token.EXPR_VOID, createAssignment( Token.ASSIGN, parser.createName(arrayName), newArray), - lineno); + lineno, + column); block.addChildToBack(init); block.addChildToBack(arrayCompTransformHelper(node, arrayName)); scopeNode.addChildToBack(block); @@ -309,7 +311,7 @@ private Node transformArrayComp(ArrayComprehension node) { } private Node arrayCompTransformHelper(ArrayComprehension node, String arrayName) { - int lineno = node.getLineno(); + int lineno = node.getLineno(), column = node.getColumn(); Node expr = transform(node.getResult()); List loops = node.getLoops(); @@ -356,10 +358,11 @@ private Node arrayCompTransformHelper(ArrayComprehension node, String arrayName) createPropertyGet( parser.createName(arrayName), null, "push", 0, node.type)); - Node body = new Node(Token.EXPR_VOID, call, lineno); + Node body = new Node(Token.EXPR_VOID, call); + body.setLineColumnNumber(lineno, column); if (node.getFilter() != null) { - body = createIf(transform(node.getFilter()), body, null, lineno); + body = createIf(transform(node.getFilter()), body, null, lineno, column); } // Now walk loops in reverse to build up the body statement. @@ -370,7 +373,8 @@ private Node arrayCompTransformHelper(ArrayComprehension node, String arrayName) Scope loop = createLoopNode( null, // no label - acl.getLineno()); + acl.getLineno(), + acl.getColumn()); parser.pushScope(loop); pushed++; body = @@ -534,7 +538,7 @@ private Node transformElementGet(ElementGet node) { private Node transformExprStmt(ExpressionStatement node) { Node expr = transform(node.getExpression()); - return new Node(node.getType(), expr, node.getLineno()); + return new Node(node.getType(), expr, node.getLineno(), node.getColumn()); } private Node transformForInLoop(ForInLoop loop) { @@ -584,7 +588,7 @@ private Node transformFunction(FunctionNode fn) { Node destructuring = (Node) fn.getProp(Node.DESTRUCTURING_PARAMS); fn.removeProp(Node.DESTRUCTURING_PARAMS); - int lineno = fn.getBody().getLineno(); + int lineno = fn.getBody().getLineno(), column = fn.getBody().getColumn(); ++parser.nestingOfFunction; // only for body, not params Node body = transform(fn.getBody()); @@ -608,9 +612,11 @@ private Node transformFunction(FunctionNode fn) { Token.ASSIGN, parser.createName(name), transform(rhs)), - body.getLineno()), + body.getLineno(), + body.getColumn()), null, - body.getLineno())); + body.getLineno(), + body.getColumn())); } i -= 2; } @@ -629,7 +635,7 @@ private Node transformFunction(FunctionNode fn) { } if (destructuring != null) { - body.addChildToFront(new Node(Token.EXPR_VOID, destructuring, lineno)); + body.addChildToFront(new Node(Token.EXPR_VOID, destructuring, lineno, column)); } int syntheticType = fn.getFunctionType(); @@ -642,7 +648,7 @@ private Node transformFunction(FunctionNode fn) { astNodePos.pop(); } if (syntheticType != FunctionNode.FUNCTION_EXPRESSION) { - pn = createExprStatementNoReturn(pn, fn.getLineno()); + pn = createExprStatementNoReturn(pn, fn.getLineno(), fn.getColumn()); } } return pn; @@ -655,7 +661,7 @@ private Node transformFunction(FunctionNode fn) { private Node transformFunctionCall(FunctionCall node) { Node call = createCallOrNew(Token.CALL, transform(node.getTarget())); - call.setLineno(node.getLineno()); + call.setLineColumnNumber(node.getLineno(), node.getColumn()); List args = node.getArguments(); for (int i = 0; i < args.size(); i++) { AstNode arg = args.get(i); @@ -686,12 +692,12 @@ private Node transformGenExpr(GeneratorExpression node) { Node destructuring = (Node) fn.getProp(Node.DESTRUCTURING_PARAMS); fn.removeProp(Node.DESTRUCTURING_PARAMS); - int lineno = node.lineno; + int lineno = node.getLineno(), column = node.getColumn(); ++parser.nestingOfFunction; // only for body, not params Node body = genExprTransformHelper(node); if (destructuring != null) { - body.addChildToFront(new Node(Token.EXPR_VOID, destructuring, lineno)); + body.addChildToFront(new Node(Token.EXPR_VOID, destructuring, lineno, column)); } int syntheticType = fn.getFunctionType(); @@ -704,7 +710,7 @@ private Node transformGenExpr(GeneratorExpression node) { astNodePos.pop(); } if (syntheticType != FunctionNode.FUNCTION_EXPRESSION) { - pn = createExprStatementNoReturn(pn, fn.getLineno()); + pn = createExprStatementNoReturn(pn, fn.getLineno(), fn.getColumn()); } } } finally { @@ -713,12 +719,12 @@ private Node transformGenExpr(GeneratorExpression node) { } Node call = createCallOrNew(Token.CALL, pn); - call.setLineno(node.getLineno()); + call.setLineColumnNumber(node.getLineno(), node.getColumn()); return call; } private Node genExprTransformHelper(GeneratorExpression node) { - int lineno = node.getLineno(); + int lineno = node.getLineno(), column = node.getColumn(); Node expr = transform(node.getResult()); List loops = node.getLoops(); @@ -760,12 +766,12 @@ private Node genExprTransformHelper(GeneratorExpression node) { } // generate code for tmpArray.push(body) - Node yield = new Node(Token.YIELD, expr, node.getLineno()); + Node yield = new Node(Token.YIELD, expr, node.getLineno(), node.getColumn()); - Node body = new Node(Token.EXPR_VOID, yield, lineno); + Node body = new Node(Token.EXPR_VOID, yield, lineno, column); if (node.getFilter() != null) { - body = createIf(transform(node.getFilter()), body, null, lineno); + body = createIf(transform(node.getFilter()), body, null, lineno, column); } // Now walk loops in reverse to build up the body statement. @@ -776,7 +782,8 @@ private Node genExprTransformHelper(GeneratorExpression node) { Scope loop = createLoopNode( null, // no label - acl.getLineno()); + acl.getLineno(), + acl.getColumn()); parser.pushScope(loop); pushed++; body = @@ -806,13 +813,25 @@ private Node transformIf(IfStatement n) { if (n.getElsePart() != null) { ifFalse = transform(n.getElsePart()); } - return createIf(cond, ifTrue, ifFalse, n.getLineno()); + return createIf(cond, ifTrue, ifFalse, n.getLineno(), n.getColumn()); } private Node transformInfix(InfixExpression node) { Node left = transform(node.getLeft()); Node right = transform(node.getRight()); - return createBinary(node.getType(), left, right); + Node binaryNode = createBinary(node.getType(), left, right); + + // Since we are transforming InfixExpression -> Node, we need to copy over the column and + // line, but only for newly created nodes which have no column information set. + // createBinary() may return a Node reference with the correct line/column. + // Example: `true && ` would return the `` reference from + // createBinary + boolean nodeCreated = (binaryNode != left) && (binaryNode != right); + if (nodeCreated) { + binaryNode.setLineColumnNumber(node.getLineno(), node.getColumn()); + } + + return binaryNode; } private Node transformLabeledStatement(LabeledStatement ls) { @@ -853,7 +872,7 @@ private Node transformName(Name node) { private Node transformNewExpr(NewExpression node) { Node nx = createCallOrNew(Token.NEW, transform(node.getTarget())); - nx.setLineno(node.getLineno()); + nx.setLineColumnNumber(node.getLineno(), node.getColumn()); List args = node.getArguments(); for (int i = 0; i < args.size(); i++) { AstNode arg = args.get(i); @@ -878,6 +897,7 @@ private Node transformObjectLiteral(ObjectLiteral node) { // stages don't need to know about object literals. List elems = node.getElements(); Node object = new Node(Token.OBJECTLIT); + object.setLineColumnNumber(node.getLineno(), node.getColumn()); Object[] properties; if (elems.isEmpty()) { properties = ScriptRuntime.emptyArgs; @@ -950,7 +970,7 @@ private Node transformTemplateLiteral(TemplateLiteral node) { private Node transformTemplateLiteralCall(TaggedTemplateLiteral node) { Node call = createCallOrNew(Token.CALL, transform(node.getTarget())); - call.setLineno(node.getLineno()); + call.setLineColumnNumber(node.getLineno(), node.getColumn()); TemplateLiteral templateLiteral = (TemplateLiteral) node.getTemplateLiteral(); List elems = templateLiteral.getElements(); call.addChildToBack(templateLiteral); @@ -972,8 +992,8 @@ private Node transformReturn(ReturnStatement node) { AstNode rv = node.getReturnValue(); Node value = rv == null ? null : transform(rv); return rv == null - ? new Node(Token.RETURN, node.getLineno()) - : new Node(Token.RETURN, value, node.getLineno()); + ? new Node(Token.RETURN, node.getLineno(), node.getColumn()) + : new Node(Token.RETURN, value, node.getLineno(), node.getColumn()); } private Node transformScript(ScriptNode node) { @@ -992,7 +1012,9 @@ private Node transformScript(ScriptNode node) { } private Node transformString(StringLiteral node) { - return Node.newString(node.getValue()); + Node stringNode = Node.newString(node.getValue()); + stringNode.setLineColumnNumber(node.getLineno(), node.getColumn()); + return stringNode; } private Node transformSwitch(SwitchStatement node) { @@ -1038,7 +1060,7 @@ private Node transformSwitch(SwitchStatement node) { Node switchExpr = transform(node.getExpression()); node.addChildToBack(switchExpr); - Node block = new Node(Token.BLOCK, node, node.getLineno()); + Node block = new Node(Token.BLOCK, node, node.getLineno(), node.getColumn()); for (SwitchCase sc : node.getCases()) { AstNode expr = sc.getExpression(); @@ -1063,7 +1085,10 @@ private Node transformSwitch(SwitchStatement node) { private Node transformThrow(ThrowStatement node) { Node value = transform(node.getExpression()); - return new Node(Token.THROW, value, node.getLineno()); + value.setLineColumnNumber(node.getLineno(), node.getColumn()); + Node nx = new Node(Token.THROW, value); + nx.setLineColumnNumber(node.getLineno(), node.getColumn()); + return nx; } private Node transformTry(TryStatement node) { @@ -1088,13 +1113,15 @@ private Node transformTry(TryStatement node) { Node body = transform(cc.getBody()); - catchBlocks.addChildToBack(createCatch(varNameNode, catchCond, body, cc.getLineno())); + catchBlocks.addChildToBack( + createCatch(varNameNode, catchCond, body, cc.getLineno(), cc.getColumn())); } Node finallyBlock = null; if (node.getFinallyBlock() != null) { finallyBlock = transform(node.getFinallyBlock()); } - return createTryCatchFinally(tryBlock, catchBlocks, finallyBlock, node.getLineno()); + return createTryCatchFinally( + tryBlock, catchBlocks, finallyBlock, node.getLineno(), node.getColumn()); } private Node transformUnary(UnaryExpression node) { @@ -1175,20 +1202,20 @@ private Node transformWhileLoop(WhileLoop loop) { private Node transformWith(WithStatement node) { Node expr = transform(node.getExpression()); Node stmt = transform(node.getStatement()); - return createWith(expr, stmt, node.getLineno()); + return createWith(expr, stmt, node.getLineno(), node.getColumn()); } private Node transformYield(Yield node) { Node kid = node.getValue() == null ? null : transform(node.getValue()); - if (kid != null) return new Node(node.getType(), kid, node.getLineno()); - return new Node(node.getType(), node.getLineno()); + if (kid != null) return new Node(node.getType(), kid, node.getLineno(), node.getColumn()); + return new Node(node.getType(), node.getLineno(), node.getColumn()); } private Node transformXmlLiteral(XmlLiteral node) { // a literal like {bar} is rewritten as // new XML("" + bar + ""); - Node pnXML = new Node(Token.NEW, node.getLineno()); + Node pnXML = new Node(Token.NEW, node.getLineno(), node.getColumn()); List frags = node.getFragments(); XmlString first = (XmlString) frags.get(0); @@ -1298,8 +1325,8 @@ private static void closeSwitch(Node switchBlock) { switchBlock.addChildToBack(switchBreakTarget); } - private static Node createExprStatementNoReturn(Node expr, int lineno) { - return new Node(Token.EXPR_VOID, expr, lineno); + private static Node createExprStatementNoReturn(Node expr, int lineno, int column) { + return new Node(Token.EXPR_VOID, expr, lineno, column); } private static Node createString(String string) { @@ -1314,15 +1341,16 @@ private static Node createString(String string) { * condition is given. * @param stmts the statements in the catch clause * @param lineno the starting line number of the catch clause + * @param column the starting column number of the catch clause */ - private Node createCatch(Node varName, Node catchCond, Node stmts, int lineno) { + private Node createCatch(Node varName, Node catchCond, Node stmts, int lineno, int column) { if (varName == null) { varName = new Node(Token.EMPTY); } if (catchCond == null) { catchCond = new Node(Token.EMPTY); } - return new Node(Token.CATCH, varName, catchCond, stmts, lineno); + return new Node(Token.CATCH, varName, catchCond, stmts, lineno, column); } private static Node initFunction( @@ -1375,8 +1403,8 @@ private static Node initFunction( * Create loop node. The code generator will later call * createWhile|createDoWhile|createFor|createForIn to finish loop generation. */ - private Scope createLoopNode(Node loopLabel, int lineno) { - Scope result = parser.createScopeNode(Token.LOOP, lineno); + private Scope createLoopNode(Node loopLabel, int lineno, int column) { + Scope result = parser.createScopeNode(Token.LOOP, lineno, column); if (loopLabel != null) { ((Jump) loopLabel).setLoop(result); } @@ -1412,7 +1440,7 @@ private static Node createLoop( loop.addChildrenToBack(body); if (loopType == LOOP_WHILE || loopType == LOOP_FOR) { // propagate lineno to condition - loop.addChildrenToBack(new Node(Token.EMPTY, loop.getLineno())); + loop.addChildrenToBack(new Node(Token.EMPTY, loop.getLineno(), loop.getColumn())); } loop.addChildToBack(condTarget); loop.addChildToBack(IFEQ); @@ -1552,7 +1580,7 @@ private Node createForIn( *

... and a goto to GOTO around these handlers. */ private Node createTryCatchFinally( - Node tryBlock, Node catchBlocks, Node finallyBlock, int lineno) { + Node tryBlock, Node catchBlocks, Node finallyBlock, int lineno, int column) { boolean hasFinally = (finallyBlock != null) && (finallyBlock.getType() != Token.BLOCK || finallyBlock.hasChildren()); @@ -1571,7 +1599,8 @@ private Node createTryCatchFinally( } Node handlerBlock = new Node(Token.LOCAL_BLOCK); - Jump pn = new Jump(Token.TRY, tryBlock, lineno); + Jump pn = new Jump(Token.TRY, tryBlock); + pn.setLineColumnNumber(lineno, column); pn.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock); if (hasCatch) { @@ -1640,7 +1669,7 @@ private Node createTryCatchFinally( boolean hasDefault = false; int scopeIndex = 0; while (cb != null) { - int catchLineNo = cb.getLineno(); + int catchLineno = cb.getLineno(), catchColumn = cb.getColumn(); Node name = cb.getFirstChild(); Node cond = name.getNext(); @@ -1662,7 +1691,7 @@ private Node createTryCatchFinally( condStmt = catchStatement; hasDefault = true; } else { - condStmt = createIf(cond, catchStatement, null, catchLineNo); + condStmt = createIf(cond, catchStatement, null, catchLineno, catchColumn); } // Generate code to create the scope object and store @@ -1674,7 +1703,11 @@ private Node createTryCatchFinally( // Add with statement based on catch scope object catchScopeBlock.addChildToBack( - createWith(createUseLocal(catchScopeBlock), condStmt, catchLineNo)); + createWith( + createUseLocal(catchScopeBlock), + condStmt, + catchLineno, + catchColumn)); // move to next cb cb = cb.getNext(); @@ -1713,17 +1746,17 @@ private Node createTryCatchFinally( return handlerBlock; } - private Node createWith(Node obj, Node body, int lineno) { + private Node createWith(Node obj, Node body, int lineno, int column) { parser.setRequiresActivation(); - Node result = new Node(Token.BLOCK, lineno); + Node result = new Node(Token.BLOCK, lineno, column); result.addChildToBack(new Node(Token.ENTERWITH, obj)); - Node bodyNode = new Node(Token.WITH, body, lineno); + Node bodyNode = new Node(Token.WITH, body, lineno, column); result.addChildrenToBack(bodyNode); result.addChildToBack(new Node(Token.LEAVEWITH)); return result; } - private static Node createIf(Node cond, Node ifTrue, Node ifFalse, int lineno) { + private static Node createIf(Node cond, Node ifTrue, Node ifFalse, int lineno, int column) { int condStatus = isAlwaysDefinedBoolean(cond); if (condStatus == ALWAYS_TRUE_BOOLEAN) { return ifTrue; @@ -1732,10 +1765,10 @@ private static Node createIf(Node cond, Node ifTrue, Node ifFalse, int lineno) { return ifFalse; } // Replace if (false) xxx by empty block - return new Node(Token.BLOCK, lineno); + return new Node(Token.BLOCK, lineno, column); } - Node result = new Node(Token.BLOCK, lineno); + Node result = new Node(Token.BLOCK, lineno, column); Node ifNotTarget = Node.newTarget(); Jump IFNE = new Jump(Token.IFNE, cond); IFNE.target = ifNotTarget; @@ -1753,6 +1786,11 @@ private static Node createIf(Node cond, Node ifTrue, Node ifFalse, int lineno) { result.addChildToBack(ifNotTarget); } + if (cond.getFirstChild() != null) { + Node conditionalChild = cond.getFirstChild(); + result.setLineColumnNumber(conditionalChild.getLineno(), conditionalChild.getColumn()); + } + return result; } diff --git a/rhino/src/main/java/org/mozilla/javascript/Node.java b/rhino/src/main/java/org/mozilla/javascript/Node.java index 149326d5c7..928426fef5 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Node.java +++ b/rhino/src/main/java/org/mozilla/javascript/Node.java @@ -118,24 +118,24 @@ public Node(int nodeType, Node left, Node mid, Node right) { right.next = null; } - public Node(int nodeType, int line) { + public Node(int nodeType, int line, int column) { type = nodeType; - lineno = line; + setLineColumnNumber(line, column); } - public Node(int nodeType, Node child, int line) { + public Node(int nodeType, Node child, int line, int column) { this(nodeType, child); - lineno = line; + setLineColumnNumber(line, column); } - public Node(int nodeType, Node left, Node right, int line) { + public Node(int nodeType, Node left, Node right, int line, int column) { this(nodeType, left, right); - lineno = line; + setLineColumnNumber(line, column); } - public Node(int nodeType, Node left, Node mid, Node right, int line) { + public Node(int nodeType, Node left, Node mid, Node right, int line, int column) { this(nodeType, left, mid, right); - lineno = line; + setLineColumnNumber(line, column); } public static Node newNumber(double number) { @@ -531,8 +531,9 @@ public int getLineno() { return lineno; } - public void setLineno(int lineno) { + public void setLineColumnNumber(int lineno, int column) { this.lineno = lineno; + this.column = column; } /** Can only be called when getType() == Token.NUMBER */ @@ -1253,11 +1254,21 @@ private static void appendPrintId(Node n, Map printIds, StringBui } } + /** + * @return the column of where a Node is defined in source. If the column is -1, it was never + * initialized. One-based. + *

May be overridden by sub classes + */ + public int getColumn() { + return column; + } + protected int type = Token.ERROR; // type of the node, e.g. Token.NAME protected Node next; // next sibling protected Node first; // first element of a linked list of children protected Node last; // last element of a linked list of children protected int lineno = -1; + private int column = -1; /** * Linked list of properties. Since vast majority of nodes would have no more then 2 properties, diff --git a/rhino/src/main/java/org/mozilla/javascript/NodeTransformer.java b/rhino/src/main/java/org/mozilla/javascript/NodeTransformer.java index 18e0feb331..fe91153a79 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NodeTransformer.java +++ b/rhino/src/main/java/org/mozilla/javascript/NodeTransformer.java @@ -180,7 +180,8 @@ private void transformCompilationUnit_r( unwind = new Node(Token.LEAVEWITH); } if (unwindBlock == null) { - unwindBlock = new Node(Token.BLOCK, node.getLineno()); + unwindBlock = new Node(Token.BLOCK); + unwind.setLineColumnNumber(node.getLineno(), node.getColumn()); } unwindBlock.addChildToBack(unwind); } @@ -297,7 +298,8 @@ private void transformCompilationUnit_r( // to a LETEXPR if (n.getType() != Token.LETEXPR) throw Kit.codeBug(); } - Node pop = new Node(Token.EXPR_VOID, n, node.getLineno()); + Node pop = new Node(Token.EXPR_VOID, n); + pop.setLineColumnNumber(node.getLineno(), node.getColumn()); result.addChildToBack(pop); } node = replaceCurrent(parent, previous, node, result); diff --git a/rhino/src/main/java/org/mozilla/javascript/Parser.java b/rhino/src/main/java/org/mozilla/javascript/Parser.java index 422d4b666d..2a1fe44277 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Parser.java +++ b/rhino/src/main/java/org/mozilla/javascript/Parser.java @@ -154,6 +154,9 @@ public class Parser { private int prevNameTokenStart; private String prevNameTokenString = ""; private int prevNameTokenLineno; + private int prevNameTokenColumn; + private int lastTokenLineno = -1; + private int lastTokenColumn = -1; private boolean defaultUseStrictDirective; @@ -332,7 +335,7 @@ private static int getNodeEnd(AstNode n) { return n.getPosition() + n.getLength(); } - private void recordComment(int lineno, String comment) { + private void recordComment(int lineno, int column, String comment) { if (scannedComments == null) { scannedComments = new ArrayList<>(); } @@ -342,9 +345,9 @@ private void recordComment(int lineno, String comment) { && compilerEnv.isRecordingLocalJsDocComments()) { currentJsDocComment = new Comment(ts.tokenBeg, ts.getTokenLength(), ts.commentType, comment); - currentJsDocComment.setLineno(lineno); + currentJsDocComment.setLineColumnNumber(lineno, column); } - commentNode.setLineno(lineno); + commentNode.setLineColumnNumber(lineno, column); scannedComments.add(commentNode); } @@ -376,20 +379,18 @@ private int peekToken() throws IOException { return currentToken; } - int lineno = ts.getLineno(); int tt = ts.getToken(); boolean sawEOL = false; // process comments and whitespace while (tt == Token.EOL || tt == Token.COMMENT) { if (tt == Token.EOL) { - lineno++; sawEOL = true; tt = ts.getToken(); } else { if (compilerEnv.isRecordingComments()) { String comment = ts.getAndResetCurrentComment(); - recordComment(lineno, comment); + recordComment(ts.getTokenStartLineno(), ts.getTokenColumn(), comment); break; } tt = ts.getToken(); @@ -401,6 +402,14 @@ private int peekToken() throws IOException { return currentToken; // return unflagged token } + private int lineNumber() { + return lastTokenLineno; + } + + private int columnNumber() { + return lastTokenColumn; + } + private int peekFlaggedToken() throws IOException { peekToken(); return currentFlaggedToken; @@ -408,6 +417,8 @@ private int peekFlaggedToken() throws IOException { private void consumeToken() { currentFlaggedToken = Token.EOF; + lastTokenLineno = ts.getTokenStartLineno(); + lastTokenColumn = ts.getTokenColumn(); } private int nextToken() throws IOException { @@ -580,6 +591,8 @@ private AstRoot parse() throws IOException { currentScope = currentScriptOrFn = root; int baseLineno = ts.lineno; // line number where source starts + prevNameTokenLineno = ts.getLineno(); + prevNameTokenColumn = ts.getTokenColumn(); int end = pos; // in case source is empty boolean inDirectivePrologue = true; @@ -631,7 +644,7 @@ private AstRoot parse() throws IOException { } catch (StackOverflowError ex) { String msg = lookupMessage("msg.too.deep.parser.recursion"); if (!compilerEnv.isIdeMode()) - throw Context.reportRuntimeError(msg, sourceURI, ts.lineno, null, 0); + throw Context.reportRuntimeError(msg, sourceURI, lineNumber(), null, 0); } finally { inUseStrictDirective = savedStrictMode; } @@ -652,7 +665,7 @@ private AstRoot parse() throws IOException { root.setLength(end - pos); root.setSourceName(sourceURI); root.setBaseLineno(baseLineno); - root.setEndLineno(ts.lineno); + root.setEndLineno(ts.getLineno()); return root; } @@ -679,7 +692,7 @@ private AstNode parseFunctionBody(int type, FunctionNode fnNode) throws IOExcept boolean savedStrictMode = inUseStrictDirective; inUseStrictDirective = false; - pn.setLineno(ts.lineno); + pn.setLineColumnNumber(lineNumber(), columnNumber()); try { if (isExpressionClosure) { AstNode returnValue = assignExpr(); @@ -688,6 +701,7 @@ private AstNode parseFunctionBody(int type, FunctionNode fnNode) throws IOExcept returnValue.getPosition(), returnValue.getLength(), returnValue); // expression closure flag is required on both nodes n.putProp(Node.EXPRESSION_CLOSURE_PROP, Boolean.TRUE); + n.setLineColumnNumber(returnValue.getLineno(), returnValue.getColumn()); pn.putProp(Node.EXPRESSION_CLOSURE_PROP, Boolean.TRUE); if (isArrow) { n.putProp(Node.ARROW_FUNCTION_PROP, Boolean.TRUE); @@ -827,6 +841,7 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException { } } else { boolean wasRest = false; + int restStartLineno = -1, restStartColumn = -1; if (tt == Token.DOTDOTDOT) { if (fnNode.hasRestParameter()) { // Error: parameter after rest parameter @@ -836,6 +851,8 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException { fnNode.setHasRestParameter(true); wasRest = true; consumeToken(); + restStartLineno = lineNumber(); + restStartColumn = columnNumber(); } if (mustMatchToken(Token.NAME, "msg.no.parm", true)) { @@ -845,6 +862,9 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException { } Name paramNameNode = createNameNode(); + if (wasRest) { + paramNameNode.setLineColumnNumber(restStartLineno, restStartColumn); + } Comment jsdocNodeForName = getAndResetJsDoc(); if (jsdocNodeForName != null) { paramNameNode.setJsDocNode(jsdocNodeForName); @@ -901,8 +921,9 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException { private FunctionNode function(int type) throws IOException { boolean isGenerator = false; int syntheticType = type; - int baseLineno = ts.lineno; // line number where source starts + int baseLineno = lineNumber(); // line number where source starts int functionSourceStart = ts.tokenBeg; // start of "function" kwd + int functionStartColumn = columnNumber(); Name name = null; AstNode memberExprNode = null; @@ -999,8 +1020,8 @@ private FunctionNode function(int type) throws IOException { } fnNode.setSourceName(sourceURI); - fnNode.setBaseLineno(baseLineno); - fnNode.setEndLineno(ts.lineno); + fnNode.setLineColumnNumber(baseLineno, functionStartColumn); + fnNode.setEndLineno(lineNumber()); // Set the parent scope. Needed for finding undeclared vars. // Have to wait until after parsing the function to set its parent @@ -1012,8 +1033,9 @@ private FunctionNode function(int type) throws IOException { return fnNode; } - private AstNode arrowFunction(AstNode params) throws IOException { - int baseLineno = ts.lineno; // line number where source starts + private AstNode arrowFunction(AstNode params, int startLine, int startColumn) + throws IOException { + int baseLineno = lineNumber(); // line number where source starts int functionSourceStart = params != null ? params.getPosition() : -1; // start of "function" kwd @@ -1078,7 +1100,8 @@ private AstNode arrowFunction(AstNode params) throws IOException { fnNode.setSourceName(sourceURI); fnNode.setBaseLineno(baseLineno); - fnNode.setEndLineno(ts.lineno); + fnNode.setEndLineno(lineNumber()); + fnNode.setLineColumnNumber(startLine, startColumn); return fnNode; } @@ -1166,7 +1189,7 @@ private AstNode statements(AstNode parent) throws IOException { && !compilerEnv.isIdeMode()) codeBug(); int pos = ts.tokenBeg; AstNode block = parent != null ? parent : new Block(pos); - block.setLineno(ts.lineno); + block.setLineColumnNumber(lineNumber(), columnNumber()); int tt; while ((tt = peekToken()) > Token.EOF && tt != Token.RC) { @@ -1262,6 +1285,7 @@ private AstNode statementHelper() throws IOException { AstNode pn = null; int tt = peekToken(), pos = ts.tokenBeg; + int lineno, column; switch (tt) { case Token.IF: @@ -1303,9 +1327,10 @@ private AstNode statementHelper() throws IOException { case Token.CONST: case Token.VAR: consumeToken(); - int lineno = ts.lineno; + lineno = lineNumber(); + column = columnNumber(); pn = variables(currentToken, ts.tokenBeg, true); - pn.setLineno(lineno); + pn.setLineColumnNumber(lineno, column); break; case Token.LET: @@ -1321,7 +1346,7 @@ private AstNode statementHelper() throws IOException { case Token.DEBUGGER: consumeToken(); pn = new KeywordLiteral(ts.tokenBeg, ts.tokenEnd - ts.tokenBeg, tt); - pn.setLineno(ts.lineno); + pn.setLineColumnNumber(lineNumber(), columnNumber()); break; case Token.LC: @@ -1335,7 +1360,7 @@ private AstNode statementHelper() throws IOException { consumeToken(); pos = ts.tokenBeg; pn = new EmptyStatement(pos, ts.tokenEnd - pos); - pn.setLineno(ts.lineno); + pn.setLineColumnNumber(lineNumber(), columnNumber()); return pn; case Token.FUNCTION: @@ -1355,9 +1380,12 @@ private AstNode statementHelper() throws IOException { pn = scannedComments.get(scannedComments.size() - 1); return pn; default: - lineno = ts.lineno; + // Intentionally not calling lineNumber/columnNumber here! + // We have not consumed any token yet, so the position would be invalid + lineno = ts.getLineno(); + column = ts.getTokenColumn(); pn = new ExpressionStatement(expr(false), !insideFunction()); - pn.setLineno(lineno); + pn.setLineColumnNumber(lineno, column); break; } @@ -1397,7 +1425,7 @@ private void autoInsertSemicolon(AstNode pn) throws IOException { private IfStatement ifStatement() throws IOException { if (currentToken != Token.IF) codeBug(); consumeToken(); - int pos = ts.tokenBeg, lineno = ts.lineno, elsePos = -1; + int pos = ts.tokenBeg, lineno = lineNumber(), elsePos = -1, column = columnNumber(); IfStatement pn = new IfStatement(pos); ConditionData data = condition(); AstNode ifTrue = getNextStatementAfterInlineComments(pn), ifFalse = null; @@ -1417,7 +1445,7 @@ private IfStatement ifStatement() throws IOException { pn.setThenPart(ifTrue); pn.setElsePart(ifFalse); pn.setElsePosition(elsePos); - pn.setLineno(lineno); + pn.setLineColumnNumber(lineno, column); return pn; } @@ -1427,8 +1455,8 @@ private SwitchStatement switchStatement() throws IOException { int pos = ts.tokenBeg; SwitchStatement pn = new SwitchStatement(pos); + pn.setLineColumnNumber(lineNumber(), columnNumber()); if (mustMatchToken(Token.LP, "msg.no.paren.switch", true)) pn.setLp(ts.tokenBeg - pos); - pn.setLineno(ts.lineno); AstNode discriminant = expr(false); pn.setExpression(discriminant); @@ -1446,7 +1474,7 @@ private SwitchStatement switchStatement() throws IOException { for (; ; ) { tt = nextToken(); int casePos = ts.tokenBeg; - int caseLineno = ts.lineno; + int caseLineno = lineNumber(), caseColumn = columnNumber(); AstNode caseExpression = null; switch (tt) { case Token.RC: @@ -1477,7 +1505,7 @@ private SwitchStatement switchStatement() throws IOException { SwitchCase caseNode = new SwitchCase(casePos); caseNode.setExpression(caseExpression); caseNode.setLength(ts.tokenEnd - pos); // include colon - caseNode.setLineno(caseLineno); + caseNode.setLineColumnNumber(caseLineno, caseColumn); while ((tt = peekToken()) != Token.RC && tt != Token.CASE @@ -1510,7 +1538,7 @@ private WhileLoop whileLoop() throws IOException { consumeToken(); int pos = ts.tokenBeg; WhileLoop pn = new WhileLoop(pos); - pn.setLineno(ts.lineno); + pn.setLineColumnNumber(lineNumber(), columnNumber()); enterLoop(pn); try { ConditionData data = condition(); @@ -1531,7 +1559,7 @@ private DoLoop doLoop() throws IOException { consumeToken(); int pos = ts.tokenBeg, end; DoLoop pn = new DoLoop(pos); - pn.setLineno(ts.lineno); + pn.setLineColumnNumber(lineNumber(), columnNumber()); enterLoop(pn); try { AstNode body = getNextStatementAfterInlineComments(pn); @@ -1581,7 +1609,7 @@ private AstNode getNextStatementAfterInlineComments(AstNode pn) throws IOExcepti private Loop forLoop() throws IOException { if (currentToken != Token.FOR) codeBug(); consumeToken(); - int forPos = ts.tokenBeg, lineno = ts.lineno; + int forPos = ts.tokenBeg, lineno = lineNumber(), column = columnNumber(); boolean isForEach = false, isForIn = false, isForOf = false; int eachPos = -1, inPos = -1, lp = -1, rp = -1; AstNode init = null; // init is also foo in 'foo in object' @@ -1623,7 +1651,8 @@ && matchToken(Token.NAME, true) if (peekToken() == Token.SEMI) { // no loop condition cond = new EmptyExpression(ts.tokenBeg, 1); - cond.setLineno(ts.lineno); + // We haven't consumed the token, so we need the CURRENT lexer position + cond.setLineColumnNumber(ts.getLineno(), ts.getTokenColumn()); } else { cond = expr(false); } @@ -1632,7 +1661,8 @@ && matchToken(Token.NAME, true) int tmpPos = ts.tokenEnd; if (peekToken() == Token.RP) { incr = new EmptyExpression(tmpPos, 1); - incr.setLineno(ts.lineno); + // We haven't consumed the token, so we need the CURRENT lexer position + incr.setLineColumnNumber(ts.getLineno(), ts.getTokenColumn()); } else { incr = expr(false); } @@ -1689,7 +1719,7 @@ && matchToken(Token.NAME, true) } } pn.setParens(lp, rp); - pn.setLineno(lineno); + pn.setLineColumnNumber(lineno, column); return pn; } @@ -1699,7 +1729,8 @@ private AstNode forLoopInit(int tt) throws IOException { AstNode init = null; if (tt == Token.SEMI) { init = new EmptyExpression(ts.tokenBeg, 1); - init.setLineno(ts.lineno); + // We haven't consumed the token, so we need the CURRENT lexer position + init.setLineColumnNumber(ts.getLineno(), ts.getTokenColumn()); } else if (tt == Token.VAR || tt == Token.LET) { consumeToken(); init = variables(tt, ts.tokenBeg, false); @@ -1719,7 +1750,7 @@ private TryStatement tryStatement() throws IOException { // Pull out JSDoc info and reset it before recursing. Comment jsdocNode = getAndResetJsDoc(); - int tryPos = ts.tokenBeg, lineno = ts.lineno, finallyPos = -1; + int tryPos = ts.tokenBeg, lineno = lineNumber(), column = columnNumber(), finallyPos = -1; TryStatement pn = new TryStatement(tryPos); // Hnadled comment here because there should not be try without LC @@ -1748,11 +1779,16 @@ private TryStatement tryStatement() throws IOException { } if (peek == Token.CATCH) { while (matchToken(Token.CATCH, true)) { - int catchLineNum = ts.lineno; + int catchLineNum = lineNumber(); if (sawDefaultCatch) { reportError("msg.catch.unreachable"); } - int catchPos = ts.tokenBeg, lp = -1, rp = -1, guardPos = -1; + int catchPos = ts.tokenBeg, + lp = -1, + rp = -1, + guardPos = -1, + catchLine = lineNumber(), + catchColumn = columnNumber(); Name varName = null; AstNode catchCond = null; @@ -1803,7 +1839,7 @@ private TryStatement tryStatement() throws IOException { Scope catchScope = new Scope(catchPos); CatchClause catchNode = new CatchClause(catchPos); - catchNode.setLineno(ts.lineno); + catchNode.setLineColumnNumber(catchLine, catchColumn); pushScope(catchScope); try { statements(catchScope); @@ -1819,7 +1855,6 @@ private TryStatement tryStatement() throws IOException { catchNode.setIfPosition(guardPos - catchPos); } catchNode.setParens(lp, rp); - catchNode.setLineno(catchLineNum); if (mustMatchToken(Token.RC, "msg.no.brace.after.body", true)) tryEnd = ts.tokenEnd; catchNode.setLength(tryEnd - catchPos); @@ -1844,7 +1879,7 @@ private TryStatement tryStatement() throws IOException { if (finallyPos != -1) { pn.setFinallyPosition(finallyPos - tryPos); } - pn.setLineno(lineno); + pn.setLineColumnNumber(lineno, column); if (jsdocNode != null) { pn.setJsDocNode(jsdocNode); @@ -1856,7 +1891,7 @@ private TryStatement tryStatement() throws IOException { private ThrowStatement throwStatement() throws IOException { if (currentToken != Token.THROW) codeBug(); consumeToken(); - int pos = ts.tokenBeg, lineno = ts.lineno; + int pos = ts.tokenBeg, lineno = lineNumber(), column = columnNumber(); if (peekTokenOrEOL() == Token.EOL) { // ECMAScript does not allow new lines before throw expression, // see bug 256617 @@ -1864,7 +1899,7 @@ private ThrowStatement throwStatement() throws IOException { } AstNode expr = expr(false); ThrowStatement pn = new ThrowStatement(pos, expr); - pn.setLineno(lineno); + pn.setLineColumnNumber(lineno, column); return pn; } @@ -1893,7 +1928,7 @@ private LabeledStatement matchJumpLabelName() throws IOException { private BreakStatement breakStatement() throws IOException { if (currentToken != Token.BREAK) codeBug(); consumeToken(); - int lineno = ts.lineno, pos = ts.tokenBeg, end = ts.tokenEnd; + int lineno = lineNumber(), pos = ts.tokenBeg, end = ts.tokenEnd, column = columnNumber(); Name breakLabel = null; if (peekTokenOrEOL() == Token.NAME) { breakLabel = createNameNode(); @@ -1913,18 +1948,22 @@ private BreakStatement breakStatement() throws IOException { } } + if (breakLabel != null) { + breakLabel.setLineColumnNumber(lineNumber(), columnNumber()); + } + BreakStatement pn = new BreakStatement(pos, end - pos); pn.setBreakLabel(breakLabel); // can be null if it's a bad break in error-recovery mode if (breakTarget != null) pn.setBreakTarget(breakTarget); - pn.setLineno(lineno); + pn.setLineColumnNumber(lineno, column); return pn; } private ContinueStatement continueStatement() throws IOException { if (currentToken != Token.CONTINUE) codeBug(); consumeToken(); - int lineno = ts.lineno, pos = ts.tokenBeg, end = ts.tokenEnd; + int lineno = lineNumber(), pos = ts.tokenBeg, end = ts.tokenEnd, column = columnNumber(); Name label = null; if (peekTokenOrEOL() == Token.NAME) { label = createNameNode(); @@ -1947,11 +1986,15 @@ private ContinueStatement continueStatement() throws IOException { target = labels == null ? null : (Loop) labels.getStatement(); } + if (label != null) { + label.setLineColumnNumber(lineNumber(), columnNumber()); + } + ContinueStatement pn = new ContinueStatement(pos, end - pos); if (target != null) // can be null in error-recovery mode pn.setTarget(target); pn.setLabel(label); - pn.setLineno(lineno); + pn.setLineColumnNumber(lineno, column); return pn; } @@ -1961,7 +2004,7 @@ private WithStatement withStatement() throws IOException { Comment withComment = getAndResetJsDoc(); - int lineno = ts.lineno, pos = ts.tokenBeg, lp = -1, rp = -1; + int lineno = lineNumber(), column = columnNumber(), pos = ts.tokenBeg, lp = -1, rp = -1; if (mustMatchToken(Token.LP, "msg.no.paren.with", true)) lp = ts.tokenBeg; AstNode obj = expr(false); @@ -1975,21 +2018,21 @@ private WithStatement withStatement() throws IOException { pn.setExpression(obj); pn.setStatement(body); pn.setParens(lp, rp); - pn.setLineno(lineno); + pn.setLineColumnNumber(lineno, column); return pn; } private AstNode letStatement() throws IOException { if (currentToken != Token.LET) codeBug(); consumeToken(); - int lineno = ts.lineno, pos = ts.tokenBeg; + int lineno = lineNumber(), pos = ts.tokenBeg, column = columnNumber(); AstNode pn; if (peekToken() == Token.LP) { pn = let(true, pos); } else { pn = variables(Token.LET, pos, true); // else, e.g.: let x=6, y=7; } - pn.setLineno(lineno); + pn.setLineColumnNumber(lineno, column); return pn; } @@ -2010,7 +2053,7 @@ private AstNode returnOrYield(int tt, boolean exprContext) throws IOException { reportError(tt == Token.RETURN ? "msg.bad.return" : "msg.bad.yield"); } consumeToken(); - int lineno = ts.lineno, pos = ts.tokenBeg, end = ts.tokenEnd; + int lineno = lineNumber(), column = columnNumber(), pos = ts.tokenBeg, end = ts.tokenEnd; boolean yieldStar = false; if ((tt == Token.YIELD) @@ -2059,6 +2102,7 @@ private AstNode returnOrYield(int tt, boolean exprContext) throws IOException { setRequiresActivation(); setIsGenerator(); if (!exprContext) { + ret.setLineColumnNumber(lineno, column); ret = new ExpressionStatement(ret); } } @@ -2077,7 +2121,7 @@ && nowAllSet(before, endFlags, Node.END_YIELDS | Node.END_RETURNS_VALUE)) { } } - ret.setLineno(lineno); + ret.setLineColumnNumber(lineno, column); return ret; } @@ -2086,7 +2130,7 @@ private AstNode block() throws IOException { consumeToken(); int pos = ts.tokenBeg; Scope block = new Scope(pos); - block.setLineno(ts.lineno); + block.setLineColumnNumber(lineNumber(), columnNumber()); pushScope(block); try { statements(block); @@ -2103,7 +2147,7 @@ private AstNode defaultXmlNamespace() throws IOException { consumeToken(); mustHaveXML(); setRequiresActivation(); - int lineno = ts.lineno, pos = ts.tokenBeg; + int lineno = lineNumber(), column = columnNumber(), pos = ts.tokenBeg; if (!(matchToken(Token.NAME, true) && "xml".equals(ts.getString()))) { reportError("msg.bad.namespace"); @@ -2119,7 +2163,7 @@ private AstNode defaultXmlNamespace() throws IOException { UnaryExpression dxmln = new UnaryExpression(pos, getNodeEnd(e) - pos); dxmln.setOperator(Token.DEFAULTNAMESPACE); dxmln.setOperand(e); - dxmln.setLineno(lineno); + dxmln.setLineColumnNumber(lineno, column); ExpressionStatement es = new ExpressionStatement(dxmln, true); return es; @@ -2161,13 +2205,13 @@ private AstNode nameOrLabel() throws IOException { if (expr.getType() != Token.LABEL) { AstNode n = new ExpressionStatement(expr, !insideFunction()); - n.lineno = expr.lineno; + n.setLineColumnNumber(expr.getLineno(), expr.getColumn()); return n; } LabeledStatement bundle = new LabeledStatement(pos); recordLabel((Label) expr, bundle); - bundle.setLineno(ts.lineno); + bundle.setLineColumnNumber(expr.getLineno(), expr.getColumn()); // look for more labels AstNode stmt = null; while (peekToken() == Token.NAME) { @@ -2223,7 +2267,7 @@ private VariableDeclaration variables(int declType, int pos, boolean isStatement int end; VariableDeclaration pn = new VariableDeclaration(pos); pn.setType(declType); - pn.setLineno(ts.lineno); + pn.setLineColumnNumber(lineNumber(), columnNumber()); Comment varjsdocNode = getAndResetJsDoc(); if (varjsdocNode != null) { pn.setJsDocNode(varjsdocNode); @@ -2252,7 +2296,7 @@ private VariableDeclaration variables(int declType, int pos, boolean isStatement // Simple variable name mustMatchToken(Token.NAME, "msg.bad.var", true); name = createNameNode(); - name.setLineno(ts.getLineno()); + name.setLineColumnNumber(lineNumber(), columnNumber()); if (inUseStrictDirective) { String id = ts.getString(); if ("eval".equals(id) || "arguments".equals(ts.getString())) { @@ -2262,7 +2306,7 @@ private VariableDeclaration variables(int declType, int pos, boolean isStatement defineSymbol(declType, ts.getString(), inForInit); } - int lineno = ts.lineno; + int lineno = lineNumber(), column = columnNumber(); Comment jsdocNode = getAndResetJsDoc(); @@ -2284,7 +2328,7 @@ private VariableDeclaration variables(int declType, int pos, boolean isStatement vi.setInitializer(init); vi.setType(declType); vi.setJsDocNode(jsdocNode); - vi.setLineno(lineno); + vi.setLineColumnNumber(lineno, column); pn.addVariable(vi); if (!matchToken(Token.COMMA, true)) break; @@ -2297,7 +2341,7 @@ private VariableDeclaration variables(int declType, int pos, boolean isStatement // have to pass in 'let' kwd position to compute kid offsets properly private AstNode let(boolean isStatement, int pos) throws IOException { LetNode pn = new LetNode(pos); - pn.setLineno(ts.lineno); + pn.setLineColumnNumber(lineNumber(), columnNumber()); if (mustMatchToken(Token.LP, "msg.no.paren.after.let", true)) pn.setLp(ts.tokenBeg - pos); pushScope(pn); try { @@ -2324,7 +2368,7 @@ private AstNode let(boolean isStatement, int pos) throws IOException { if (isStatement) { // let expression in statement context ExpressionStatement es = new ExpressionStatement(pn, !insideFunction()); - es.setLineno(pn.getLineno()); + es.setLineColumnNumber(pn.getLineno(), pn.getColumn()); return es; } } @@ -2424,6 +2468,11 @@ private AstNode assignExpr() throws IOException { if (tt == Token.YIELD) { return returnOrYield(tt, true); } + + // Intentionally not calling lineNumber/columnNumber here! + // We have not consumed any token yet, so the position would be invalid + int startLine = ts.lineno, startColumn = ts.getTokenColumn(); + AstNode pn = condExpr(); boolean hasEOL = false; tt = peekTokenOrEOL(); @@ -2455,7 +2504,7 @@ private AstNode assignExpr() throws IOException { } } else if (!hasEOL && tt == Token.ARROW) { consumeToken(); - pn = arrowFunction(pn); + pn = arrowFunction(pn, startLine, startColumn); } else if (pn.getIntProp(Node.OBJECT_LITERAL_DESTRUCTURING, 0) == 1 && !inDestructuringAssignment) { reportError("msg.syntax"); @@ -2472,7 +2521,6 @@ private static boolean isNotValidSimpleAssignmentTarget(AstNode pn) { private AstNode condExpr() throws IOException { AstNode pn = nullishCoalescingExpr(); if (matchToken(Token.HOOK, true)) { - int line = ts.lineno; int qmarkPos = ts.tokenBeg, colonPos = -1; /* * Always accept the 'in' operator in the middle clause of a ternary, @@ -2491,7 +2539,7 @@ private AstNode condExpr() throws IOException { AstNode ifFalse = assignExpr(); int beg = pn.getPosition(), len = getNodeEnd(ifFalse) - beg; ConditionalExpression ce = new ConditionalExpression(beg, len); - ce.setLineno(line); + ce.setLineColumnNumber(pn.getLineno(), pn.getColumn()); ce.setTestExpression(pn); ce.setTrueExpression(ifTrue); ce.setFalseExpression(ifFalse); @@ -2688,7 +2736,7 @@ private AstNode unaryExpr() throws IOException { consumeToken(); tt = peekUntilNonComment(tt); } - int line = ts.lineno; + int line, column; switch (tt) { case Token.VOID: @@ -2696,36 +2744,46 @@ private AstNode unaryExpr() throws IOException { case Token.BITNOT: case Token.TYPEOF: consumeToken(); + line = lineNumber(); + column = columnNumber(); node = new UnaryExpression(tt, ts.tokenBeg, unaryExpr()); - node.setLineno(line); + node.setLineColumnNumber(line, column); return node; case Token.ADD: consumeToken(); + line = lineNumber(); + column = columnNumber(); // Convert to special POS token in parse tree node = new UnaryExpression(Token.POS, ts.tokenBeg, unaryExpr()); - node.setLineno(line); + node.setLineColumnNumber(line, column); return node; case Token.SUB: consumeToken(); + line = lineNumber(); + column = columnNumber(); // Convert to special NEG token in parse tree node = new UnaryExpression(Token.NEG, ts.tokenBeg, unaryExpr()); - node.setLineno(line); + node.setLineColumnNumber(line, column); return node; case Token.INC: case Token.DEC: consumeToken(); + line = lineNumber(); + column = columnNumber(); UpdateExpression expr = new UpdateExpression(tt, ts.tokenBeg, memberExpr(true)); - expr.setLineno(line); + expr.setLineColumnNumber(line, column); checkBadIncDec(expr); return expr; case Token.DELPROP: consumeToken(); + line = lineNumber(); + column = columnNumber(); node = new UnaryExpression(tt, ts.tokenBeg, unaryExpr()); - node.setLineno(line); + node.setLineColumnNumber(line, column); return node; case Token.ERROR: @@ -2749,7 +2807,7 @@ private AstNode unaryExpr() throws IOException { } consumeToken(); UpdateExpression uexpr = new UpdateExpression(tt, ts.tokenBeg, pn, true); - uexpr.setLineno(line); + uexpr.setLineColumnNumber(pn.getLineno(), pn.getColumn()); checkBadIncDec(uexpr); return uexpr; } @@ -2764,7 +2822,7 @@ private AstNode xmlInitializer() throws IOException { } XmlLiteral pn = new XmlLiteral(pos); - pn.setLineno(ts.lineno); + pn.setLineColumnNumber(lineNumber(), columnNumber()); for (; ; tt = ts.getNextXMLToken()) { switch (tt) { @@ -2834,19 +2892,20 @@ private List argumentList() throws IOException { * @param allowCallSyntax passed down to {@link #memberExprTail} */ private AstNode memberExpr(boolean allowCallSyntax) throws IOException { - int tt = peekToken(), lineno = ts.lineno; + int tt = peekToken(); AstNode pn; if (tt != Token.NEW) { pn = primaryExpr(); } else { consumeToken(); - int pos = ts.tokenBeg; + int pos = ts.tokenBeg, lineno = lineNumber(), column = columnNumber(); NewExpression nx = new NewExpression(pos); AstNode target = memberExpr(false); int end = getNodeEnd(target); nx.setTarget(target); + nx.setLineColumnNumber(lineno, column); int lp = -1; if (matchToken(Token.LP, true)) { @@ -2872,9 +2931,7 @@ private AstNode memberExpr(boolean allowCallSyntax) throws IOException { nx.setLength(end - pos); pn = nx; } - pn.setLineno(lineno); - AstNode tail = memberExprTail(allowCallSyntax, pn); - return tail; + return memberExprTail(allowCallSyntax, pn); } /** @@ -2889,25 +2946,24 @@ private AstNode memberExprTail(boolean allowCallSyntax, AstNode pn) throws IOExc // we no longer return null for errors, so this won't be null if (pn == null) codeBug(); int pos = pn.getPosition(); - int lineno; + int lineno, column; boolean isOptionalChain = false; tailLoop: for (; ; ) { + lineno = lineNumber(); + column = columnNumber(); int tt = peekToken(); switch (tt) { case Token.DOT: case Token.QUESTION_DOT: case Token.DOTDOT: - lineno = ts.lineno; isOptionalChain |= (tt == Token.QUESTION_DOT); pn = propertyAccess(tt, pn, isOptionalChain); - pn.setLineno(lineno); break; case Token.DOTQUERY: consumeToken(); int opPos = ts.tokenBeg, rp = -1; - lineno = ts.lineno; mustHaveXML(); setRequiresActivation(); AstNode filter = expr(false); @@ -2921,22 +2977,20 @@ private AstNode memberExprTail(boolean allowCallSyntax, AstNode pn) throws IOExc q.setRight(filter); q.setOperatorPosition(opPos); q.setRp(rp - pos); - q.setLineno(lineno); + q.setLineColumnNumber(lineno, column); pn = q; break; case Token.LB: consumeToken(); - lineno = ts.lineno; - pn = makeElemGet(pn, ts.tokenBeg, lineno); + pn = makeElemGet(pn, ts.tokenBeg); break; case Token.LP: if (!allowCallSyntax) { break tailLoop; } - lineno = ts.lineno; - pn = makeFunctionCall(pn, pos, lineno, isOptionalChain); + pn = makeFunctionCall(pn, pos, isOptionalChain); break; case Token.COMMENT: // Ignoring all the comments, because previous statement may not be terminated @@ -2959,15 +3013,12 @@ private AstNode memberExprTail(boolean allowCallSyntax, AstNode pn) throws IOExc return pn; } - private FunctionCall makeFunctionCall(AstNode pn, int pos, int lineno, boolean isOptionalChain) + private FunctionCall makeFunctionCall(AstNode pn, int pos, boolean isOptionalChain) throws IOException { consumeToken(); checkCallRequiresActivation(pn); FunctionCall f = new FunctionCall(pos); f.setTarget(pn); - // Assign the line number for the function call to where - // the paren appeared, not where the name expression started. - f.setLineno(lineno); f.setLp(ts.tokenBeg - pos); List args = argumentList(); if (args != null && args.size() > ARGC_LIMIT) reportError("msg.too.many.function.args"); @@ -2985,6 +3036,7 @@ private AstNode taggedTemplateLiteral(AstNode pn) throws IOException { TaggedTemplateLiteral tagged = new TaggedTemplateLiteral(); tagged.setTarget(pn); tagged.setTemplateLiteral(templateLiteral); + tagged.setLineColumnNumber(pn.getLineno(), pn.getColumn()); return tagged; } @@ -2998,7 +3050,10 @@ private AstNode taggedTemplateLiteral(AstNode pn) throws IOException { */ private AstNode propertyAccess(int tt, AstNode pn, boolean isOptionalChain) throws IOException { if (pn == null) codeBug(); - int memberTypeFlags = 0, lineno = ts.lineno, dotPos = ts.tokenBeg; + int memberTypeFlags = 0, + lineno = lineNumber(), + dotPos = ts.tokenBeg, + column = columnNumber(); consumeToken(); if (tt == Token.DOTDOT) { @@ -3019,7 +3074,7 @@ private AstNode propertyAccess(int tt, AstNode pn, boolean isOptionalChain) thro Name name = createNameNode(true, Token.GETPROP); PropertyGet pg = new PropertyGet(pn, name, dotPos); - pg.setLineno(lineno); + pg.setLineColumnNumber(lineno, column); return pg; } @@ -3028,7 +3083,7 @@ private AstNode propertyAccess(int tt, AstNode pn, boolean isOptionalChain) thro switch (token) { case Token.THROW: // needed for generator.throw(); - saveNameTokenData(ts.tokenBeg, "throw", ts.lineno); + saveNameTokenData(ts.tokenBeg, "throw", lineNumber(), columnNumber()); ref = propertyName(-1, memberTypeFlags); break; @@ -3039,7 +3094,7 @@ private AstNode propertyAccess(int tt, AstNode pn, boolean isOptionalChain) thro case Token.MUL: // handles: *, *::name, *::*, *::[expr] - saveNameTokenData(ts.tokenBeg, "*", ts.lineno); + saveNameTokenData(ts.tokenBeg, "*", lineNumber(), columnNumber()); ref = propertyName(-1, memberTypeFlags); break; @@ -3052,7 +3107,7 @@ private AstNode propertyAccess(int tt, AstNode pn, boolean isOptionalChain) thro case Token.RESERVED: { String name = ts.getString(); - saveNameTokenData(ts.tokenBeg, name, ts.lineno); + saveNameTokenData(ts.tokenBeg, name, lineNumber(), columnNumber()); ref = propertyName(-1, memberTypeFlags); break; } @@ -3061,7 +3116,7 @@ private AstNode propertyAccess(int tt, AstNode pn, boolean isOptionalChain) thro if (tt == Token.QUESTION_DOT) { // a ?.[ expr ] consumeToken(); - ElementGet g = makeElemGet(pn, ts.tokenBeg, ts.lineno); + ElementGet g = makeElemGet(pn, ts.tokenBeg); g.setType(Token.QUESTION_DOT); return g; } else { @@ -3072,7 +3127,7 @@ private AstNode propertyAccess(int tt, AstNode pn, boolean isOptionalChain) thro case Token.LP: if (tt == Token.QUESTION_DOT) { // a function call such as f?.() - return makeFunctionCall(pn, pn.getPosition(), lineno, isOptionalChain); + return makeFunctionCall(pn, pn.getPosition(), isOptionalChain); } else { reportError("msg.no.name.after.dot"); return makeErrorNode(); @@ -3083,7 +3138,7 @@ private AstNode propertyAccess(int tt, AstNode pn, boolean isOptionalChain) thro // allow keywords as property names, e.g. ({if: 1}) String name = Token.keywordToName(token); if (name != null) { - saveNameTokenData(ts.tokenBeg, name, ts.lineno); + saveNameTokenData(ts.tokenBeg, name, lineNumber(), columnNumber()); ref = propertyName(-1, memberTypeFlags); break; } @@ -3102,13 +3157,13 @@ private AstNode propertyAccess(int tt, AstNode pn, boolean isOptionalChain) thro result.setPosition(pos); result.setLength(getNodeEnd(ref) - pos); result.setOperatorPosition(dotPos - pos); - result.setLineno(pn.getLineno()); + result.setLineColumnNumber(lineno, column); result.setLeft(pn); // do this after setting position result.setRight(ref); return result; } - private ElementGet makeElemGet(AstNode pn, int lb, int lineno) throws IOException { + private ElementGet makeElemGet(AstNode pn, int lb) throws IOException { int pos = pn.getPosition(); AstNode expr = expr(false); int end = getNodeEnd(expr); @@ -3121,7 +3176,6 @@ private ElementGet makeElemGet(AstNode pn, int lb, int lineno) throws IOExceptio g.setTarget(pn); g.setElement(expr); g.setParens(lb, rb); - g.setLineno(lineno); return g; } @@ -3143,7 +3197,7 @@ private AstNode attributeAccess() throws IOException { // handles: @*, @*::name, @*::*, @*::[expr] case Token.MUL: - saveNameTokenData(ts.tokenBeg, "*", ts.lineno); + saveNameTokenData(ts.tokenBeg, "*", lineNumber(), columnNumber()); return propertyName(atPos, 0); // handles @[expr] @@ -3166,7 +3220,7 @@ private AstNode attributeAccess() throws IOException { * malformed XML expressions. (For now - might change to return a partial XmlRef.) */ private AstNode propertyName(int atPos, int memberTypeFlags) throws IOException { - int pos = atPos != -1 ? atPos : ts.tokenBeg, lineno = ts.lineno; + int pos = atPos != -1 ? atPos : ts.tokenBeg, lineno = lineNumber(), column = columnNumber(); int colonPos = -1; Name name = createNameNode(true, currentToken); Name ns = null; @@ -3183,7 +3237,7 @@ private AstNode propertyName(int atPos, int memberTypeFlags) throws IOException // handles name::* case Token.MUL: - saveNameTokenData(ts.tokenBeg, "*", ts.lineno); + saveNameTokenData(ts.tokenBeg, "*", lineNumber(), columnNumber()); name = createNameNode(false, -1); break; @@ -3206,7 +3260,7 @@ private AstNode propertyName(int atPos, int memberTypeFlags) throws IOException ref.setNamespace(ns); ref.setColonPos(colonPos); ref.setPropName(name); - ref.setLineno(lineno); + ref.setLineColumnNumber(lineno, column); return ref; } @@ -3303,6 +3357,7 @@ private AstNode primaryExpr() throws IOException { RegExpLiteral re = new RegExpLiteral(pos, end - pos); re.setValue(ts.getString()); re.setFlags(ts.readAndClearRegExpFlags()); + re.setLineColumnNumber(lineNumber(), columnNumber()); return re; case Token.NULL: @@ -3312,7 +3367,9 @@ private AstNode primaryExpr() throws IOException { consumeToken(); pos = ts.tokenBeg; end = ts.tokenEnd; - return new KeywordLiteral(pos, end - pos, tt); + KeywordLiteral keywordLiteral = new KeywordLiteral(pos, end - pos, tt); + keywordLiteral.setLineColumnNumber(lineNumber(), columnNumber()); + return keywordLiteral; case Token.TEMPLATE_LITERAL: consumeToken(); @@ -3348,7 +3405,7 @@ private AstNode parenExpr() throws IOException { inForInit = false; try { Comment jsdocNode = getAndResetJsDoc(); - int lineno = ts.lineno; + int lineno = lineNumber(), column = columnNumber(); int begin = ts.tokenBeg; AstNode e = (peekToken() == Token.RP ? new EmptyExpression(begin) : expr(true)); if (peekToken() == Token.FOR) { @@ -3368,7 +3425,7 @@ && peekToken() != Token.ARROW) { } ParenthesizedExpression pn = new ParenthesizedExpression(begin, length, e); - pn.setLineno(lineno); + pn.setLineColumnNumber(lineno, column); if (jsdocNode == null) { jsdocNode = getAndResetJsDoc(); } @@ -3386,19 +3443,19 @@ && peekToken() != Token.ARROW) { private AstNode name(int ttFlagged, int tt) throws IOException { String nameString = ts.getString(); - int namePos = ts.tokenBeg, nameLineno = ts.lineno; + int namePos = ts.tokenBeg, nameLineno = lineNumber(), nameColumn = columnNumber(); if (0 != (ttFlagged & TI_CHECK_LABEL) && peekToken() == Token.COLON) { // Do not consume colon. It is used as an unwind indicator // to return to statementHelper. Label label = new Label(namePos, ts.tokenEnd - namePos); label.setName(nameString); - label.setLineno(ts.lineno); + label.setLineColumnNumber(lineNumber(), columnNumber()); return label; } // Not a label. Unfortunately peeking the next token to check for // a colon has biffed ts.tokenBeg, ts.tokenEnd. We store the name's // bounds in instance vars and createNameNode uses them. - saveNameTokenData(namePos, nameString, nameLineno); + saveNameTokenData(namePos, nameString, nameLineno, nameColumn); if (compilerEnv.isXmlAvailable()) { return propertyName(-1, 0); @@ -3409,7 +3466,7 @@ private AstNode name(int ttFlagged, int tt) throws IOException { /** May return an {@link ArrayLiteral} or {@link ArrayComprehension}. */ private AstNode arrayLiteral() throws IOException { if (currentToken != Token.LB) codeBug(); - int pos = ts.tokenBeg, end = ts.tokenEnd; + int pos = ts.tokenBeg, end = ts.tokenEnd, lineno = lineNumber(), column = columnNumber(); List elements = new ArrayList<>(); ArrayLiteral pn = new ArrayLiteral(pos); boolean after_lb_or_comma = true; @@ -3458,6 +3515,7 @@ private AstNode arrayLiteral() throws IOException { pn.addElement(e); } pn.setLength(end - pos); + pn.setLineColumnNumber(lineno, column); return pn; } @@ -3657,7 +3715,7 @@ private GeneratorExpressionLoop generatorExpressionLoop() throws IOException { private static final int METHOD_ENTRY = 8; private ObjectLiteral objectLiteral() throws IOException { - int pos = ts.tokenBeg, lineno = ts.lineno; + int pos = ts.tokenBeg, lineno = lineNumber(), column = columnNumber(); int afterComma = -1; List elems = new ArrayList<>(); Set getterNames = null; @@ -3689,6 +3747,10 @@ private ObjectLiteral objectLiteral() throws IOException { propertyName = ts.getString(); int ppos = ts.tokenBeg; consumeToken(); + if (pname instanceof Name || pname instanceof StringLiteral) { + // For complicated reasons, parsing a name does not advance the token + pname.setLineColumnNumber(lineNumber(), columnNumber()); + } // This code path needs to handle both destructuring object // literals like: @@ -3790,7 +3852,7 @@ private ObjectLiteral objectLiteral() throws IOException { pn.setJsDocNode(objJsdocNode); } pn.setElements(elems); - pn.setLineno(lineno); + pn.setLineColumnNumber(lineno, column); return pn; } @@ -3814,8 +3876,9 @@ private AstNode objliteralProperty() throws IOException { case Token.LB: if (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) { int pos = ts.tokenBeg; - int lineno = ts.lineno; nextToken(); + int lineno = lineNumber(); + int column = columnNumber(); AstNode expr = assignExpr(); if (peekToken() != Token.RB) { reportError("msg.bad.prop"); @@ -3823,7 +3886,7 @@ private AstNode objliteralProperty() throws IOException { nextToken(); pname = new ComputedPropertyKey(pos, ts.tokenEnd - pos); - pname.setLineno(lineno); + pname.setLineColumnNumber(lineno, column); ((ComputedPropertyKey) pname).setExpression(expr); } else { reportError("msg.bad.prop"); @@ -3922,14 +3985,17 @@ private Name createNameNode() { private Name createNameNode(boolean checkActivation, int token) { int beg = ts.tokenBeg; String s = ts.getString(); - int lineno = ts.lineno; + int lineno = lineNumber(); + int column = columnNumber(); if (!"".equals(prevNameTokenString)) { beg = prevNameTokenStart; s = prevNameTokenString; lineno = prevNameTokenLineno; + column = prevNameTokenColumn; prevNameTokenStart = 0; prevNameTokenString = ""; prevNameTokenLineno = 0; + prevNameTokenColumn = 0; } if (s == null) { if (compilerEnv.isIdeMode()) { @@ -3939,7 +4005,7 @@ private Name createNameNode(boolean checkActivation, int token) { } } Name name = new Name(beg, s); - name.setLineno(lineno); + name.setLineColumnNumber(lineno, column); if (checkActivation) { checkActivationName(s, token); } @@ -3949,7 +4015,7 @@ private Name createNameNode(boolean checkActivation, int token) { private StringLiteral createStringLiteral() { int pos = ts.tokenBeg, end = ts.tokenEnd; StringLiteral s = new StringLiteral(pos, end - pos); - s.setLineno(ts.lineno); + s.setLineColumnNumber(lineNumber(), columnNumber()); s.setValue(ts.getString()); s.setQuoteCharacter(ts.getQuoteChar()); return s; @@ -3957,7 +4023,7 @@ private StringLiteral createStringLiteral() { private AstNode templateLiteral(boolean isTaggedLiteral) throws IOException { if (currentToken != Token.TEMPLATE_LITERAL) codeBug(); - int pos = ts.tokenBeg, end = ts.tokenEnd; + int pos = ts.tokenBeg, end = ts.tokenEnd, lineno = lineNumber(), column = columnNumber(); List elements = new ArrayList<>(); TemplateLiteral pn = new TemplateLiteral(pos); @@ -3978,6 +4044,7 @@ private AstNode templateLiteral(boolean isTaggedLiteral) throws IOException { end = ts.tokenEnd; pn.setElements(elements); pn.setLength(end - pos); + pn.setLineColumnNumber(lineno, column); return pn; } @@ -4011,11 +4078,15 @@ private AstNode createNumericLiteral(int tt, boolean isProperty) { s = "0x" + s; } } + + AstNode result; if (tt == Token.BIGINT) { - return new BigIntLiteral(ts.tokenBeg, s + "n", ts.getBigInt()); + result = new BigIntLiteral(ts.tokenBeg, s + "n", ts.getBigInt()); } else { - return new NumberLiteral(ts.tokenBeg, s, ts.getNumber()); + result = new NumberLiteral(ts.tokenBeg, s, ts.getNumber()); } + result.setLineColumnNumber(lineNumber(), columnNumber()); + return result; } protected void checkActivationName(String name, int token) { @@ -4075,7 +4146,7 @@ private void checkBadIncDec(UpdateExpression expr) { private ErrorNode makeErrorNode() { ErrorNode pn = new ErrorNode(ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); - pn.setLineno(ts.lineno); + pn.setLineColumnNumber(lineNumber(), columnNumber()); return pn; } @@ -4084,10 +4155,11 @@ private static int nodeEnd(AstNode node) { return node.getPosition() + node.getLength(); } - private void saveNameTokenData(int pos, String name, int lineno) { + private void saveNameTokenData(int pos, String name, int lineno, int column) { prevNameTokenStart = pos; prevNameTokenString = name; prevNameTokenLineno = lineno; + prevNameTokenColumn = column; } /** @@ -4235,7 +4307,7 @@ Node destructuringAssignmentHelper( String tempName, AstNode defaultValue, Transformer transformer) { - Scope result = createScopeNode(Token.LETEXPR, left.getLineno()); + Scope result = createScopeNode(Token.LETEXPR, left.getLineno(), left.getColumn()); result.addChildToFront(new Node(Token.LET, createName(Token.NAME, tempName, right))); try { pushScope(result); @@ -4454,12 +4526,13 @@ boolean destructuringObject( boolean defaultValuesSetup = false; for (ObjectProperty prop : node.getElements()) { - int lineno = 0; + int lineno = 0, column = 0; // This function is sometimes called from the IRFactory // when executing regression tests, and in those cases the // tokenStream isn't set. Deal with it. if (ts != null) { - lineno = ts.lineno; + lineno = lineNumber(); + column = columnNumber(); } AstNode id = prop.getLeft(); @@ -4480,7 +4553,7 @@ boolean destructuringObject( throw codeBug(); } - rightElem.setLineno(lineno); + rightElem.setLineColumnNumber(lineno, column); if (defaultValue != null && !defaultValuesSetup) { setupDefaultValues(tempName, parent, defaultValue, setOp, transformer); defaultValuesSetup = true; @@ -4543,10 +4616,10 @@ protected Node createNumber(double number) { * @param lineno line number of source * @return the created node */ - protected Scope createScopeNode(int token, int lineno) { + protected Scope createScopeNode(int token, int lineno, int column) { Scope scope = new Scope(); scope.setType(token); - scope.setLineno(lineno); + scope.setLineColumnNumber(lineno, column); return scope; } diff --git a/rhino/src/main/java/org/mozilla/javascript/TokenStream.java b/rhino/src/main/java/org/mozilla/javascript/TokenStream.java index 07a51ac940..f133ac8f31 100644 --- a/rhino/src/main/java/org/mozilla/javascript/TokenStream.java +++ b/rhino/src/main/java/org/mozilla/javascript/TokenStream.java @@ -606,6 +606,10 @@ public int getLineno() { return lineno; } + public int getTokenStartLineno() { + return tokenStartLineno; + } + final String getString() { return string; } @@ -650,11 +654,15 @@ final int getToken() throws IOException { for (; ; ) { c = getChar(); if (c == EOF_CHAR) { + tokenStartLastLineEnd = lastLineEnd; + tokenStartLineno = lineno; tokenBeg = cursor - 1; tokenEnd = cursor; return Token.EOF; } else if (c == '\n') { dirtyLine = false; + tokenStartLastLineEnd = lastLineEnd; + tokenStartLineno = lineno; tokenBeg = cursor - 1; tokenEnd = cursor; return Token.EOL; @@ -667,6 +675,8 @@ final int getToken() throws IOException { } // Assume the token will be 1 char - fixed up below. + tokenStartLastLineEnd = lastLineEnd; + tokenStartLineno = lineno; tokenBeg = cursor - 1; tokenEnd = cursor; @@ -1233,6 +1243,8 @@ && matchChar('.')) { if (matchChar('!')) { if (matchChar('-')) { if (matchChar('-')) { + tokenStartLastLineEnd = lastLineEnd; + tokenStartLineno = lineno; tokenBeg = cursor - 4; skipLine(); commentType = Token.CommentType.HTML; @@ -1289,6 +1301,8 @@ && matchChar('.')) { markCommentStart(); // is it a // comment? if (matchChar('/')) { + tokenStartLastLineEnd = lastLineEnd; + tokenStartLineno = lineno; tokenBeg = cursor - 2; skipLine(); commentType = Token.CommentType.LINE; @@ -1297,6 +1311,8 @@ && matchChar('.')) { // is it a /* or /** comment? if (matchChar('*')) { boolean lookForSlash = false; + tokenStartLastLineEnd = lastLineEnd; + tokenStartLineno = lineno; tokenBeg = cursor - 2; if (matchChar('*')) { lookForSlash = true; @@ -1842,6 +1858,8 @@ int getFirstXMLToken() throws IOException { } int getNextXMLToken() throws IOException { + tokenStartLastLineEnd = lastLineEnd; + tokenStartLineno = lineno; tokenBeg = cursor; stringBufferTop = 0; // remember the XML @@ -2180,6 +2198,7 @@ private int getChar(boolean skipFormattingChars, boolean ignoreLineEnd) throws I } lineEndChar = -1; lineStart = sourceCursor - 1; + lastLineEnd = tokenEnd; lineno++; } @@ -2432,6 +2451,10 @@ public int getLength() { return tokenEnd - tokenBeg; } + public int getTokenColumn() { + return tokenBeg - tokenStartLastLineEnd + 1; + } + // stuff other than whitespace since start of line private boolean dirtyLine; @@ -2484,6 +2507,10 @@ public int getLength() { int tokenBeg; int tokenEnd; + private int lastLineEnd; + private int tokenStartLastLineEnd; + private int tokenStartLineno; + // Type of last comment scanned. Token.CommentType commentType; diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/AstNode.java b/rhino/src/main/java/org/mozilla/javascript/ast/AstNode.java index a7d4284844..74187eef60 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/AstNode.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/AstNode.java @@ -69,7 +69,7 @@ public abstract class AstNode extends Node implements Comparable { * and so on */ protected AstNode inlineComment; - private static Map operatorNames = new HashMap<>(); + private static final Map operatorNames = new HashMap<>(); private static final int MAX_INDENT = 42; private static final String[] INDENTATIONS = new String[MAX_INDENT + 1]; diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ElementGet.java b/rhino/src/main/java/org/mozilla/javascript/ast/ElementGet.java index 19e366c724..6c301f579b 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/ElementGet.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ElementGet.java @@ -59,6 +59,7 @@ public void setTarget(AstNode target) { assertNotNull(target); this.target = target; target.setParent(this); + setLineColumnNumber(target.getLineno(), target.getColumn()); } /** Returns the element being accessed */ diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/ExpressionStatement.java b/rhino/src/main/java/org/mozilla/javascript/ast/ExpressionStatement.java index 475941548b..b70cf46895 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/ExpressionStatement.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/ExpressionStatement.java @@ -84,7 +84,7 @@ public void setExpression(AstNode expression) { assertNotNull(expression); expr = expression; expression.setParent(this); - setLineno(expression.getLineno()); + setLineColumnNumber(expression.getLineno(), expr.getColumn()); } /** diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/FunctionCall.java b/rhino/src/main/java/org/mozilla/javascript/ast/FunctionCall.java index d25efae079..1358980da4 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/FunctionCall.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/FunctionCall.java @@ -51,6 +51,7 @@ public void setTarget(AstNode target) { assertNotNull(target); this.target = target; target.setParent(this); + setLineColumnNumber(target.getLineno(), target.getColumn()); } /** diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/InfixExpression.java b/rhino/src/main/java/org/mozilla/javascript/ast/InfixExpression.java index afdde7c84f..7be56955d4 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/InfixExpression.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/InfixExpression.java @@ -94,8 +94,8 @@ public AstNode getLeft() { public void setLeft(AstNode left) { assertNotNull(left); this.left = left; - // line number should agree with source position - setLineno(left.getLineno()); + // line and column number should agree with source position + setLineColumnNumber(left.getLineno(), left.getColumn()); left.setParent(this); } diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/Jump.java b/rhino/src/main/java/org/mozilla/javascript/ast/Jump.java index 4fd4939e2e..dbf183747a 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/Jump.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/Jump.java @@ -29,21 +29,11 @@ public Jump(int nodeType) { type = nodeType; } - public Jump(int type, int lineno) { - this(type); - setLineno(lineno); - } - public Jump(int type, Node child) { this(type); addChildToBack(child); } - public Jump(int type, Node child, int lineno) { - this(type, child); - setLineno(lineno); - } - public Jump getJumpStatement() { if (type != Token.BREAK && type != Token.CONTINUE) codeBug(); return jumpNode; diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/PropertyGet.java b/rhino/src/main/java/org/mozilla/javascript/ast/PropertyGet.java index 4f67a84354..d534d505c4 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/PropertyGet.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/PropertyGet.java @@ -35,10 +35,12 @@ public PropertyGet(int pos, int len, AstNode target, Name property) { */ public PropertyGet(AstNode target, Name property) { super(target, property); + super.setLineColumnNumber(property.getLineno(), property.getColumn()); } public PropertyGet(AstNode target, Name property, int dotPosition) { super(Token.GETPROP, target, property, dotPosition); + super.setLineColumnNumber(property.getLineno(), property.getColumn()); } /** Returns the object on which the property is being fetched. Should never be {@code null}. */ diff --git a/tests/src/test/java/org/mozilla/javascript/tests/ParserLineColumnNumberTest.java b/tests/src/test/java/org/mozilla/javascript/tests/ParserLineColumnNumberTest.java new file mode 100644 index 0000000000..b800ce62c5 --- /dev/null +++ b/tests/src/test/java/org/mozilla/javascript/tests/ParserLineColumnNumberTest.java @@ -0,0 +1,1042 @@ +package org.mozilla.javascript.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mozilla.javascript.CompilerEnvirons; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Node; +import org.mozilla.javascript.ast.ArrayLiteral; +import org.mozilla.javascript.ast.Assignment; +import org.mozilla.javascript.ast.AstNode; +import org.mozilla.javascript.ast.AstRoot; +import org.mozilla.javascript.ast.BigIntLiteral; +import org.mozilla.javascript.ast.Block; +import org.mozilla.javascript.ast.BreakStatement; +import org.mozilla.javascript.ast.CatchClause; +import org.mozilla.javascript.ast.Comment; +import org.mozilla.javascript.ast.ComputedPropertyKey; +import org.mozilla.javascript.ast.ConditionalExpression; +import org.mozilla.javascript.ast.ContinueStatement; +import org.mozilla.javascript.ast.DoLoop; +import org.mozilla.javascript.ast.ElementGet; +import org.mozilla.javascript.ast.EmptyStatement; +import org.mozilla.javascript.ast.ExpressionStatement; +import org.mozilla.javascript.ast.ForInLoop; +import org.mozilla.javascript.ast.ForLoop; +import org.mozilla.javascript.ast.FunctionCall; +import org.mozilla.javascript.ast.FunctionNode; +import org.mozilla.javascript.ast.IfStatement; +import org.mozilla.javascript.ast.InfixExpression; +import org.mozilla.javascript.ast.KeywordLiteral; +import org.mozilla.javascript.ast.LabeledStatement; +import org.mozilla.javascript.ast.Name; +import org.mozilla.javascript.ast.NewExpression; +import org.mozilla.javascript.ast.NumberLiteral; +import org.mozilla.javascript.ast.ObjectLiteral; +import org.mozilla.javascript.ast.ObjectProperty; +import org.mozilla.javascript.ast.ParenthesizedExpression; +import org.mozilla.javascript.ast.PropertyGet; +import org.mozilla.javascript.ast.RegExpLiteral; +import org.mozilla.javascript.ast.ReturnStatement; +import org.mozilla.javascript.ast.Scope; +import org.mozilla.javascript.ast.StringLiteral; +import org.mozilla.javascript.ast.SwitchCase; +import org.mozilla.javascript.ast.SwitchStatement; +import org.mozilla.javascript.ast.TaggedTemplateLiteral; +import org.mozilla.javascript.ast.ThrowStatement; +import org.mozilla.javascript.ast.TryStatement; +import org.mozilla.javascript.ast.UnaryExpression; +import org.mozilla.javascript.ast.UpdateExpression; +import org.mozilla.javascript.ast.VariableDeclaration; +import org.mozilla.javascript.ast.VariableInitializer; +import org.mozilla.javascript.ast.WhileLoop; +import org.mozilla.javascript.ast.WithStatement; +import org.mozilla.javascript.ast.Yield; + +class ParserLineColumnNumberTest { + CompilerEnvirons environment; + + @BeforeEach + public void setUp() throws Exception { + environment = new CompilerEnvirons(); + } + + @Test + void declarationVar() { + AstRoot root = myParse("var a =\n1;"); + VariableDeclaration varStatement = + assertInstanceOf(VariableDeclaration.class, root.getFirstChild()); + assertLineColumnAre(varStatement, 0, 1); + assertEquals(1, varStatement.getVariables().size()); + VariableInitializer varInitializer = varStatement.getVariables().get(0); + assertLineColumnAre(varInitializer.getTarget(), 0, 5); + assertLineColumnAre(varInitializer.getInitializer(), 1, 1); + } + + @Test + void declarationConst() { + AstRoot root = myParse("const a =\n1;"); + VariableDeclaration varStatement = + assertInstanceOf(VariableDeclaration.class, root.getFirstChild()); + assertLineColumnAre(varStatement, 0, 1); + assertEquals(1, varStatement.getVariables().size()); + VariableInitializer varInitializer = varStatement.getVariables().get(0); + assertLineColumnAre(varInitializer.getTarget(), 0, 7); + assertLineColumnAre(varInitializer.getInitializer(), 1, 1); + } + + @Test + void declarationLet() { + AstRoot root = myParse("let a =\n1;"); + VariableDeclaration varStatement = + assertInstanceOf(VariableDeclaration.class, root.getFirstChild()); + assertLineColumnAre(varStatement, 0, 1); + assertEquals(1, varStatement.getVariables().size()); + VariableInitializer varInitializer = varStatement.getVariables().get(0); + assertLineColumnAre(varInitializer.getTarget(), 0, 5); + assertLineColumnAre(varInitializer.getInitializer(), 1, 1); + } + + @Test + void controlFlowIf() { + AstRoot root = myParse("if (a) {\n" + " b\n" + "} else {\n" + " c;\n" + "}"); + IfStatement ifStatement = assertInstanceOf(IfStatement.class, root.getFirstChild()); + assertLineColumnAre(ifStatement, 0, 1); + assertLineColumnAre(ifStatement.getCondition(), 0, 5); + + Scope thenPart = assertInstanceOf(Scope.class, ifStatement.getThenPart()); + assertLineColumnAre(thenPart, 0, 8); + assertLineColumnAre(thenPart.getStatements().get(0), 1, 3); + + Scope elsePart = assertInstanceOf(Scope.class, ifStatement.getElsePart()); + assertLineColumnAre(elsePart, 2, 8); + assertLineColumnAre(elsePart.getStatements().get(0), 3, 3); + } + + @Test + void controlFlowSwitch() { + AstRoot root = + myParse( + "switch (a) {\n" + + " case 1: break;\n" + + " case 2: {\n" + + " f();\n" + + " }\n" + + " default:\n" + + " g();\n" + + "}"); + SwitchStatement switchStatement = + assertInstanceOf(SwitchStatement.class, root.getFirstChild()); + assertLineColumnAre(switchStatement, 0, 1); + assertLineColumnAre(switchStatement.getExpression(), 0, 9); + assertEquals(3, switchStatement.getCases().size()); + + SwitchCase case1 = switchStatement.getCases().get(0); + assertLineColumnAre(case1, 1, 3); + assertLineColumnAre(case1.getExpression(), 1, 8); + assertInstanceOf(BreakStatement.class, case1.getStatements().get(0)); + assertLineColumnAre(case1.getStatements().get(0), 1, 11); + + SwitchCase case2 = switchStatement.getCases().get(1); + assertLineColumnAre(case2, 2, 3); + assertLineColumnAre(case2.getExpression(), 2, 8); + assertInstanceOf(Scope.class, case2.getStatements().get(0)); + assertLineColumnAre(case2.getStatements().get(0), 2, 11); + + SwitchCase caseDefault = switchStatement.getCases().get(2); + assertLineColumnAre(caseDefault, 5, 3); + assertTrue(caseDefault.isDefault()); + assertNull(caseDefault.getExpression()); + } + + @Test + void controlFlowDo() { + AstRoot root = myParse("" + "do {\n" + " break;\n" + "} while (cond)"); + DoLoop doStatement = assertInstanceOf(DoLoop.class, root.getFirstChild()); + assertLineColumnAre(doStatement, 0, 1); + + Scope body = assertInstanceOf(Scope.class, doStatement.getBody()); + assertLineColumnAre(body, 0, 4); + + assertEquals(1, body.getStatements().size()); + BreakStatement breakStatement = + assertInstanceOf(BreakStatement.class, body.getStatements().get(0)); + assertLineColumnAre(breakStatement, 1, 3); + + assertLineColumnAre(doStatement.getCondition(), 2, 10); + } + + @Test + void controlFlowWhile() { + AstRoot root = myParse("" + "while (cond) {\n" + " continue;\n" + "}"); + WhileLoop whileStatement = assertInstanceOf(WhileLoop.class, root.getFirstChild()); + assertLineColumnAre(whileStatement, 0, 1); + assertLineColumnAre(whileStatement.getCondition(), 0, 8); + + Scope body = assertInstanceOf(Scope.class, whileStatement.getBody()); + assertLineColumnAre(body, 0, 14); + + assertEquals(1, body.getStatements().size()); + ContinueStatement continueStatement = + assertInstanceOf(ContinueStatement.class, body.getStatements().get(0)); + assertLineColumnAre(continueStatement, 1, 3); + } + + @Test + void controlFlowFor() { + AstRoot root = myParse("" + "for (var i = 0; i < 10; ++i) {\n" + "}"); + ForLoop forStatement = assertInstanceOf(ForLoop.class, root.getFirstChild()); + assertLineColumnAre(forStatement, 0, 1); + assertLineColumnAre(forStatement.getInitializer(), 0, 6); + assertLineColumnAre(forStatement.getCondition(), 0, 17); + assertLineColumnAre(forStatement.getIncrement(), 0, 25); + Scope body = assertInstanceOf(Scope.class, forStatement.getBody()); + assertLineColumnAre(body, 0, 30); + } + + @Test + void controlFlowForIn() { + AstRoot root = myParse("" + "for (let key in obj) {\n" + "}"); + ForInLoop forStatement = assertInstanceOf(ForInLoop.class, root.getFirstChild()); + assertFalse(forStatement.isForOf()); + assertLineColumnAre(forStatement, 0, 1); + assertLineColumnAre(forStatement.getIterator(), 0, 6); + assertLineColumnAre(forStatement.getIteratedObject(), 0, 17); + Scope body = assertInstanceOf(Scope.class, forStatement.getBody()); + assertLineColumnAre(body, 0, 22); + } + + @Test + void controlFlowForOf() { + AstRoot root = myParse("" + "for (var key of obj) {\n" + "}"); + ForInLoop forStatement = assertInstanceOf(ForInLoop.class, root.getFirstChild()); + assertTrue(forStatement.isForOf()); + assertLineColumnAre(forStatement, 0, 1); + assertLineColumnAre(forStatement.getIterator(), 0, 6); + assertLineColumnAre(forStatement.getIteratedObject(), 0, 17); + Scope body = assertInstanceOf(Scope.class, forStatement.getBody()); + assertLineColumnAre(body, 0, 22); + } + + @Test + void controlFlowTryCatchFinally() { + AstRoot root = + myParse( + "" + + "try {\n" + + " f();\n" + + "} catch (\n" + + " err) {\n" + + " g(err);\n" + + "} finally {\n" + + " i();\n" + + "}\n"); + TryStatement tryStatement = assertInstanceOf(TryStatement.class, root.getFirstChild()); + assertLineColumnAre(tryStatement, 0, 1); + Scope tryBlock = assertInstanceOf(Scope.class, tryStatement.getTryBlock()); + assertLineColumnAre(tryBlock, 0, 5); + + assertEquals(1, tryStatement.getCatchClauses().size()); + CatchClause catchClause = tryStatement.getCatchClauses().get(0); + assertLineColumnAre(catchClause, 2, 3); + assertLineColumnAre(catchClause.getVarName(), 3, 3); + assertLineColumnAre(catchClause.getBody(), 3, 8); + + assertNotNull(tryStatement.getFinallyBlock()); + assertLineColumnAre(tryStatement.getFinallyBlock(), 5, 11); + } + + @Test + void controlFlowTryCatchNoVar() { + AstRoot root = + myParse( + "" + + "try {\n" + + " throw /* comment */ 'err';\n" + + "} catch {\n" + + " g(err);\n" + + "}\n"); + + TryStatement tryStatement = assertInstanceOf(TryStatement.class, root.getFirstChild()); + assertLineColumnAre(tryStatement, 0, 1); + Scope tryBlock = assertInstanceOf(Scope.class, tryStatement.getTryBlock()); + assertLineColumnAre(tryBlock, 0, 5); + assertEquals(1, tryBlock.getStatements().size()); + ThrowStatement throwStatement = + assertInstanceOf(ThrowStatement.class, tryBlock.getStatements().get(0)); + assertLineColumnAre(throwStatement, 1, 3); + assertNotNull(throwStatement.getExpression()); + assertLineColumnAre(throwStatement.getExpression(), 1, 23); + + assertEquals(1, tryStatement.getCatchClauses().size()); + CatchClause catchClause = tryStatement.getCatchClauses().get(0); + assertLineColumnAre(catchClause, 2, 3); + assertNull(catchClause.getVarName()); + assertLineColumnAre(catchClause.getBody(), 2, 9); + + assertNull(tryStatement.getFinallyBlock()); + } + + @Test + void controlFlowLabels() { + AstRoot root = + myParse( + "" + + "l1: while (true) {\n" + + " l2: for (var i in obj)\n" + + " break l2;\n" + + " continue l1;\n" + + "}\n"); + + LabeledStatement l1Statement = + assertInstanceOf(LabeledStatement.class, root.getFirstChild()); + assertLineColumnAre(l1Statement, 0, 1); + assertEquals(1, l1Statement.getLabels().size()); + assertLineColumnAre(l1Statement.getLabels().get(0), 0, 1); + + WhileLoop whileLoop = assertInstanceOf(WhileLoop.class, l1Statement.getStatement()); + assertLineColumnAre(whileLoop, 0, 5); + Scope whileBody = assertInstanceOf(Scope.class, whileLoop.getBody()); + assertLineColumnAre(whileBody, 0, 18); + + assertEquals(2, whileBody.getStatements().size()); + LabeledStatement l2Statement = + assertInstanceOf(LabeledStatement.class, whileBody.getStatements().get(0)); + assertLineColumnAre(l2Statement, 1, 3); + assertEquals(1, l2Statement.getLabels().size()); + assertLineColumnAre(l2Statement.getLabels().get(0), 1, 3); + + ForInLoop forLoop = assertInstanceOf(ForInLoop.class, l2Statement.getStatement()); + assertLineColumnAre(forLoop, 1, 7); + + BreakStatement breakStatement = assertInstanceOf(BreakStatement.class, forLoop.getBody()); + assertLineColumnAre(breakStatement, 2, 5); + assertNotNull(breakStatement.getBreakLabel()); + assertLineColumnAre(breakStatement.getBreakLabel(), 2, 11); + + ContinueStatement continueStatement = + assertInstanceOf(ContinueStatement.class, whileBody.getStatements().get(1)); + assertLineColumnAre(continueStatement, 3, 3); + assertNotNull(continueStatement.getLabel()); + assertLineColumnAre(continueStatement.getLabel(), 3, 12); + } + + @Test + void statementWith() { + AstRoot root = myParse(" with (a) {\n" + "}\n"); + + WithStatement withStatement = assertInstanceOf(WithStatement.class, root.getFirstChild()); + assertLineColumnAre(withStatement, 0, 2); + assertLineColumnAre(withStatement.getExpression(), 0, 8); + Scope block = assertInstanceOf(Scope.class, withStatement.getStatement()); + assertLineColumnAre(block, 0, 11); + } + + @Test + void statementDebugger() { + AstRoot root = myParse(" debugger"); + + KeywordLiteral debuggerStatement = + assertInstanceOf(KeywordLiteral.class, root.getFirstChild()); + assertLineColumnAre(debuggerStatement, 0, 2); + } + + @Test + void statementEmpty() { + AstRoot root = myParse(" ;"); + + EmptyStatement emptyStatement = + assertInstanceOf(EmptyStatement.class, root.getFirstChild()); + assertLineColumnAre(emptyStatement, 0, 2); + } + + @Test + void functionDeclarationNoArgs() { + AstRoot root = myParse("" + "function f() {\n" + " return a();\n" + "}"); + + FunctionNode functionDecl = assertInstanceOf(FunctionNode.class, root.getFirstChild()); + assertLineColumnAre(functionDecl, 0, 1); + assertNotNull(functionDecl.getFunctionName()); + assertLineColumnAre(functionDecl.getFunctionName(), 0, 10); + Block body = assertInstanceOf(Block.class, functionDecl.getBody()); + assertLineColumnAre(body, 0, 14); + ReturnStatement returnStatement = + assertInstanceOf(ReturnStatement.class, body.getFirstChild()); + assertLineColumnAre(returnStatement, 1, 3); + assertNotNull(returnStatement.getReturnValue()); + FunctionCall returnValue = + assertInstanceOf(FunctionCall.class, returnStatement.getReturnValue()); + assertLineColumnAre(returnValue, 1, 10); + } + + @Test + void functionDeclarationWithArgs() { + AstRoot root = myParse(" function /*comment*/ \nf( a, /* comment */ b) {}"); + + FunctionNode functionDecl = assertInstanceOf(FunctionNode.class, root.getFirstChild()); + assertLineColumnAre(functionDecl, 0, 2); + assertNotNull(functionDecl.getFunctionName()); + assertLineColumnAre(functionDecl.getFunctionName(), 1, 1); + Block body = assertInstanceOf(Block.class, functionDecl.getBody()); + assertLineColumnAre(body, 1, 24); + + assertEquals(2, functionDecl.getParams().size()); + Name firstParam = assertInstanceOf(Name.class, functionDecl.getParams().get(0)); + assertLineColumnAre(firstParam, 1, 4); + Name secondParam = assertInstanceOf(Name.class, functionDecl.getParams().get(1)); + assertLineColumnAre(secondParam, 1, 21); + } + + @Test + void functionDeclarationDirectives() { + AstRoot root = myParse("function a() {\n" + " 'use strict';\n" + " return 42;\n" + "}"); + FunctionNode fun = assertInstanceOf(FunctionNode.class, root.getFirstChild()); + assertLineColumnAre(fun, 0, 1); + Block aBody = assertInstanceOf(Block.class, fun.getBody()); + assertLineColumnAre(aBody, 0, 14); + ExpressionStatement statement = + assertInstanceOf(ExpressionStatement.class, aBody.getFirstChild()); + assertLineColumnAre(statement, 1, 3); + StringLiteral directive = assertInstanceOf(StringLiteral.class, statement.getExpression()); + assertLineColumnAre(directive, 1, 3); + ReturnStatement returnStatement = + assertInstanceOf(ReturnStatement.class, statement.getNext()); + assertLineColumnAre(returnStatement, 2, 3); + } + + @Test + void functionDeclarationRest() { + AstRoot root = myParse("function b(... /* a comment */ rest) {}"); + FunctionNode fun = assertInstanceOf(FunctionNode.class, root.getFirstChild()); + assertLineColumnAre(fun, 0, 1); + assertEquals(1, fun.getParams().size()); + Name restParam = assertInstanceOf(Name.class, fun.getParams().get(0)); + assertLineColumnAre(restParam, 0, 12); // Start of the dots + } + + @Test + void functionDeclarationDefaultArgs() { + AstRoot root = myParse("function b(a =42) {}"); + FunctionNode fun = assertInstanceOf(FunctionNode.class, root.getFirstChild()); + assertLineColumnAre(fun, 0, 1); + assertEquals(1, fun.getParams().size()); + Name paramA = assertInstanceOf(Name.class, fun.getParams().get(0)); + assertLineColumnAre(paramA, 0, 12); + assertEquals(2, fun.getDefaultParams().size()); + assertEquals("a", fun.getDefaultParams().get(0)); + NumberLiteral paramADefaultValue = + assertInstanceOf(NumberLiteral.class, fun.getDefaultParams().get(1)); + assertLineColumnAre(paramADefaultValue, 0, 16); + } + + @Test + void functionDeclarationDestructuring() { + AstRoot root = myParse("function f({props}) {}"); + FunctionNode fun = assertInstanceOf(FunctionNode.class, root.getFirstChild()); + assertLineColumnAre(fun, 0, 1); + assertEquals(1, fun.getParams().size()); + ObjectLiteral param = assertInstanceOf(ObjectLiteral.class, fun.getParams().get(0)); + assertLineColumnAre(param, 0, 12); + } + + @Test + void generatorDeclaration() { + AstRoot root = myParse("" + " function* f() {\n" + " yield 1;\n" + " return a;\n" + "}"); + FunctionNode generatorDecl = assertInstanceOf(FunctionNode.class, root.getFirstChild()); + assertLineColumnAre(generatorDecl, 0, 2); + assertTrue(generatorDecl.isGenerator()); + assertTrue(generatorDecl.isES6Generator()); + assertNotNull(generatorDecl.getFunctionName()); + assertLineColumnAre(generatorDecl.getFunctionName(), 0, 12); + Block body = assertInstanceOf(Block.class, generatorDecl.getBody()); + assertLineColumnAre(body, 0, 16); + + ExpressionStatement firstStatement = + assertInstanceOf(ExpressionStatement.class, body.getFirstChild()); + assertLineColumnAre(firstStatement, 1, 3); + Yield yield = assertInstanceOf(Yield.class, firstStatement.getExpression()); + assertLineColumnAre(yield, 1, 3); + assertNotNull(yield.getValue()); + assertLineColumnAre(yield.getValue(), 1, 9); + + ReturnStatement returnStatement = + assertInstanceOf(ReturnStatement.class, firstStatement.getNext()); + assertLineColumnAre(returnStatement, 2, 3); + assertNotNull(returnStatement.getReturnValue()); + assertLineColumnAre(returnStatement.getReturnValue(), 2, 10); + } + + @Test + void expressionArrowFunction() { + AstRoot root = myParse(" (a=1) /* comment */ =>\n3;"); + ExpressionStatement expressionStatement = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(expressionStatement, 0, 2); + FunctionNode arrowFunction = + assertInstanceOf(FunctionNode.class, expressionStatement.getExpression()); + assertLineColumnAre(arrowFunction, 0, 2); + + assertEquals(1, arrowFunction.getParams().size()); + Name firstParam = assertInstanceOf(Name.class, arrowFunction.getParams().get(0)); + assertLineColumnAre(firstParam, 0, 3); + assertEquals(2, arrowFunction.getDefaultParams().size()); + assertEquals("a", arrowFunction.getDefaultParams().get(0)); + NumberLiteral firstParamDefaultValue = + assertInstanceOf(NumberLiteral.class, arrowFunction.getDefaultParams().get(1)); + assertLineColumnAre(firstParamDefaultValue, 0, 5); + + Block body = assertInstanceOf(Block.class, arrowFunction.getBody()); + assertLineColumnAre(body, 0, 22); + ReturnStatement statement = assertInstanceOf(ReturnStatement.class, body.getFirstChild()); + assertLineColumnAre(statement, 1, 1); + } + + @Test + void expressionName() { + AstRoot root = myParse(" a"); + ExpressionStatement nameExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(nameExpr, 0, 2); + Name name = assertInstanceOf(Name.class, nameExpr.getExpression()); + assertLineColumnAre(name, 0, 2); + } + + @Test + void expressionNameInBlock() { + AstRoot root = myParse(" {a}"); + Scope block = assertInstanceOf(Scope.class, root.getFirstChild()); + assertLineColumnAre(block, 0, 2); + assertEquals(1, block.getStatements().size()); + AstNode firstStatement = block.getStatements().get(0); + assertLineColumnAre(firstStatement, 0, 3); + } + + @Test + void expressionNumericLiteral() { + AstRoot root = myParse(" 42"); + ExpressionStatement expr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(expr, 0, 2); + NumberLiteral name = assertInstanceOf(NumberLiteral.class, expr.getExpression()); + assertLineColumnAre(name, 0, 2); + } + + @Test + void expressionBooleanLiteral() { + AstRoot root = myParse(" true"); + ExpressionStatement expr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(expr, 0, 2); + KeywordLiteral name = assertInstanceOf(KeywordLiteral.class, expr.getExpression()); + assertLineColumnAre(name, 0, 2); + } + + @Test + void expressionStringLiteral() { + AstRoot root = myParse(" 'x'"); + ExpressionStatement expr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(expr, 0, 2); + StringLiteral name = assertInstanceOf(StringLiteral.class, expr.getExpression()); + assertLineColumnAre(name, 0, 2); + } + + @Test + void expressionBigIntLiteral() { + AstRoot root = myParse(" 42n"); + ExpressionStatement expr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(expr, 0, 2); + BigIntLiteral name = assertInstanceOf(BigIntLiteral.class, expr.getExpression()); + assertLineColumnAre(name, 0, 2); + } + + @Test + void expressionRegexpLiteral() { + AstRoot root = myParse("/* c */ /regex/y "); + Comment comment = assertInstanceOf(Comment.class, root.getFirstChild()); + assertLineColumnAre(comment, 0, 1); + ExpressionStatement statement = + assertInstanceOf(ExpressionStatement.class, comment.getNext()); + assertLineColumnAre(statement, 0, 9); + RegExpLiteral infix = assertInstanceOf(RegExpLiteral.class, statement.getExpression()); + assertLineColumnAre(infix, 0, 9); + } + + @Test + void expressionUnaryIncrementPrefix() { + AstRoot root = myParse(" ++a;"); + ExpressionStatement expr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(expr, 0, 2); + UpdateExpression increment = assertInstanceOf(UpdateExpression.class, expr.getExpression()); + assertLineColumnAre(increment, 0, 2); + assertTrue(increment.isPrefix()); + assertFalse(increment.isPostfix()); + Name name = assertInstanceOf(Name.class, increment.getOperand()); + assertLineColumnAre(name, 0, 4); + } + + @Test + void expressionUnaryIncrementSuffix() { + AstRoot root = myParse(" a++;"); + ExpressionStatement expr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(expr, 0, 2); + UpdateExpression increment = assertInstanceOf(UpdateExpression.class, expr.getExpression()); + assertLineColumnAre(increment, 0, 2); + assertFalse(increment.isPrefix()); + assertTrue(increment.isPostfix()); + Name name = assertInstanceOf(Name.class, increment.getOperand()); + assertLineColumnAre(name, 0, 2); + } + + @Test + void expressionUnaryNot() { + AstRoot root = myParse(" !a;"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + UnaryExpression unaryExpr = + assertInstanceOf(UnaryExpression.class, firstExpr.getExpression()); + assertLineColumnAre(unaryExpr, 0, 2); + Name name = assertInstanceOf(Name.class, unaryExpr.getOperand()); + assertLineColumnAre(name, 0, 3); + } + + @Test + void expressionUnaryPlus() { + AstRoot root = myParse(" + a;"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + UnaryExpression unaryExpr = + assertInstanceOf(UnaryExpression.class, firstExpr.getExpression()); + assertLineColumnAre(unaryExpr, 0, 2); + Name name = assertInstanceOf(Name.class, unaryExpr.getOperand()); + assertLineColumnAre(name, 0, 5); + } + + @Test + void expressionUnaryMinus() { + AstRoot root = myParse(" - a;"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + UnaryExpression unaryExpr = + assertInstanceOf(UnaryExpression.class, firstExpr.getExpression()); + assertLineColumnAre(unaryExpr, 0, 2); + Name name = assertInstanceOf(Name.class, unaryExpr.getOperand()); + assertLineColumnAre(name, 0, 4); + } + + @Test + void expressionDeleteName() { + AstRoot root = myParse(" delete a;"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + UnaryExpression unaryExpr = + assertInstanceOf(UnaryExpression.class, firstExpr.getExpression()); + assertLineColumnAre(unaryExpr, 0, 2); + Name name = assertInstanceOf(Name.class, unaryExpr.getOperand()); + assertLineColumnAre(name, 0, 11); + } + + @Test + void expressionFunctionCallNoArg() { + AstRoot root = myParse(" a ()"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + FunctionCall functionCall = assertInstanceOf(FunctionCall.class, firstExpr.getExpression()); + assertLineColumnAre(functionCall, 0, 2); + Name name = assertInstanceOf(Name.class, functionCall.getTarget()); + assertLineColumnAre(name, 0, 2); + assertTrue(functionCall.getArguments().isEmpty()); + } + + @Test + void expressionFunctionCallWithArgs() { + AstRoot root = myParse("a(b, 42)"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 1); + FunctionCall functionCall = assertInstanceOf(FunctionCall.class, firstExpr.getExpression()); + assertLineColumnAre(functionCall, 0, 1); + Name name = assertInstanceOf(Name.class, functionCall.getTarget()); + assertLineColumnAre(name, 0, 1); + + assertEquals(2, functionCall.getArguments().size()); + Name firstArg = assertInstanceOf(Name.class, functionCall.getArguments().get(0)); + assertLineColumnAre(firstArg, 0, 3); + NumberLiteral secondArg = + assertInstanceOf(NumberLiteral.class, functionCall.getArguments().get(1)); + assertLineColumnAre(secondArg, 0, 7); + } + + @Test + void expressionIffe() { + AstRoot root = myParse(" (function() {})()"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + FunctionCall functionCall = assertInstanceOf(FunctionCall.class, firstExpr.getExpression()); + assertLineColumnAre(functionCall, 0, 2); + ParenthesizedExpression name = + assertInstanceOf(ParenthesizedExpression.class, functionCall.getTarget()); + assertLineColumnAre(name, 0, 2); + } + + @Test + void expressionMemberCall() { + AstRoot root = myParse(" a /* comment */. b ()"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + FunctionCall functionCall = assertInstanceOf(FunctionCall.class, firstExpr.getExpression()); + assertLineColumnAre(functionCall, 0, 2); + PropertyGet propGet = assertInstanceOf(PropertyGet.class, functionCall.getTarget()); + assertLineColumnAre(propGet, 0, 2); + Name target = assertInstanceOf(Name.class, propGet.getTarget()); + assertLineColumnAre(target, 0, 2); + Name property = assertInstanceOf(Name.class, propGet.getProperty()); + assertLineColumnAre(property, 0, 19); + } + + @Test + void expressionCallAfterCall() { + AstRoot root = myParse("a.b().c()"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 1); + FunctionCall callC = assertInstanceOf(FunctionCall.class, firstExpr.getExpression()); + assertLineColumnAre(callC, 0, 1); + PropertyGet getC = assertInstanceOf(PropertyGet.class, callC.getTarget()); + assertLineColumnAre(callC, 0, 1); + Name nameC = assertInstanceOf(Name.class, getC.getProperty()); + assertLineColumnAre(nameC, 0, 7); + FunctionCall callB = assertInstanceOf(FunctionCall.class, getC.getTarget()); + assertLineColumnAre(callB, 0, 1); + PropertyGet propGet = assertInstanceOf(PropertyGet.class, callB.getTarget()); + assertLineColumnAre(propGet, 0, 1); + Name nameB = assertInstanceOf(Name.class, propGet.getProperty()); + assertLineColumnAre(nameB, 0, 3); + Name nameA = assertInstanceOf(Name.class, propGet.getTarget()); + assertLineColumnAre(nameA, 0, 1); + } + + @Test + void expressionOptionalCall() { + AstRoot root = myParse(" a?.(x)"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + FunctionCall functionCall = assertInstanceOf(FunctionCall.class, firstExpr.getExpression()); + assertLineColumnAre(functionCall, 0, 2); + Name name = assertInstanceOf(Name.class, functionCall.getTarget()); + assertLineColumnAre(name, 0, 2); + assertEquals(1, functionCall.getArguments().size()); + Name property = assertInstanceOf(Name.class, functionCall.getArguments().get(0)); + assertLineColumnAre(property, 0, 6); + } + + @Test + void expressionGeneratorThrow() { + AstRoot root = myParse(" a = gen();\n a.throw()"); + ExpressionStatement statement1 = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(statement1, 0, 2); + + ExpressionStatement statement2 = + assertInstanceOf(ExpressionStatement.class, statement1.getNext()); + assertLineColumnAre(statement2, 1, 2); + FunctionCall functionCall = + assertInstanceOf(FunctionCall.class, statement2.getExpression()); + assertLineColumnAre(functionCall, 1, 2); + PropertyGet propertyGet = assertInstanceOf(PropertyGet.class, functionCall.getTarget()); + assertLineColumnAre(propertyGet, 1, 2); + assertLineColumnAre(propertyGet.getTarget(), 1, 2); + assertLineColumnAre(propertyGet.getProperty(), 1, 4); + } + + @Test + void expressionPropertyElem() { + AstRoot root = myParse(" a[ 1]"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + ElementGet propGet = assertInstanceOf(ElementGet.class, firstExpr.getExpression()); + assertLineColumnAre(propGet, 0, 2); + Name target = assertInstanceOf(Name.class, propGet.getTarget()); + assertLineColumnAre(target, 0, 2); + NumberLiteral property = assertInstanceOf(NumberLiteral.class, propGet.getElement()); + assertLineColumnAre(property, 0, 5); + } + + @Test + void expressionPropertyElemOptional() { + AstRoot root = myParse(" a?.[ 1]"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + ElementGet propGet = assertInstanceOf(ElementGet.class, firstExpr.getExpression()); + assertLineColumnAre(propGet, 0, 2); + Name target = assertInstanceOf(Name.class, propGet.getTarget()); + assertLineColumnAre(target, 0, 2); + NumberLiteral property = assertInstanceOf(NumberLiteral.class, propGet.getElement()); + assertLineColumnAre(property, 0, 7); + } + + @Test + void expressionInfixOperator() { + AstRoot root = myParse(" a + /*comment*/ 1"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + InfixExpression infix = assertInstanceOf(InfixExpression.class, firstExpr.getExpression()); + assertLineColumnAre(infix, 0, 2); + Name left = assertInstanceOf(Name.class, infix.getLeft()); + assertLineColumnAre(left, 0, 2); + NumberLiteral right = assertInstanceOf(NumberLiteral.class, infix.getRight()); + assertLineColumnAre(right, 0, 18); + } + + @Test + void statementAssignment() { + AstRoot root = myParse(" a |= 1"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + Assignment assignment = assertInstanceOf(Assignment.class, firstExpr.getExpression()); + assertLineColumnAre(assignment, 0, 2); + Name target = assertInstanceOf(Name.class, assignment.getLeft()); + assertLineColumnAre(target, 0, 2); + NumberLiteral right = assertInstanceOf(NumberLiteral.class, assignment.getRight()); + assertLineColumnAre(right, 0, 7); + } + + @Test + void expressionHook() { + AstRoot root = myParse(" a ? b : 1"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + ConditionalExpression hook = + assertInstanceOf(ConditionalExpression.class, firstExpr.getExpression()); + assertLineColumnAre(hook, 0, 2); + Name testExpression = assertInstanceOf(Name.class, hook.getTestExpression()); + assertLineColumnAre(testExpression, 0, 2); + Name trueExpression = assertInstanceOf(Name.class, hook.getTrueExpression()); + assertLineColumnAre(trueExpression, 0, 6); + NumberLiteral falseExpression = + assertInstanceOf(NumberLiteral.class, hook.getFalseExpression()); + assertLineColumnAre(falseExpression, 0, 10); + } + + @Test + void expressionParenthesis() { + AstRoot root = myParse(" (3)"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + ParenthesizedExpression parenthesizedExpression = + assertInstanceOf(ParenthesizedExpression.class, firstExpr.getExpression()); + assertLineColumnAre(parenthesizedExpression, 0, 2); + NumberLiteral nestedExpression = + assertInstanceOf(NumberLiteral.class, parenthesizedExpression.getExpression()); + assertLineColumnAre(nestedExpression, 0, 3); + } + + @Test + void expressionComma() { + AstRoot root = myParse(" a, /*comment*/ 3"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + InfixExpression infixExpression = + assertInstanceOf(InfixExpression.class, firstExpr.getExpression()); + assertLineColumnAre(infixExpression, 0, 2); + Name left = assertInstanceOf(Name.class, infixExpression.getLeft()); + assertLineColumnAre(left, 0, 2); + NumberLiteral right = assertInstanceOf(NumberLiteral.class, infixExpression.getRight()); + assertLineColumnAre(right, 0, 17); + } + + @Test + void expressionNew() { + AstRoot root = myParse(" new Foo( a)"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + NewExpression newExpression = + assertInstanceOf(NewExpression.class, firstExpr.getExpression()); + assertLineColumnAre(newExpression, 0, 2); + Name target = assertInstanceOf(Name.class, newExpression.getTarget()); + assertLineColumnAre(target, 0, 7); + assertEquals(1, newExpression.getArguments().size()); + Name arg = assertInstanceOf(Name.class, newExpression.getArguments().get(0)); + assertLineColumnAre(arg, 0, 12); + } + + @Test + void expressionObjectLiteral() { + AstRoot root = + myParse( + " o = {a: 1,\n" + + " [b]: 2,\n" + + " c() { return 3; },\n" + + " d: function() { return 4; },\n" + + " get e() { return 5; },\n" + + " set f(value) { this._f = value; }\n" + + "}"); + ExpressionStatement expressionStatement = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(expressionStatement, 0, 2); + Assignment assignment = + assertInstanceOf(Assignment.class, expressionStatement.getExpression()); + assertLineColumnAre(assignment, 0, 2); + ObjectLiteral objectLiteral = assertInstanceOf(ObjectLiteral.class, assignment.getRight()); + assertLineColumnAre(objectLiteral, 0, 6); + assertEquals(6, objectLiteral.getElements().size()); + + ObjectProperty propA = + assertInstanceOf(ObjectProperty.class, objectLiteral.getElements().get(0)); + assertLineColumnAre(propA, 0, 7); + assertLineColumnAre(propA.getLeft(), 0, 7); + assertLineColumnAre(propA.getRight(), 0, 10); + + ObjectProperty propB = + assertInstanceOf(ObjectProperty.class, objectLiteral.getElements().get(1)); + assertLineColumnAre(propB, 1, 2); + ComputedPropertyKey propBKey = assertInstanceOf(ComputedPropertyKey.class, propB.getLeft()); + assertLineColumnAre(propBKey, 1, 2); + assertLineColumnAre(propBKey.getExpression(), 1, 3); + assertLineColumnAre(propB.getRight(), 1, 7); + + ObjectProperty propC = + assertInstanceOf(ObjectProperty.class, objectLiteral.getElements().get(2)); + assertLineColumnAre(propC, 2, 2); + assertLineColumnAre(propC.getLeft(), 2, 2); + assertLineColumnAre(propC.getRight(), 2, 2); + + ObjectProperty propD = + assertInstanceOf(ObjectProperty.class, objectLiteral.getElements().get(3)); + assertLineColumnAre(propD, 3, 2); + assertLineColumnAre(propD.getLeft(), 3, 2); + assertLineColumnAre(propD.getRight(), 3, 5); + + ObjectProperty propE = + assertInstanceOf(ObjectProperty.class, objectLiteral.getElements().get(4)); + assertLineColumnAre(propE, 4, 2); + assertLineColumnAre(propE.getLeft(), 4, 2); + assertLineColumnAre(propE.getRight(), 4, 6); + + ObjectProperty propF = + assertInstanceOf(ObjectProperty.class, objectLiteral.getElements().get(5)); + assertLineColumnAre(propF, 5, 2); + assertLineColumnAre(propF.getLeft(), 5, 2); + assertLineColumnAre(propF.getRight(), 5, 6); + } + + @Test + void expressionArrayLiteral() { + AstRoot root = myParse(" [a, /*comment*/ 3]"); + ExpressionStatement firstExpr = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(firstExpr, 0, 2); + ArrayLiteral arrayLiteral = assertInstanceOf(ArrayLiteral.class, firstExpr.getExpression()); + assertLineColumnAre(arrayLiteral, 0, 2); + + assertEquals(2, arrayLiteral.getElements().size()); + Name elem0 = assertInstanceOf(Name.class, arrayLiteral.getElements().get(0)); + assertLineColumnAre(elem0, 0, 3); + NumberLiteral elem1 = + assertInstanceOf(NumberLiteral.class, arrayLiteral.getElements().get(1)); + assertLineColumnAre(elem1, 0, 18); + } + + @Test + void expressionDestructuring() { + AstRoot root = myParse(" let {a, b} = c();"); + VariableDeclaration variableDeclaration = + assertInstanceOf(VariableDeclaration.class, root.getFirstChild()); + assertLineColumnAre(variableDeclaration, 0, 2); + assertEquals(1, variableDeclaration.getVariables().size()); + + VariableInitializer firstVar = variableDeclaration.getVariables().get(0); + ObjectLiteral left = assertInstanceOf(ObjectLiteral.class, firstVar.getTarget()); + assertLineColumnAre(left, 0, 6); + assertEquals(2, left.getElements().size()); + ObjectProperty firstProp = + assertInstanceOf(ObjectProperty.class, left.getElements().get(0)); + assertLineColumnAre(firstProp, 0, 7); + ObjectProperty secondProp = + assertInstanceOf(ObjectProperty.class, left.getElements().get(1)); + assertLineColumnAre(secondProp, 0, 10); + + FunctionCall right = assertInstanceOf(FunctionCall.class, firstVar.getInitializer()); + assertLineColumnAre(right, 0, 15); + } + + @Test + void expressionTaggedTemplateLiteral() { + AstRoot root = myParse(" f`x`"); + ExpressionStatement statement = + assertInstanceOf(ExpressionStatement.class, root.getFirstChild()); + assertLineColumnAre(statement, 0, 2); + TaggedTemplateLiteral taggedTemplateLiteral = + assertInstanceOf(TaggedTemplateLiteral.class, statement.getExpression()); + assertLineColumnAre(taggedTemplateLiteral, 0, 2); + assertLineColumnAre(taggedTemplateLiteral.getTarget(), 0, 2); + assertLineColumnAre(taggedTemplateLiteral.getTemplateLiteral(), 0, 3); + } + + @Test + void commentsAndNestedFunctions() { + AstRoot root = + myParse( + " function a() {\n" + + " // comment 1\n" + + " // comment 2\n" + + " function b() {}\n" + + " /* another" + + "comment */" + + "}"); + FunctionNode funA = assertInstanceOf(FunctionNode.class, root.getFirstChild()); + assertLineColumnAre(funA, 0, 2); + Block aBody = assertInstanceOf(Block.class, funA.getBody()); + assertLineColumnAre(aBody, 0, 15); + Comment comment1 = assertInstanceOf(Comment.class, aBody.getFirstChild()); + assertLineColumnAre(comment1, 1, 3); + Comment comment2 = assertInstanceOf(Comment.class, comment1.getNext()); + assertLineColumnAre(comment2, 2, 4); + FunctionNode funB = assertInstanceOf(FunctionNode.class, comment2.getNext()); + assertLineColumnAre(funB, 3, 3); + Comment comment3 = assertInstanceOf(Comment.class, funB.getNext()); + assertLineColumnAre(comment3, 4, 4); + } + + @Test + void emptySource() { + AstRoot root = myParse(""); + assertNull(root.getFirstChild()); + } + + private AstRoot myParse(String source) { + environment.setLanguageVersion(Context.VERSION_ES6); + return ParserTest.parse(source, null, null, true, environment); + } + + private void assertLineColumnAre(Node node, int line, int column) { + assertEquals(line, node.getLineno(), "line number mismatch"); + assertEquals(column, node.getColumn(), "column number mismatch"); + } +} diff --git a/tests/src/test/java/org/mozilla/javascript/tests/ParserTest.java b/tests/src/test/java/org/mozilla/javascript/tests/ParserTest.java index 59b6b6a276..c9b6aa9480 100644 --- a/tests/src/test/java/org/mozilla/javascript/tests/ParserTest.java +++ b/tests/src/test/java/org/mozilla/javascript/tests/ParserTest.java @@ -14,10 +14,12 @@ import java.util.List; import org.junit.Before; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import org.mozilla.javascript.CompilerEnvirons; import org.mozilla.javascript.Context; import org.mozilla.javascript.ErrorReporter; import org.mozilla.javascript.EvaluatorException; +import org.mozilla.javascript.Node; import org.mozilla.javascript.Parser; import org.mozilla.javascript.Token; import org.mozilla.javascript.ast.Assignment; @@ -151,7 +153,7 @@ public void linenoAssign() { assertTrue(n instanceof Assignment); assertEquals(Token.ASSIGN, n.getType()); - assertEquals(2, n.getLineno()); + assertLineColumnAre(2, 1, n); } @Test @@ -162,7 +164,7 @@ public void linenoCall() { assertTrue(n instanceof FunctionCall); assertEquals(Token.CALL, n.getType()); - assertEquals(1, n.getLineno()); + assertLineColumnAre(1, 1, n); } @Test @@ -173,14 +175,14 @@ public void linenoGetProp() { assertTrue(n instanceof PropertyGet); assertEquals(Token.GETPROP, n.getType()); - assertEquals(1, n.getLineno()); + assertLineColumnAre(1, 1, n); PropertyGet getprop = (PropertyGet) n; AstNode m = getprop.getRight(); assertTrue(m instanceof Name); assertEquals(Token.NAME, m.getType()); // used to be Token.STRING! - assertEquals(1, m.getLineno()); + assertLineColumnAre(1, 5, m); } @Test @@ -191,7 +193,7 @@ public void linenoGetElem() { assertTrue(n instanceof ElementGet); assertEquals(Token.GETELEM, n.getType()); - assertEquals(1, n.getLineno()); + assertLineColumnAre(1, 1, n); } @Test @@ -199,7 +201,7 @@ public void linenoComment() { AstRoot root = parse("\n/** a */"); assertNotNull(root.getComments()); assertEquals(1, root.getComments().size()); - assertEquals(1, root.getComments().first().getLineno()); + assertLineColumnAre(1, 1, root.getComments().first()); } @Test @@ -207,7 +209,7 @@ public void linenoComment2() { AstRoot root = parse("\n/**\n\n a */"); assertNotNull(root.getComments()); assertEquals(1, root.getComments().size()); - assertEquals(1, root.getComments().first().getLineno()); + assertLineColumnAre(1, 1, root.getComments().first()); } @Test @@ -215,7 +217,7 @@ public void linenoComment3() { AstRoot root = parse("\n \n\n/**\n\n a */"); assertNotNull(root.getComments()); assertEquals(1, root.getComments().size()); - assertEquals(3, root.getComments().first().getLineno()); + assertLineColumnAre(3, 1, root.getComments().first()); } @Test @@ -223,7 +225,7 @@ public void linenoComment4() { AstRoot root = parse("\n \n\n /**\n\n a */"); assertNotNull(root.getComments()); assertEquals(1, root.getComments().size()); - assertEquals(3, root.getComments().first().getLineno()); + assertLineColumnAre(3, 3, root.getComments().first()); } @Test @@ -231,7 +233,7 @@ public void lineComment5() { AstRoot root = parse(" /**\n* a.\n* b.\n* c.*/\n"); assertNotNull(root.getComments()); assertEquals(1, root.getComments().size()); - assertEquals(0, root.getComments().first().getLineno()); + assertLineColumnAre(0, 3, root.getComments().first()); } @Test @@ -239,7 +241,7 @@ public void lineComment6() { AstRoot root = parse(" \n/**\n* a.\n* b.\n* c.*/\n"); assertNotNull(root.getComments()); assertEquals(1, root.getComments().size()); - assertEquals(1, root.getComments().first().getLineno()); + assertLineColumnAre(1, 1, root.getComments().first()); } @Test @@ -247,7 +249,7 @@ public void linenoComment7() { AstRoot root = parse("var x;\n/**\n\n a */"); assertNotNull(root.getComments()); assertEquals(1, root.getComments().size()); - assertEquals(1, root.getComments().first().getLineno()); + assertLineColumnAre(1, 1, root.getComments().first()); } @Test @@ -255,7 +257,7 @@ public void linenoComment8() { AstRoot root = parse("\nvar x;/**\n\n a */"); assertNotNull(root.getComments()); assertEquals(1, root.getComments().size()); - assertEquals(1, root.getComments().first().getLineno()); + assertLineColumnAre(1, 7, root.getComments().first()); } @Test @@ -303,11 +305,11 @@ public void linenoLiteral() { Name fifthVarName = (Name) fifthVar.getTarget(); AstNode fifthVarLiteral = fifthVar.getInitializer(); - assertEquals(2, firstVarLiteral.getLineno()); - assertEquals(4, secondVarLiteral.getLineno()); - assertEquals(6, thirdVarLiteral.getLineno()); - assertEquals(8, fourthVarLiteral.getLineno()); - assertEquals(10, fifthVarLiteral.getLineno()); + assertLineColumnAre(2, 5, firstVarLiteral); + assertLineColumnAre(4, 5, secondVarLiteral); + assertLineColumnAre(6, 5, thirdVarLiteral); + assertLineColumnAre(8, 5, fourthVarLiteral); + assertLineColumnAre(10, 5, fifthVarLiteral); } @Test @@ -337,15 +339,15 @@ public void linenoSwitch() { AstNode defaultCase = cases.get(2); AstNode returnStmt = (AstNode) switchStmt.getNext(); - assertEquals(1, switchStmt.getLineno()); - assertEquals(1, switchVar.getLineno()); - assertEquals(2, firstCase.getLineno()); - assertEquals(3, caseArg.getLineno()); - assertEquals(4, exprStmt.getLineno()); - assertEquals(4, incrExpr.getLineno()); - assertEquals(4, incrVar.getLineno()); - assertEquals(5, secondCase.getLineno()); - assertEquals(6, defaultCase.getLineno()); + assertLineColumnAre(1, 1, switchStmt); + assertLineColumnAre(1, 9, switchVar); + assertLineColumnAre(2, 4, firstCase); + assertLineColumnAre(3, 6, caseArg); + assertLineColumnAre(4, 6, exprStmt); + assertLineColumnAre(4, 6, incrExpr); + assertLineColumnAre(4, 6, incrVar); + assertLineColumnAre(5, 4, secondCase); + assertLineColumnAre(6, 4, defaultCase); } @Test @@ -367,12 +369,12 @@ public void linenoFunctionParams() { AstNode param2 = params.get(1); AstNode param3 = params.get(2); - assertEquals(1, function.getLineno()); - assertEquals(2, functionName.getLineno()); - assertEquals(3, param1.getLineno()); - assertEquals(4, param2.getLineno()); - assertEquals(5, param3.getLineno()); - assertEquals(5, body.getLineno()); + assertLineColumnAre(1, 1, function); + assertLineColumnAre(2, 5, functionName); + assertLineColumnAre(3, 5, param1); + assertLineColumnAre(4, 5, param2); + assertLineColumnAre(5, 5, param3); + assertLineColumnAre(5, 8, body); } @Test @@ -385,10 +387,10 @@ public void linenoVarDecl() { AstNode declName = init.getTarget(); AstNode expr = init.getInitializer(); - assertEquals(1, decl.getLineno()); - assertEquals(2, init.getLineno()); - assertEquals(2, declName.getLineno()); - assertEquals(3, expr.getLineno()); + assertLineColumnAre(1, 1, decl); + assertLineColumnAre(2, 5, init); + assertLineColumnAre(2, 5, declName); + assertLineColumnAre(3, 5, expr); } @Test @@ -411,9 +413,9 @@ public void linenoReturn() { ExpressionStatement exprStmt = (ExpressionStatement) returnStmt.getNext(); AstNode returnVal = exprStmt.getExpression(); - assertEquals(6, returnStmt.getLineno()); - assertEquals(7, exprStmt.getLineno()); - assertEquals(7, returnVal.getLineno()); + assertLineColumnAre(6, 5, returnStmt); + assertLineColumnAre(7, 5, exprStmt); + assertLineColumnAre(7, 5, returnVal); } @Test @@ -425,10 +427,10 @@ public void linenoFor() { AstNode condClause = forLoop.getCondition(); AstNode incrClause = forLoop.getIncrement(); - assertEquals(1, forLoop.getLineno()); - assertEquals(2, initClause.getLineno()); - assertEquals(3, condClause.getLineno()); - assertEquals(4, incrClause.getLineno()); + assertLineColumnAre(1, 1, forLoop); + assertLineColumnAre(2, 1, initClause); + assertLineColumnAre(3, 1, condClause); + assertLineColumnAre(4, 1, incrClause); } @Test @@ -439,9 +441,9 @@ public void linenoInfix() { + " + \n" + " b;\n" + "var\n" - + " e =\n" + + " e =\n" + " a +\n" - + " c;\n" + + " c;\n" + "var f = b\n" + " / c;\n"); @@ -465,25 +467,25 @@ public void linenoInfix() { ReturnStatement returnStmt = (ReturnStatement) stmt3.getNext(); - assertEquals(1, var1.getLineno()); - assertEquals(1, firstVarName.getLineno()); - assertEquals(1, var1Add.getLineno()); - assertEquals(1, var1Add.getLeft().getLineno()); - assertEquals(3, var1Add.getRight().getLineno()); + assertLineColumnAre(1, 5, var1); + assertLineColumnAre(1, 5, firstVarName); + assertLineColumnAre(1, 9, var1Add); + assertLineColumnAre(1, 9, var1Add.getLeft()); + assertLineColumnAre(3, 5, var1Add.getRight()); // var directive with name on next line wrong -- // should be 6. - assertEquals(5, var2.getLineno()); - assertEquals(5, secondVarName.getLineno()); - assertEquals(6, var2Add.getLineno()); - assertEquals(6, var2Add.getLeft().getLineno()); - assertEquals(7, var2Add.getRight().getLineno()); + assertLineColumnAre(5, 4, var2); + assertLineColumnAre(5, 4, secondVarName); + assertLineColumnAre(6, 5, var2Add); + assertLineColumnAre(6, 5, var2Add.getLeft()); + assertLineColumnAre(7, 6, var2Add.getRight()); - assertEquals(8, var3.getLineno()); - assertEquals(8, thirdVarName.getLineno()); - assertEquals(8, thirdVarDiv.getLineno()); - assertEquals(8, thirdVarDiv.getLeft().getLineno()); - assertEquals(9, thirdVarDiv.getRight().getLineno()); + assertLineColumnAre(8, 5, var3); + assertLineColumnAre(8, 5, thirdVarName); + assertLineColumnAre(8, 9, thirdVarDiv); + assertLineColumnAre(8, 9, thirdVarDiv.getLeft()); + assertLineColumnAre(9, 7, thirdVarDiv.getRight()); } @Test @@ -497,10 +499,10 @@ public void linenoPrefix() { AstNode firstVarRef = firstOp.getOperand(); AstNode secondVarRef = secondOp.getOperand(); - assertEquals(1, firstOp.getLineno()); - assertEquals(2, secondOp.getLineno()); - assertEquals(1, firstVarRef.getLineno()); - assertEquals(3, secondVarRef.getLineno()); + assertLineColumnAre(1, 1, firstOp); + assertLineColumnAre(2, 4, secondOp); + assertLineColumnAre(1, 1, firstVarRef); + assertLineColumnAre(3, 4, secondVarRef); } @Test @@ -522,10 +524,10 @@ public void linenoIf() { AstNode thenClause = ifStmt.getThenPart(); AstNode elseClause = ifStmt.getElsePart(); - assertEquals(1, ifStmt.getLineno()); - assertEquals(2, condClause.getLineno()); - assertEquals(3, thenClause.getLineno()); - assertEquals(7, elseClause.getLineno()); + assertLineColumnAre(1, 1, ifStmt); + assertLineColumnAre(2, 5, condClause); + assertLineColumnAre(3, 4, thenClause); + assertLineColumnAre(7, 4, elseClause); } @Test @@ -550,13 +552,13 @@ public void linenoTry() { AstNode finallyBlock = tryStmt.getFinallyBlock(); AstNode finallyStmt = (AstNode) finallyBlock.getFirstChild(); - assertEquals(1, tryStmt.getLineno()); - assertEquals(1, tryBlock.getLineno()); - assertEquals(5, catchVarBlock.getLineno()); - assertEquals(4, catchVar.getLineno()); - assertEquals(3, catchClause.getLineno()); - assertEquals(6, finallyBlock.getLineno()); - assertEquals(7, finallyStmt.getLineno()); + assertLineColumnAre(1, 1, tryStmt); + assertLineColumnAre(1, 5, tryBlock); + assertLineColumnAre(5, 1, catchVarBlock); + assertLineColumnAre(4, 6, catchVar); + assertLineColumnAre(3, 3, catchClause); + assertLineColumnAre(6, 11, finallyBlock); + assertLineColumnAre(7, 5, finallyStmt); } @Test @@ -569,10 +571,10 @@ public void linenoConditional() { AstNode thenExpr = hook.getTrueExpression(); AstNode elseExpr = hook.getFalseExpression(); - assertEquals(2, hook.getLineno()); - assertEquals(1, condExpr.getLineno()); - assertEquals(3, thenExpr.getLineno()); - assertEquals(5, elseExpr.getLineno()); + assertLineColumnAre(1, 1, hook); + assertLineColumnAre(1, 1, condExpr); + assertLineColumnAre(3, 5, thenExpr); + assertLineColumnAre(5, 5, elseExpr); } @Test @@ -582,8 +584,8 @@ public void linenoLabel() { LabeledStatement firstStmt = (LabeledStatement) root.getFirstChild(); LabeledStatement secondStmt = (LabeledStatement) firstStmt.getNext(); - assertEquals(1, firstStmt.getLineno()); - assertEquals(3, secondStmt.getLineno()); + assertLineColumnAre(1, 1, firstStmt); + assertLineColumnAre(3, 1, secondStmt); } @Test @@ -595,9 +597,9 @@ public void linenoCompare() { AstNode lhs = compare.getLeft(); AstNode rhs = compare.getRight(); - assertEquals(1, lhs.getLineno()); - assertEquals(1, compare.getLineno()); - assertEquals(3, rhs.getLineno()); + assertLineColumnAre(1, 1, lhs); + assertLineColumnAre(1, 1, compare); + assertLineColumnAre(3, 1, rhs); } @Test @@ -608,9 +610,9 @@ public void linenoEq() { AstNode lhs = compare.getLeft(); AstNode rhs = compare.getRight(); - assertEquals(1, lhs.getLineno()); - assertEquals(1, compare.getLineno()); - assertEquals(3, rhs.getLineno()); + assertLineColumnAre(1, 1, lhs); + assertLineColumnAre(1, 1, compare); + assertLineColumnAre(3, 1, rhs); } @Test @@ -621,14 +623,14 @@ public void linenoPlusEq() { AstNode lhs = assign.getLeft(); AstNode rhs = assign.getRight(); - assertEquals(1, lhs.getLineno()); - assertEquals(1, assign.getLineno()); - assertEquals(3, rhs.getLineno()); + assertLineColumnAre(1, 1, lhs); + assertLineColumnAre(1, 1, assign); + assertLineColumnAre(3, 1, rhs); } @Test public void linenoComma() { - AstRoot root = parse("\na,\n" + " b,\n" + " c;\n"); + AstRoot root = parse("\na,\n" + " b,\n" + " c;\n"); ExpressionStatement stmt = (ExpressionStatement) root.getFirstChild(); InfixExpression comma1 = (InfixExpression) stmt.getExpression(); @@ -637,11 +639,11 @@ public void linenoComma() { AstNode aRef = comma2.getLeft(); AstNode bRef = comma2.getRight(); - assertEquals(1, comma1.getLineno()); - assertEquals(1, comma2.getLineno()); - assertEquals(1, aRef.getLineno()); - assertEquals(2, bRef.getLineno()); - assertEquals(3, cRef.getLineno()); + assertLineColumnAre(1, 1, comma1); + assertLineColumnAre(1, 1, comma2); + assertLineColumnAre(1, 1, aRef); + assertLineColumnAre(2, 5, bRef); + assertLineColumnAre(3, 4, cRef); } @Test @@ -658,16 +660,16 @@ public void regexpLocation() { RegExpLiteral regexObject = (RegExpLiteral) args.get(0); AstNode aString = args.get(1); - assertEquals(1, firstVarDecl.getLineno()); - assertEquals(1, firstVarName.getLineno()); - assertEquals(2, callNode.getLineno()); - assertEquals(2, fnName.getLineno()); - assertEquals(3, regexObject.getLineno()); - assertEquals(3, aString.getLineno()); + assertLineColumnAre(1, 1, firstVarDecl); + assertLineColumnAre(1, 5, firstVarName); + assertLineColumnAre(2, 7, callNode); + assertLineColumnAre(2, 7, fnName); + assertLineColumnAre(3, 1, regexObject); + assertLineColumnAre(3, 6, aString); } @Test - public void mestedOr() { + public void nestedOr() { AstNode root = parse( "\nif (a && \n" @@ -681,10 +683,10 @@ public void mestedOr() { InfixExpression andClause = (InfixExpression) orClause.getLeft(); AstNode cName = orClause.getRight(); - assertEquals(1, ifStmt.getLineno()); - assertEquals(1, orClause.getLineno()); - assertEquals(1, andClause.getLineno()); - assertEquals(4, cName.getLineno()); + assertLineColumnAre(1, 1, ifStmt); + assertLineColumnAre(1, 5, orClause); + assertLineColumnAre(1, 5, andClause); + assertLineColumnAre(4, 5, cName); } @Test @@ -708,7 +710,7 @@ public void objectLitLocation() { + "{ \n" + "'A' : 'A', \n" + "'B' : 'B', \n" - + "'C' : \n" + + " 'C' : \n" + " 'C' \n" + "};\n"); @@ -730,17 +732,17 @@ public void objectLitLocation() { AstNode thirdKey = thirdObjectLit.getLeft(); AstNode thirdValue = thirdObjectLit.getRight(); - assertEquals(1, firstVarName.getLineno()); - assertEquals(2, objectLiteral.getLineno()); - assertEquals(3, firstObjectLit.getLineno()); - assertEquals(3, firstKey.getLineno()); - assertEquals(3, firstValue.getLineno()); + assertLineColumnAre(1, 5, firstVarName); + assertLineColumnAre(2, 1, objectLiteral); + assertLineColumnAre(3, 1, firstObjectLit); + assertLineColumnAre(3, 1, firstKey); + assertLineColumnAre(3, 7, firstValue); - assertEquals(4, secondKey.getLineno()); - assertEquals(4, secondValue.getLineno()); + assertLineColumnAre(4, 1, secondKey); + assertLineColumnAre(4, 7, secondValue); - assertEquals(5, thirdKey.getLineno()); - assertEquals(6, thirdValue.getLineno()); + assertLineColumnAre(5, 2, thirdKey); + assertLineColumnAre(6, 7, thirdValue); } @Test @@ -754,10 +756,10 @@ public void tryWithoutCatchLocation() { Scope finallyBlock = (Scope) tryStmt.getFinallyBlock(); AstNode finallyStmt = (AstNode) finallyBlock.getFirstChild(); - assertEquals(1, tryStmt.getLineno()); - assertEquals(1, tryBlock.getLineno()); - assertEquals(3, finallyBlock.getLineno()); - assertEquals(4, finallyStmt.getLineno()); + assertLineColumnAre(1, 1, tryStmt); + assertLineColumnAre(1, 5, tryBlock); + assertLineColumnAre(3, 11, finallyBlock); + assertLineColumnAre(4, 3, finallyStmt); } @Test @@ -778,12 +780,12 @@ public void tryWithoutFinallyLocation() { AstNode exceptionVar = catchClause.getVarName(); AstNode varDecl = (AstNode) catchStmt.getFirstChild(); - assertEquals(1, tryStmt.getLineno()); - assertEquals(1, tryBlock.getLineno()); - assertEquals(3, catchClause.getLineno()); - assertEquals(3, catchStmt.getLineno()); - assertEquals(3, exceptionVar.getLineno()); - assertEquals(4, varDecl.getLineno()); + assertLineColumnAre(1, 1, tryStmt); + assertLineColumnAre(1, 5, tryBlock); + assertLineColumnAre(3, 3, catchClause); + assertLineColumnAre(3, 14, catchStmt); + assertLineColumnAre(3, 10, exceptionVar); + assertLineColumnAre(4, 3, varDecl); } @Test @@ -805,14 +807,14 @@ public void linenoMultilineEq() { AstNode aTest = andTest.getLeft(); AstNode bTest = andTest.getRight(); - assertEquals(1, ifStmt.getLineno()); - assertEquals(2, orTest.getLineno()); - assertEquals(2, andTest.getLineno()); - assertEquals(2, aTest.getLineno()); - assertEquals(4, bTest.getLineno()); - assertEquals(5, cTest.getLineno()); - assertEquals(5, cTestParen.getLineno()); - assertEquals(2, andTestParen.getLineno()); + assertLineColumnAre(1, 1, ifStmt); + assertLineColumnAre(2, 6, orTest); + assertLineColumnAre(2, 7, andTest); + assertLineColumnAre(2, 7, aTest); + assertLineColumnAre(4, 3, bTest); + assertLineColumnAre(5, 3, cTest); + assertLineColumnAre(5, 2, cTestParen); + assertLineColumnAre(2, 6, andTestParen); } @Test @@ -851,22 +853,22 @@ public void linenoMultilineBitTest() { InfixExpression bitXorTest = (InfixExpression) test3Expr.getExpression(); InfixExpression bitShiftTest = (InfixExpression) test4Expr.getExpression(); - assertEquals(1, ifStmt.getLineno()); + assertLineColumnAre(1, 1, ifStmt); - assertEquals(2, bigLHSExpr.getLineno()); - assertEquals(7, bigRHSExpr.getLineno()); - assertEquals(2, eqTest.getLineno()); - assertEquals(7, notEqTest.getLineno()); + assertLineColumnAre(2, 7, bigLHSExpr); + assertLineColumnAre(7, 7, bigRHSExpr); + assertLineColumnAre(2, 8, eqTest); + assertLineColumnAre(7, 8, notEqTest); - assertEquals(2, test1Expr.getLineno()); - assertEquals(5, test2Expr.getLineno()); - assertEquals(7, test3Expr.getLineno()); - assertEquals(10, test4Expr.getLineno()); + assertLineColumnAre(2, 8, test1Expr); + assertLineColumnAre(5, 8, test2Expr); + assertLineColumnAre(7, 8, test3Expr); + assertLineColumnAre(10, 8, test4Expr); - assertEquals(2, bitOrTest.getLineno()); - assertEquals(5, bitAndTest.getLineno()); - assertEquals(7, bitXorTest.getLineno()); - assertEquals(10, bitShiftTest.getLineno()); + assertLineColumnAre(2, 9, bitOrTest); + assertLineColumnAre(5, 9, bitAndTest); + assertLineColumnAre(7, 9, bitXorTest); + assertLineColumnAre(10, 9, bitShiftTest); } @Test @@ -876,7 +878,7 @@ public void linenoFunctionCall() { ExpressionStatement stmt = (ExpressionStatement) root.getFirstChild(); FunctionCall fc = (FunctionCall) stmt.getExpression(); // Line number should get closest to the actual paren. - assertEquals(3, fc.getLineno()); + assertLineColumnAre(1, 1, fc); } @Test @@ -888,8 +890,8 @@ public void linenoName() { ExpressionStatement bExprStmt = (ExpressionStatement) exprStmt.getNext(); AstNode bRef = bExprStmt.getExpression(); - assertEquals(1, aRef.getLineno()); - assertEquals(2, bRef.getLineno()); + assertLineColumnAre(1, 1, aRef); + assertLineColumnAre(2, 1, bRef); } @Test @@ -903,11 +905,11 @@ public void linenoDeclaration() { AstNode bName = aDotbName.getRight(); FunctionNode fnNode = (FunctionNode) fnAssignment.getRight(); - assertEquals(1, fnAssignment.getLineno()); - assertEquals(1, aDotbName.getLineno()); - assertEquals(1, aName.getLineno()); - assertEquals(2, bName.getLineno()); - assertEquals(3, fnNode.getLineno()); + assertLineColumnAre(1, 1, fnAssignment); + assertLineColumnAre(1, 1, aDotbName); + assertLineColumnAre(1, 1, aName); + assertLineColumnAre(2, 1, bName); + assertLineColumnAre(3, 1, fnNode); } @Test @@ -1181,8 +1183,8 @@ public void linenoCommentsWithJSDoc() throws IOException { assertEquals(2, root.getComments().size()); Comment[] comments = new Comment[2]; comments = root.getComments().toArray(comments); - assertEquals(0, comments[0].getLineno()); - assertEquals(3, comments[1].getLineno()); + assertLineColumnAre(0, 1, comments[0]); + assertLineColumnAre(3, 1, comments[1]); } @Test @@ -1489,4 +1491,9 @@ private AstRoot parseAsReader(String string) throws IOException { return script; } + + private void assertLineColumnAre(int line, int column, Node node) { + Assertions.assertEquals(line, node.getLineno(), "line number mismatch"); + Assertions.assertEquals(column, node.getColumn(), "column number mismatch"); + } }