blob: 6726152cbf554f5263d36b712becc03cf6dfa3d2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.javascript2.editor.formatter;
import com.oracle.js.parser.ir.AccessNode;
import com.oracle.js.parser.ir.BinaryNode;
import com.oracle.js.parser.ir.Block;
import com.oracle.js.parser.ir.BlockStatement;
import com.oracle.js.parser.ir.CallNode;
import com.oracle.js.parser.ir.CaseNode;
import com.oracle.js.parser.ir.CatchNode;
import com.oracle.js.parser.ir.Expression;
import com.oracle.js.parser.ir.ForNode;
import com.oracle.js.parser.ir.FunctionNode;
import com.oracle.js.parser.ir.IdentNode;
import com.oracle.js.parser.ir.IfNode;
import com.oracle.js.parser.ir.LexicalContext;
import com.oracle.js.parser.ir.LiteralNode;
import com.oracle.js.parser.ir.LoopNode;
import com.oracle.js.parser.ir.Node;
import com.oracle.js.parser.ir.ObjectNode;
import com.oracle.js.parser.ir.PropertyNode;
import com.oracle.js.parser.ir.Statement;
import com.oracle.js.parser.ir.SwitchNode;
import com.oracle.js.parser.ir.TernaryNode;
import com.oracle.js.parser.ir.TryNode;
import com.oracle.js.parser.ir.UnaryNode;
import com.oracle.js.parser.ir.VarNode;
import com.oracle.js.parser.ir.WhileNode;
import com.oracle.js.parser.ir.WithNode;
import com.oracle.js.parser.ir.visitor.NodeVisitor;
import com.oracle.js.parser.TokenType;
import com.oracle.js.parser.ir.ClassNode;
import com.oracle.js.parser.ir.ExportNode;
import com.oracle.js.parser.ir.ExpressionStatement;
import com.oracle.js.parser.ir.ImportNode;
import com.oracle.js.parser.ir.JsxAttributeNode;
import com.oracle.js.parser.ir.JsxElementNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.javascript2.lexer.api.JsTokenId;
/**
*
* @author Petr Hejl
*/
public class JsFormatVisitor extends NodeVisitor {
private static final Set<TokenType> UNARY_TYPES = EnumSet.noneOf(TokenType.class);
static {
Collections.addAll(UNARY_TYPES, TokenType.ADD, TokenType.SUB,
TokenType.BIT_NOT, TokenType.NOT,
TokenType.INCPOSTFIX, TokenType.INCPREFIX,
TokenType.DECPOSTFIX, TokenType.DECPREFIX);
}
private final TokenSequence<? extends JsTokenId> ts;
private final FormatTokenStream tokenStream;
private final int formatFinish;
private final TokenUtils tokenUtils;
public JsFormatVisitor(FormatTokenStream tokenStream, TokenSequence<? extends JsTokenId> ts, int formatFinish) {
super(new LexicalContext());
this.ts = ts;
this.tokenStream = tokenStream;
this.formatFinish = formatFinish;
this.tokenUtils = new TokenUtils(ts, tokenStream, formatFinish);
}
@Override
public boolean enterBlock(Block block) {
if (isScript(block) || !isVirtual(block)) {
if (isScript(block)) {
handleBlockContent(block, true);
} else {
handleStandardBlock(block);
}
}
if (isScript(block) || !isVirtual(block)) {
return false;
} else {
return super.enterBlock(block);
}
}
@Override
public Node leaveBlock(Block block) {
if (isScript(block)
|| !isVirtual(block)) {
return null;
} else {
return super.leaveBlock(block);
}
}
@Override
public boolean enterCaseNode(CaseNode caseNode) {
List<Statement> nodes = caseNode.getStatements();
if (nodes.size() == 1) {
Statement node = nodes.get(0);
if (node instanceof BlockStatement) {
return super.enterCaseNode(caseNode);
}
}
if (nodes.size() >= 1) {
// indentation mark
FormatToken formatToken = tokenUtils.getPreviousToken(getStart(nodes.get(0)), JsTokenId.OPERATOR_COLON, true);
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.INDENTATION_INC));
}
// put indentation mark
formatToken = getCaseEndToken(getStart(nodes.get(0)), getFinish(nodes.get(nodes.size() - 1)));
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.INDENTATION_DEC));
}
handleBlockContent(nodes, true);
}
return false;
}
@Override
public boolean enterJsxElementNode(JsxElementNode jsxElementNode) {
FormatToken jsxToken = tokenStream.getToken(getStart(jsxElementNode));
if (jsxToken != null) {
assert jsxToken.getId() == JsTokenId.JSX_TEXT : jsxToken.toString() + " from " + jsxElementNode.toString();
if (jsxToken.getText().toString().startsWith("<")) {
FormatToken jsxTokenPrev = jsxToken.previous();
if (jsxTokenPrev != null) {
TokenUtils.appendTokenAfterLastVirtual(jsxTokenPrev, FormatToken.forFormat(FormatToken.Kind.BEFORE_JSX_BLOCK_START), true);
}
}
jsxToken = tokenUtils.getPreviousToken(getFinish(jsxElementNode) - 1, JsTokenId.JSX_TEXT);
if (jsxToken != null && jsxToken.getText().toString().endsWith(">")) {
assert jsxToken.getId() == JsTokenId.JSX_TEXT : jsxToken;
TokenUtils.appendTokenAfterLastVirtual(jsxToken, FormatToken.forFormat(FormatToken.Kind.AFTER_JSX_BLOCK_END), true);
}
}
for (Expression e : jsxElementNode.getChildren()) {
if (!(e instanceof LiteralNode) && !(e instanceof JsxElementNode)) {
// assignmentExpression
int start = getStart(e);
FormatToken token = tokenUtils.getPreviousToken(start, JsTokenId.JSX_EXP_BEGIN);
if (token != null) {
TokenUtils.appendToken(token, FormatToken.forFormat(FormatToken.Kind.INDENTATION_INC));
}
int finish = getFinish(e);
token = tokenUtils.getNextToken(finish, JsTokenId.JSX_EXP_END);
if (token != null) {
token = tokenUtils.getPreviousNonWhiteToken(token.getOffset(),
start, JsTokenId.JSX_EXP_END, true);
if (token != null) {
TokenUtils.appendToken(token, FormatToken.forFormat(FormatToken.Kind.INDENTATION_DEC));
}
}
}
}
return super.enterJsxElementNode(jsxElementNode);
}
@Override
public boolean enterJsxAttributeNode(JsxAttributeNode jsxAttributeNode) {
Expression e = jsxAttributeNode.getValue();
if (e != null && !(e instanceof LiteralNode) && !(e instanceof JsxElementNode)) {
// assignmentExpression or unaryNode
int start = getStart(e);
FormatToken token = tokenUtils.getPreviousToken(start, JsTokenId.JSX_EXP_BEGIN);
if (token != null) {
TokenUtils.appendToken(token, FormatToken.forFormat(FormatToken.Kind.INDENTATION_INC));
}
int finish = getFinish(e);
token = tokenUtils.getNextToken(finish, JsTokenId.JSX_EXP_END);
if (token != null) {
token = tokenUtils.getPreviousNonWhiteToken(token.getOffset(),
start, JsTokenId.JSX_EXP_END, true);
if (token != null) {
TokenUtils.appendToken(token, FormatToken.forFormat(FormatToken.Kind.INDENTATION_DEC));
}
}
}
return super.enterJsxAttributeNode(jsxAttributeNode);
}
@Override
public boolean enterWhileNode(WhileNode whileNode) {
if (whileNode.isDoWhile()) {
// within parens spaces
int leftStart;
Block body = whileNode.getBody();
// if (isVirtual(body)) {
// // unfortunately due to condition at the end of do-while
// // we have to care about virtual block
// List<Statement> statements = body.getStatements();
// leftStart = getFinish(statements.get(statements.size() - 1));
// } else {
leftStart = getFinish(whileNode.getBody());
// }
markSpacesWithinParentheses(whileNode, leftStart, getFinish(whileNode),
FormatToken.Kind.AFTER_WHILE_PARENTHESIS, FormatToken.Kind.BEFORE_WHILE_PARENTHESIS);
// mark space before left brace
markSpacesBeforeBrace(whileNode.getBody(), FormatToken.Kind.BEFORE_DO_BRACE);
FormatToken whileToken = tokenUtils.getPreviousToken(getFinish(whileNode), JsTokenId.KEYWORD_WHILE);
if (whileToken != null) {
FormatToken beforeWhile = whileToken.previous();
if (beforeWhile != null) {
tokenUtils.appendToken(beforeWhile, FormatToken.forFormat(FormatToken.Kind.BEFORE_WHILE_KEYWORD));
}
}
if (handleLoop(whileNode, FormatToken.Kind.AFTER_DO_START)) {
return false;
}
markEndCurlyBrace(whileNode.getBody());
return super.enterWhileNode(whileNode);
} else {
// within parens spaces
markSpacesWithinParentheses(whileNode, getStart(whileNode), getStart(whileNode.getBody()),
FormatToken.Kind.AFTER_WHILE_PARENTHESIS, FormatToken.Kind.BEFORE_WHILE_PARENTHESIS);
// mark space before left brace
markSpacesBeforeBrace(whileNode.getBody(), FormatToken.Kind.BEFORE_WHILE_BRACE);
if (handleLoop(whileNode, FormatToken.Kind.AFTER_WHILE_START)) {
return false;
}
markEndCurlyBrace(whileNode.getBody());
return super.enterWhileNode(whileNode);
}
}
@Override
public boolean enterForNode(ForNode forNode) {
// within parens spaces
markSpacesWithinParentheses(forNode, getStart(forNode), getStart(forNode.getBody()),
FormatToken.Kind.AFTER_FOR_PARENTHESIS, FormatToken.Kind.BEFORE_FOR_PARENTHESIS);
// mark space before left brace
markSpacesBeforeBrace(forNode.getBody(), FormatToken.Kind.BEFORE_FOR_BRACE);
if (!forNode.isForEach() && !forNode.isForIn()) {
Node init = forNode.getInit();
Node test = forNode.getTest();
FormatToken formatToken;
// unfortunately init and test may be null
if (init != null) {
formatToken = tokenUtils.getNextToken(getFinish(init), JsTokenId.OPERATOR_SEMICOLON);
} else {
formatToken = tokenUtils.getNextToken(getStart(forNode), JsTokenId.OPERATOR_SEMICOLON,
getStart(forNode.getBody()));
}
if (formatToken != null && test != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken,
FormatToken.forFormat(FormatToken.Kind.BEFORE_FOR_TEST));
}
if (test != null) {
formatToken = tokenUtils.getNextToken(getFinish(forNode.getTest()), JsTokenId.OPERATOR_SEMICOLON);
} else {
// we use the position of init semicolon
int start = formatToken != null ? formatToken.getOffset() + 1 : getStart(forNode);
formatToken = tokenUtils.getNextToken(start, JsTokenId.OPERATOR_SEMICOLON,
getStart(forNode.getBody()));
}
if (formatToken != null && forNode.getModify() != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken,
FormatToken.forFormat(FormatToken.Kind.BEFORE_FOR_MODIFY));
}
}
if (handleLoop(forNode, FormatToken.Kind.AFTER_FOR_START)) {
return false;
}
markEndCurlyBrace(forNode.getBody());
return super.enterForNode(forNode);
}
@Override
public boolean enterIfNode(IfNode ifNode) {
ifNode.getTest().accept(this);
// within parens spaces
markSpacesWithinParentheses(ifNode, getStart(ifNode), getStart(ifNode.getPass()),
FormatToken.Kind.AFTER_IF_PARENTHESIS, FormatToken.Kind.BEFORE_IF_PARENTHESIS);
// pass block
Block body = ifNode.getPass();
// mark space before left brace
markSpacesBeforeBrace(body, FormatToken.Kind.BEFORE_IF_BRACE);
if (isVirtual(body)) {
handleVirtualBlock(body, FormatToken.Kind.AFTER_IF_START);
} else {
enterBlock(body);
markEndCurlyBrace(body);
}
// fail block
body = ifNode.getFail();
if (body != null) {
if (isVirtual(body)) {
// do the standard block related things
List<Statement> statements = body.getStatements();
// there might be no statements when code is broken
if (!statements.isEmpty() && (statements.get(0) instanceof IfNode)) {
// we mark else if statement here
handleVirtualBlock(body, FormatToken.Kind.ELSE_IF_INDENTATION_INC,
FormatToken.Kind.ELSE_IF_INDENTATION_DEC, FormatToken.Kind.ELSE_IF_AFTER_BLOCK_START, true);
} else {
// mark space before left brace
markSpacesBeforeBrace(body, FormatToken.Kind.BEFORE_ELSE_BRACE);
handleVirtualBlock(body, FormatToken.Kind.AFTER_ELSE_START);
}
} else {
// mark space before left brace
markSpacesBeforeBrace(body, FormatToken.Kind.BEFORE_ELSE_BRACE);
enterBlock(body);
markEndCurlyBrace(body);
}
}
return false;
}
@Override
public Node leaveIfNode(IfNode ifNode) {
return null;
}
@Override
public boolean enterWithNode(WithNode withNode) {
// within parens spaces
markSpacesWithinParentheses(withNode, getStart(withNode), getStart(withNode.getBody()),
FormatToken.Kind.AFTER_WITH_PARENTHESIS, FormatToken.Kind.BEFORE_WITH_PARENTHESIS);
Block body = withNode.getBody();
// mark space before left brace
markSpacesBeforeBrace(body, FormatToken.Kind.BEFORE_WITH_BRACE);
if (isVirtual(body)) {
handleVirtualBlock(body, FormatToken.Kind.AFTER_WITH_START);
return false;
}
markEndCurlyBrace(body);
return super.enterWithNode(withNode);
}
@Override
public boolean enterFunctionNode(FunctionNode functionNode) {
if (functionNode.isModule()) {
functionNode.visitImports(this);
functionNode.visitExports(this);
}
// if (functionNode.isClassConstructor() && !"constructor".equals(functionNode.getIdent().getName())) { // NOI18N
// // generated constructor
// return false;
// }
Block body = functionNode.getBody();
// default parameters are stored as assignments inside the function
// body block - the real block is just behind it
if (body.isParameterBlock()) {
List<Statement> statements = body.getStatements();
if (!statements.isEmpty()) {
Statement last = statements.get(statements.size() - 1);
if (last instanceof BlockStatement) {
body = ((BlockStatement) last).getBlock();
}
}
}
if (isVirtual(body) && functionNode.getKind() == FunctionNode.Kind.ARROW) {
Token nonEmpty = tokenUtils.getPreviousNonEmptyToken(getStart(body));
if (nonEmpty != null) {
FormatToken token = tokenStream.getToken(ts.offset());
if (token != null) {
TokenUtils.appendTokenAfterLastVirtual(token, FormatToken.forFormat(FormatToken.Kind.BEFORE_ARROW_BLOCK));
}
}
handleVirtualBlock(body, FormatToken.Kind.INDENTATION_INC, FormatToken.Kind.INDENTATION_DEC, null, false);
nonEmpty = tokenUtils.getPreviousNonEmptyToken(getFinish(body));
if (nonEmpty != null) {
FormatToken token = tokenStream.getToken(ts.offset());
if (token != null) {
TokenUtils.appendTokenAfterLastVirtual(token, FormatToken.forFormat(FormatToken.Kind.AFTER_ARROW_BLOCK));
}
}
} else {
enterBlock(body);
}
if (functionNode.isProgram()) {
return false;
}
int start = getStart(functionNode);
if (functionNode.getKind() == FunctionNode.Kind.ARROW) {
FormatToken left = tokenUtils.getNextToken(start, JsTokenId.BRACKET_LEFT_PAREN, start);
FormatToken leftParen = left;
if (left == null) {
// single parameter arrow without parenthesis
left = tokenUtils.getNextToken(start, JsTokenId.IDENTIFIER, start);
}
if (left != null) {
FormatToken previous = left.previous();
if (previous != null) {
TokenUtils.appendToken(previous, FormatToken.forFormat(
FormatToken.Kind.BEFORE_ARROW_FUNCTION_DECLARATION));
}
}
handleFunctionParameters(functionNode, leftParen);
} else {
// the star * is not multiplication (binary operator)
// FIXME should this be solved in lexer?
if (functionNode.getKind() == FunctionNode.Kind.GENERATOR) {
FormatToken star = tokenUtils.getNextToken(start, JsTokenId.OPERATOR_MULTIPLICATION);
if (star != null) {
FormatToken prev = star.previous();
if (prev != null && (prev.getKind() == FormatToken.Kind.BEFORE_BINARY_OPERATOR
|| prev.getKind() == FormatToken.Kind.BEFORE_BINARY_OPERATOR_WRAP)) {
tokenStream.removeToken(prev);
}
prev = star.previous();
if (prev != null && (prev.getKind() == FormatToken.Kind.BEFORE_BINARY_OPERATOR
|| prev.getKind() == FormatToken.Kind.BEFORE_BINARY_OPERATOR_WRAP)) {
tokenStream.removeToken(prev);
}
FormatToken next = star.next();
if (next != null && (next.getKind() == FormatToken.Kind.AFTER_BINARY_OPERATOR
|| next.getKind() == FormatToken.Kind.AFTER_BINARY_OPERATOR_WRAP)) {
tokenStream.removeToken(next);
}
next = star.next();
if (next != null && (next.getKind() == FormatToken.Kind.AFTER_BINARY_OPERATOR
|| next.getKind() == FormatToken.Kind.AFTER_BINARY_OPERATOR_WRAP)) {
tokenStream.removeToken(next);
}
}
}
FormatToken leftParen = tokenUtils.getNextToken(start, JsTokenId.BRACKET_LEFT_PAREN, getStart(body));
if (leftParen != null) {
FormatToken previous = leftParen.previous();
if (previous != null) {
TokenUtils.appendToken(previous, FormatToken.forFormat(
functionNode.isAnonymous()
? FormatToken.Kind.BEFORE_ANONYMOUS_FUNCTION_DECLARATION
: FormatToken.Kind.BEFORE_FUNCTION_DECLARATION));
}
handleFunctionParameters(functionNode, leftParen);
// mark left brace of block - this works if function node
// start offset is offset of the left brace
FormatToken leftBrace = tokenUtils.getNextToken(getStart(functionNode),
JsTokenId.BRACKET_LEFT_CURLY, getFinish(functionNode));
if (leftBrace != null) {
previous = leftBrace.previous();
if (previous != null) {
TokenUtils.appendToken(previous, FormatToken.forFormat(
FormatToken.Kind.BEFORE_FUNCTION_DECLARATION_BRACE));
}
}
if (functionNode.isStatement() && !functionNode.isAnonymous()) {
FormatToken rightBrace = tokenUtils.getPreviousToken(getFinish(functionNode),
JsTokenId.BRACKET_RIGHT_CURLY,
leftBrace != null ? leftBrace.getOffset() : start);
if (rightBrace != null) {
TokenUtils.appendToken(rightBrace, FormatToken.forFormat(
FormatToken.Kind.AFTER_STATEMENT));
}
}
markEndCurlyBrace(functionNode);
}
}
return false;
}
@Override
public Node leaveFunctionNode(FunctionNode functionNode) {
leaveBlock(functionNode.getBody());
return null;
}
@Override
public boolean enterImportNode(ImportNode importNode) {
int finish = getFinish(importNode);
FormatToken token = tokenUtils.getNextToken(finish, JsTokenId.OPERATOR_SEMICOLON);
if (token != null) {
// we treat the import as statement
TokenUtils.appendTokenAfterLastVirtual(token, FormatToken.forFormat(FormatToken.Kind.AFTER_STATEMENT));
}
return false;
}
@Override
public boolean enterExportNode(ExportNode exportNode) {
int finish = getFinish(exportNode);
FormatToken token = tokenUtils.getNextToken(finish, JsTokenId.OPERATOR_SEMICOLON);
if (token != null) {
// we treat the import as statement
TokenUtils.appendTokenAfterLastVirtual(token, FormatToken.forFormat(FormatToken.Kind.AFTER_STATEMENT));
}
// the complex export nodes are included in top level function body anyway
// so we do not want to to visit further
if (exportNode.isDefault()) {
return super.enterExportNode(exportNode);
}
return false;
}
@Override
public boolean enterCallNode(CallNode callNode) {
FormatToken leftBrace = tokenUtils.getNextToken(getFinish(callNode.getFunction()),
JsTokenId.BRACKET_LEFT_PAREN, getFinish(callNode));
if (leftBrace != null) {
FormatToken previous = leftBrace.previous();
if (previous != null) {
TokenUtils.appendToken(previous, FormatToken.forFormat(FormatToken.Kind.BEFORE_FUNCTION_CALL));
}
// mark the within parenthesis places
// remove original paren marks
FormatToken mark = leftBrace.next();
assert mark != null && mark.getKind() == FormatToken.Kind.AFTER_LEFT_PARENTHESIS : mark;
tokenStream.removeToken(mark);
// there is -1 as on the finish position may be some outer paren
// so we really need the position precisely
int stopMark = getStart(callNode);
// lets calculate stop mark precisely to not to catch on arguments
// parens in broken source
List<Expression> args = callNode.getArgs();
if (!args.isEmpty()) {
stopMark = getFinish(args.get(args.size() - 1));
}
FormatToken rightBrace = tokenUtils.getPreviousToken(getFinish(callNode) - 1,
JsTokenId.BRACKET_RIGHT_PAREN, stopMark);
if (rightBrace != null) {
previous = TokenUtils.findVirtualToken(rightBrace,
FormatToken.Kind.BEFORE_RIGHT_PARENTHESIS, true);
// this might happen for sanitization inserted paren
if (previous != null) {
tokenStream.removeToken(previous);
}
}
// place the new marks
if (!callNode.getArgs().isEmpty()) {
TokenUtils.appendToken(leftBrace, FormatToken.forFormat(
FormatToken.Kind.AFTER_FUNCTION_CALL_PARENTHESIS));
if (rightBrace != null) {
previous = rightBrace.previous();
if (previous != null) {
TokenUtils.appendToken(previous, FormatToken.forFormat(
FormatToken.Kind.BEFORE_FUNCTION_CALL_PARENTHESIS));
}
}
}
// place function arguments marks
for (Node arg : callNode.getArgs()) {
FormatToken argToken = tokenUtils.getNextToken(getStart(arg), null);
if (argToken != null) {
FormatToken beforeArg = argToken.previous();
if (beforeArg != null) {
TokenUtils.appendToken(beforeArg,
FormatToken.forFormat(FormatToken.Kind.BEFORE_FUNCTION_CALL_ARGUMENT));
}
}
}
}
handleFunctionCallChain(callNode);
return super.enterCallNode(callNode);
}
@Override
public boolean enterClassNode(ClassNode classNode) {
handleDecorators(classNode.getDecorators());
Expression heritage = classNode.getClassHeritage();
if (heritage != null) {
heritage.accept(this);
FormatToken extendsToken = tokenUtils.getPreviousToken(getStart(heritage), JsTokenId.KEYWORD_EXTENDS, getStart(classNode));
if (extendsToken != null) {
FormatToken token = extendsToken.previous();
if (token != null) {
TokenUtils.appendToken(token, FormatToken.forFormat(FormatToken.Kind.BEFORE_CLASS_EXTENDS));
}
}
}
// indentation mark
FormatToken formatToken = tokenUtils.getNextToken(heritage != null ? getFinish(heritage) : getStart(classNode),
JsTokenId.BRACKET_LEFT_CURLY, true);
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.INDENTATION_INC));
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.AFTER_CLASS_START));
FormatToken previous = formatToken.previous();
if (previous != null) {
TokenUtils.appendToken(previous, FormatToken.forFormat(
FormatToken.Kind.BEFORE_CLASS_DECLARATION_BRACE));
}
}
PropertyNode constructor = classNode.getConstructor();
if (constructor != null) {
// generated default constructor has range equal to class
if (constructor.getStart() != classNode.getStart()
|| constructor.getFinish() != classNode.getFinish()) {
handleDecorators(constructor.getDecorators());
handleClassElement(constructor, getStart(constructor));
}
}
for (Node property : classNode.getClassElements()) {
PropertyNode propertyNode = (PropertyNode) property;
handleDecorators(propertyNode.getDecorators());
handleClassElement(propertyNode, getStart(propertyNode));
}
// put indentation mark after non white token preceeding curly bracket
formatToken = tokenUtils.getPreviousNonWhiteToken(getFinish(classNode) - 1,
getStart(classNode), JsTokenId.BRACKET_RIGHT_CURLY, true);
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.BEFORE_CLASS_END));
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.INDENTATION_DEC));
}
markEndCurlyBrace(classNode);
return false;
}
@Override
public boolean enterObjectNode(ObjectNode objectNode) {
// indentation mark
FormatToken formatToken = tokenUtils.getPreviousToken(getStart(objectNode), JsTokenId.BRACKET_LEFT_CURLY, true);
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.INDENTATION_INC));
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.AFTER_LEFT_BRACE));
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.AFTER_OBJECT_START));
FormatToken previous = formatToken.previous();
if (previous != null) {
TokenUtils.appendToken(previous, FormatToken.forFormat(FormatToken.Kind.BEFORE_OBJECT));
}
}
int objectFinish = getFinish(objectNode);
for (Node property : objectNode.getElements()) {
property.accept(this);
PropertyNode propertyNode = (PropertyNode) property;
if (propertyNode.getGetter() != null) {
FunctionNode getter = (FunctionNode) propertyNode.getGetter();
markPropertyFinish(getFinish(getter), objectFinish, false);
}
if (propertyNode.getSetter() != null) {
FunctionNode setter = (FunctionNode) propertyNode.getSetter();
markPropertyFinish(getFinish(setter), objectFinish, false);
}
// mark property end
markPropertyFinish(getFinish(property), objectFinish, true);
}
// put indentation mark after non white token preceeding curly bracket
formatToken = tokenUtils.getPreviousNonWhiteToken(getFinish(objectNode) - 1,
getStart(objectNode), JsTokenId.BRACKET_RIGHT_CURLY, true);
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.BEFORE_OBJECT_END));
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.BEFORE_RIGHT_BRACE));
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.INDENTATION_DEC));
}
return false;
}
@Override
public boolean enterPropertyNode(PropertyNode propertyNode) {
FormatToken colon = tokenUtils.getNextToken(getFinish(propertyNode.getKey()),
JsTokenId.OPERATOR_COLON, getFinish(propertyNode));
if (colon != null) {
TokenUtils.appendToken(colon, FormatToken.forFormat(FormatToken.Kind.AFTER_PROPERTY_OPERATOR));
FormatToken before = colon.previous();
if (before != null) {
TokenUtils.appendTokenAfterLastVirtual(before, FormatToken.forFormat(FormatToken.Kind.BEFORE_PROPERTY_OPERATOR));
}
}
return super.enterPropertyNode(propertyNode);
}
@Override
public boolean enterSwitchNode(SwitchNode switchNode) {
// within parens spaces
markSpacesWithinParentheses(switchNode);
// mark space before left brace
markSpacesBeforeBrace(switchNode);
FormatToken formatToken = tokenUtils.getNextToken(getStart(switchNode), JsTokenId.BRACKET_LEFT_CURLY, true);
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.INDENTATION_INC));
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.AFTER_BLOCK_START));
}
List<CaseNode> nodes = new ArrayList<>(switchNode.getCases());
if (switchNode.getDefaultCase() != null) {
nodes.add(switchNode.getDefaultCase());
}
for (CaseNode caseNode : nodes) {
int index = getFinish(caseNode);
List<Statement> statements = caseNode.getStatements();
if (!statements.isEmpty()) {
index = getStart(statements.get(0));
}
formatToken = tokenUtils.getPreviousToken(index, JsTokenId.OPERATOR_COLON);
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.AFTER_CASE));
}
}
// put indentation mark after non white token preceeding curly bracket
formatToken = tokenUtils.getPreviousNonWhiteToken(getFinish(switchNode),
getStart(switchNode), JsTokenId.BRACKET_RIGHT_CURLY, true);
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.INDENTATION_DEC));
}
markEndCurlyBrace(switchNode);
return super.enterSwitchNode(switchNode);
}
@Override
public boolean enterUnaryNode(UnaryNode unaryNode) {
TokenType type = unaryNode.tokenType();
if (UNARY_TYPES.contains(type)) {
if (TokenType.DECPOSTFIX.equals(type) || TokenType.INCPOSTFIX.equals(type)) {
FormatToken formatToken = tokenUtils.getPreviousToken(getFinish(unaryNode),
TokenType.DECPOSTFIX.equals(type) ? JsTokenId.OPERATOR_DECREMENT : JsTokenId.OPERATOR_INCREMENT);
if (formatToken != null) {
formatToken = formatToken.previous();
if (formatToken != null) {
TokenUtils.appendToken(formatToken,
FormatToken.forFormat(FormatToken.Kind.BEFORE_UNARY_OPERATOR));
}
}
} else {
FormatToken formatToken = tokenUtils.getNextToken(getStart(unaryNode), null);
// may be null when we are out of formatted area
if (formatToken != null) {
// remove around binary operator tokens added during token
// stream creation
if (TokenType.ADD.equals(type) || TokenType.SUB.equals(type)) {
assert formatToken.getId() == JsTokenId.OPERATOR_PLUS
|| formatToken.getId() == JsTokenId.OPERATOR_MINUS : formatToken;
// we remove blindly inserted binary op markers
FormatToken toRemove = TokenUtils.findVirtualToken(formatToken,
FormatToken.Kind.BEFORE_BINARY_OPERATOR, true);
assert toRemove != null
&& toRemove.getKind() == FormatToken.Kind.BEFORE_BINARY_OPERATOR : toRemove;
tokenStream.removeToken(toRemove);
toRemove = TokenUtils.findVirtualToken(formatToken,
FormatToken.Kind.BEFORE_BINARY_OPERATOR_WRAP, true);
assert toRemove != null
&& toRemove.getKind() == FormatToken.Kind.BEFORE_BINARY_OPERATOR_WRAP : toRemove;
tokenStream.removeToken(toRemove);
toRemove = TokenUtils.findVirtualToken(formatToken,
FormatToken.Kind.AFTER_BINARY_OPERATOR, false);
assert toRemove != null
&& toRemove.getKind() == FormatToken.Kind.AFTER_BINARY_OPERATOR : toRemove;
tokenStream.removeToken(toRemove);
toRemove = TokenUtils.findVirtualToken(formatToken,
FormatToken.Kind.AFTER_BINARY_OPERATOR_WRAP, false);
assert toRemove != null
&& toRemove.getKind() == FormatToken.Kind.AFTER_BINARY_OPERATOR_WRAP : toRemove;
tokenStream.removeToken(toRemove);
}
TokenUtils.appendToken(formatToken,
FormatToken.forFormat(FormatToken.Kind.AFTER_UNARY_OPERATOR));
}
}
}
return super.enterUnaryNode(unaryNode);
}
@Override
public boolean enterTernaryNode(TernaryNode ternaryNode) {
int start = getFinish(ternaryNode.getTest());
FormatToken question = tokenUtils.getNextToken(start, JsTokenId.OPERATOR_TERNARY);
if (question != null) {
FormatToken previous = question.previous();
if (previous != null) {
TokenUtils.appendToken(previous, FormatToken.forFormat(FormatToken.Kind.BEFORE_TERNARY_OPERATOR));
TokenUtils.appendToken(previous, FormatToken.forFormat(FormatToken.Kind.BEFORE_TERNARY_OPERATOR_WRAP));
}
TokenUtils.appendToken(question, FormatToken.forFormat(FormatToken.Kind.AFTER_TERNARY_OPERATOR));
TokenUtils.appendToken(question, FormatToken.forFormat(FormatToken.Kind.AFTER_TERNARY_OPERATOR_WRAP));
FormatToken colon = tokenUtils.getPreviousToken(getStart(ternaryNode.getFalseExpression()), JsTokenId.OPERATOR_COLON);
if (colon != null) {
previous = colon.previous();
if (previous != null) {
TokenUtils.appendToken(previous, FormatToken.forFormat(FormatToken.Kind.BEFORE_TERNARY_OPERATOR));
TokenUtils.appendToken(previous, FormatToken.forFormat(FormatToken.Kind.BEFORE_TERNARY_OPERATOR_WRAP));
}
TokenUtils.appendToken(colon, FormatToken.forFormat(FormatToken.Kind.AFTER_TERNARY_OPERATOR));
TokenUtils.appendToken(colon, FormatToken.forFormat(FormatToken.Kind.AFTER_TERNARY_OPERATOR_WRAP));
}
}
return super.enterTernaryNode(ternaryNode);
}
@Override
public boolean enterCatchNode(CatchNode catchNode) {
// within parens spaces
markSpacesWithinParentheses(catchNode, getStart(catchNode), getStart(catchNode.getBody()),
FormatToken.Kind.AFTER_CATCH_PARENTHESIS, FormatToken.Kind.BEFORE_CATCH_PARENTHESIS);
// mark space before left brace
markSpacesBeforeBrace(catchNode.getBody(), FormatToken.Kind.BEFORE_CATCH_BRACE);
markEndCurlyBrace(catchNode.getBody());
return super.enterCatchNode(catchNode);
}
@Override
public boolean enterTryNode(TryNode tryNode) {
// mark space before left brace
markSpacesBeforeBrace(tryNode.getBody(), FormatToken.Kind.BEFORE_TRY_BRACE);
Block finallyBody = tryNode.getFinallyBody();
if (finallyBody != null) {
// mark space before finally left brace
markSpacesBeforeBrace(tryNode.getFinallyBody(), FormatToken.Kind.BEFORE_FINALLY_BRACE);
}
markEndCurlyBrace(tryNode.getBody());
markEndCurlyBrace(finallyBody);
return super.enterTryNode(tryNode);
}
@Override
public boolean enterLiteralNode(LiteralNode literalNode) {
Object value = literalNode.getValue();
if (value instanceof Node[]) {
int start = getStart(literalNode);
int finish = getFinish(literalNode);
FormatToken leftBracket = tokenUtils.getNextToken(start, JsTokenId.BRACKET_LEFT_BRACKET, finish);
if (leftBracket != null) {
if (leftBracket.previous() != null) {
// mark beginning of the array (see issue #250150)
TokenUtils.appendToken(leftBracket.previous(), FormatToken.forFormat(FormatToken.Kind.BEFORE_ARRAY));
}
TokenUtils.appendToken(leftBracket, FormatToken.forFormat(FormatToken.Kind.AFTER_ARRAY_LITERAL_START));
TokenUtils.appendToken(leftBracket, FormatToken.forFormat(FormatToken.Kind.AFTER_ARRAY_LITERAL_BRACKET));
TokenUtils.appendToken(leftBracket, FormatToken.forFormat(FormatToken.Kind.INDENTATION_INC));
FormatToken rightBracket = tokenUtils.getPreviousToken(finish - 1, JsTokenId.BRACKET_RIGHT_BRACKET, start + 1);
if (rightBracket != null) {
FormatToken previous = rightBracket.previous();
if (previous != null) {
TokenUtils.appendToken(previous, FormatToken.forFormat(FormatToken.Kind.BEFORE_ARRAY_LITERAL_END));
TokenUtils.appendToken(previous, FormatToken.forFormat(FormatToken.Kind.BEFORE_ARRAY_LITERAL_BRACKET));
TokenUtils.appendToken(previous, FormatToken.forFormat(FormatToken.Kind.INDENTATION_DEC));
}
}
}
if (literalNode.isArray()) {
Node[] items = ((LiteralNode.ArrayLiteralNode) literalNode).getValue();
if (items != null && items.length > 0) {
int prevItemFinish = start;
for (int i = 1; i < items.length; i++) {
Node prevItem = items[i - 1];
if (prevItem != null) {
prevItemFinish = getFinish(prevItem);
}
FormatToken comma = tokenUtils.getNextToken(prevItemFinish, JsTokenId.OPERATOR_COMMA, finish);
if (comma != null) {
prevItemFinish = comma.getOffset();
TokenUtils.appendTokenAfterLastVirtual(comma,
FormatToken.forFormat(FormatToken.Kind.AFTER_ARRAY_LITERAL_ITEM));
}
}
}
}
}
return super.enterLiteralNode(literalNode);
}
@Override
public boolean enterVarNode(VarNode varNode) {
if (varNode.isExport() || varNode.isDestructuring()) {
return false;
}
int finish = getFinish(varNode) - 1;
Token nextToken = tokenUtils.getNextNonEmptyToken(finish);
if (nextToken != null && nextToken.id() == JsTokenId.OPERATOR_COMMA) {
FormatToken formatToken = tokenStream.getToken(ts.offset());
if (formatToken != null) {
FormatToken next = formatToken.next();
assert next != null && next.getKind() == FormatToken.Kind.AFTER_COMMA : next;
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.AFTER_VAR_DECLARATION));
}
}
return super.enterVarNode(varNode);
}
// handles both standard and arrow functions
private void handleFunctionParameters(FunctionNode functionNode, FormatToken leftParen) {
if (leftParen != null) {
// remove original paren marks
FormatToken mark = leftParen.next();
assert mark != null && mark.getKind() == FormatToken.Kind.AFTER_LEFT_PARENTHESIS : mark;
tokenStream.removeToken(mark);
FormatToken rightParen = tokenUtils.getPreviousToken(getStart(functionNode.getBody()),
JsTokenId.BRACKET_RIGHT_PAREN, leftParen.getOffset());
if (rightParen != null) {
FormatToken previous = rightParen.previous();
assert previous != null && previous.getKind() == FormatToken.Kind.BEFORE_RIGHT_PARENTHESIS : previous;
tokenStream.removeToken(previous);
}
// place the new marks
if (!functionNode.getParameters().isEmpty()) {
TokenUtils.appendToken(leftParen, FormatToken.forFormat(
FormatToken.Kind.AFTER_FUNCTION_DECLARATION_PARENTHESIS));
if (rightParen != null) {
FormatToken previous = rightParen.previous();
if (previous != null) {
TokenUtils.appendToken(previous, FormatToken.forFormat(
FormatToken.Kind.BEFORE_FUNCTION_DECLARATION_PARENTHESIS));
}
}
}
}
// place function parameters marks
for (IdentNode param : functionNode.getParameters()) {
FormatToken paramToken = tokenUtils.getNextToken(getStart(param), JsTokenId.IDENTIFIER);
if (paramToken != null) {
// there might be "a, ...z" for example so we want the mark before the rest
// parameter
Token previousNonEmpty = tokenUtils.getPreviousNonEmptyToken(paramToken.getOffset());
if (previousNonEmpty != null && previousNonEmpty.id() == JsTokenId.OPERATOR_REST) {
paramToken = tokenStream.getToken(ts.offset());
}
FormatToken beforeIdent = paramToken.previous();
if (beforeIdent != null) {
TokenUtils.appendToken(beforeIdent,
FormatToken.forFormat(FormatToken.Kind.BEFORE_FUNCTION_DECLARATION_PARAMETER));
}
}
}
}
private void handleFunctionCallChain(CallNode callNode) {
Node function = callNode.getFunction();
if (function instanceof AccessNode) {
Node base = ((AccessNode) function).getBase();
if (base instanceof CallNode) {
CallNode chained = (CallNode) base;
int finish = getFinish(chained);
FormatToken formatToken = tokenUtils.getNextToken(finish, JsTokenId.OPERATOR_DOT);
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken,
FormatToken.forFormat(FormatToken.Kind.AFTER_CHAIN_CALL_DOT));
formatToken = formatToken.previous();
if (formatToken != null) {
TokenUtils.appendToken(formatToken, FormatToken.forFormat(FormatToken.Kind.BEFORE_CHAIN_CALL_DOT));
}
}
}
}
}
private boolean handleLoop(LoopNode loopNode, FormatToken.Kind afterStart) {
Block body = loopNode.getBody();
if (isVirtual(body)) {
handleVirtualBlock(body, afterStart);
return true;
}
return false;
}
private void handleStandardBlock(Block block) {
handleBlockContent(block, true);
// indentation mark & block start
FormatToken formatToken = tokenUtils.getPreviousToken(getStart(block), JsTokenId.BRACKET_LEFT_CURLY, true);
if (formatToken != null && !isScript(block)) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.INDENTATION_INC));
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.AFTER_BLOCK_START));
}
// put indentation mark after non white token preceeding curly bracket
// XXX optimize ?
formatToken = tokenUtils.getNextToken(getFinish(block) - 1, JsTokenId.BRACKET_RIGHT_CURLY);
if (formatToken != null) {
formatToken = tokenUtils.getPreviousNonWhiteToken(formatToken.getOffset(),
getStart(block), JsTokenId.BRACKET_RIGHT_CURLY, true);
if (formatToken != null && !isScript(block)) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.INDENTATION_DEC));
}
}
}
private void handleVirtualBlock(Block block, FormatToken.Kind afterBlock) {
handleVirtualBlock(block, FormatToken.Kind.INDENTATION_INC, FormatToken.Kind.INDENTATION_DEC,
afterBlock, true);
}
private void handleVirtualBlock(Block block, FormatToken.Kind indentationInc,
FormatToken.Kind indentationDec, FormatToken.Kind afterBlock, boolean markStatements) {
assert isVirtual(block) : block;
boolean assertsEnabled = false;
assert assertsEnabled = true;
if (assertsEnabled) {
if (block.getStatements().size() > 1) {
int count = 0;
// there may be multiple var statements due to the comma
// separated vars translated to multiple statements in ast
for (Node node : block.getStatements()) {
if (!(node instanceof VarNode)) {
count++;
}
}
assert count <= 1;
}
}
if (block.getStart() >= block.getFinish()/*block.getStatements().isEmpty()*/) {
return;
}
handleBlockContent(block, markStatements);
//Node statement = block.getStatements().get(0);
// indentation mark & block start
Token token = tokenUtils.getPreviousNonEmptyToken(getStart(block));
if (token != null) {
FormatToken formatToken = tokenStream.getToken(ts.offset());
if (!isScript(block)) {
if (formatToken == null && ts.offset() <= formatFinish) {
formatToken = tokenStream.getTokens().get(0);
}
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(indentationInc));
if (afterBlock != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(afterBlock));
}
}
}
}
// put indentation mark after non white token
int finish = getFinish(block);
// empty statement has start == finish
FormatToken formatToken = tokenUtils.getPreviousToken(
block.getStart() < finish ? finish - 1 : finish, null, true);
if (formatToken != null && !isScript(block)) {
while (formatToken != null && (formatToken.getKind() == FormatToken.Kind.EOL
|| formatToken.getKind() == FormatToken.Kind.WHITESPACE
|| formatToken.getKind() == FormatToken.Kind.LINE_COMMENT
|| formatToken.getKind() == FormatToken.Kind.BLOCK_COMMENT
|| formatToken.getKind() == FormatToken.Kind.DOC_COMMENT)) {
formatToken = formatToken.previous();
}
if (block.getStatementCount() == 0 && block.getStart() < block.getFinish()) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.AFTER_STATEMENT));
}
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(indentationDec));
}
}
private void handleBlockContent(Block block, boolean markStatements) {
handleBlockContent(block.getStatements(), markStatements);
}
private void handleBlockContent(List<Statement> statements, boolean markStatements) {
// statements
boolean destructuring = false;
for (int i = 0; i < statements.size(); i++) {
Node statement = statements.get(i);
statement.accept(this);
if (markStatements) {
int start = getStart(statement);
int finish = getFinish(statement);
/*
* What do we solve here? Unfortunately nashorn parses single
* var statement as (possibly) multiple VarNodes. For example:
* var a=1,b=2; is parsed to two VarNodes. The first covering a=1,
* the second b=2. So we iterate subsequent VarNodes searching the
* last one and the proper finish token.
*/
if (statement instanceof VarNode) {
if (((VarNode) statement).isDestructuring()) {
destructuring = true;
continue;
}
if (!isDeclaration((VarNode) statement)) {
int index = i + 1;
Node lastVarNode = statement;
while (i + 1 < statements.size()) {
Node next = statements.get(++i);
if (!(next instanceof VarNode) || isDeclaration((VarNode) next)) {
i--;
break;
} else {
Token token = tokenUtils.getPreviousNonEmptyToken(getStart(next));
if (token != null && (JsTokenId.KEYWORD_VAR == token.id()
|| JsTokenId.KEYWORD_CONST == token.id()
|| JsTokenId.RESERVED_LET == token.id())) {
i--;
break;
}
}
lastVarNode = next;
}
for (int j = index; j < i + 1; j++) {
Node skipped = statements.get(j);
skipped.accept(this);
}
finish = getFinish(lastVarNode);
}
}
int searchOffset = start < finish ? finish - 1 : finish;
// if it is destructuring the finish is at the end
if (statement instanceof ExpressionStatement && destructuring) {
searchOffset = finish;
}
destructuring = false;
FormatToken formatToken = tokenUtils.getPreviousToken(searchOffset, null);
while (formatToken != null && (formatToken.getKind() == FormatToken.Kind.EOL
|| formatToken.getKind() == FormatToken.Kind.WHITESPACE
|| formatToken.getKind() == FormatToken.Kind.LINE_COMMENT
|| formatToken.getKind() == FormatToken.Kind.BLOCK_COMMENT
|| formatToken.getKind() == FormatToken.Kind.DOC_COMMENT)) {
formatToken = formatToken.previous();
}
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken,
FormatToken.forFormat(FormatToken.Kind.AFTER_STATEMENT), true);
}
}
}
}
private void handleClassElement(PropertyNode property, int start) {
property.accept(this);
PropertyNode propertyNode = (PropertyNode) property;
if (propertyNode.getGetter() != null) {
FunctionNode getter = (FunctionNode) propertyNode.getGetter();
markClassElementFinish(getStart(getter), getFinish(getter), start,
false, propertyNode.getGetter());
}
if (propertyNode.getSetter() != null) {
FunctionNode setter = (FunctionNode) propertyNode.getSetter();
markClassElementFinish(getStart(setter), getFinish(setter), start,
false, propertyNode.getSetter());
}
// mark property end
markClassElementFinish(getStart(property), getFinish(property), start,
true, propertyNode.getValue());
}
private void handleDecorators(List<Expression> decorators) {
for (Expression decorator : decorators) {
FormatToken formatToken = tokenUtils.getPreviousNonWhiteToken(getFinish(decorator) - 1, -1, null, true);
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken, FormatToken.forFormat(FormatToken.Kind.AFTER_DECORATOR));
}
}
}
private void markSpacesWithinParentheses(SwitchNode node) {
int leftStart = getStart(node);
// the { has to be there for switch
FormatToken token = tokenUtils.getNextToken(leftStart, JsTokenId.BRACKET_LEFT_CURLY, getFinish(node));
if (token != null) {
markSpacesWithinParentheses(node, leftStart, token.getOffset(),
FormatToken.Kind.AFTER_SWITCH_PARENTHESIS, FormatToken.Kind.BEFORE_SWITCH_PARENTHESIS);
}
}
private void markSpacesBeforeBrace(SwitchNode node) {
int leftStart = getStart(node);
// the { has to be there for switch
FormatToken token = tokenUtils.getNextToken(leftStart, JsTokenId.BRACKET_LEFT_CURLY, getFinish(node));
if (token != null) {
FormatToken previous = token.previous();
if (previous != null) {
TokenUtils.appendToken(previous, FormatToken.forFormat(FormatToken.Kind.BEFORE_SWITCH_BRACE));
}
}
}
/**
* Method putting formatting tokens for within parenthesis rule. Note
* that this method may be more secure as it can search for the left paren
* from start of the node and for the right from the body of the node
* avoiding possibly wrong offset of expressions/conditions.
*
* @param outerNode the node we are marking, such as if, while, with
* @param leftStart from where to start search to the right for the left paren
* @param rightStart from where to start search to the left for the right paren
* @param leftMark where to stop searching for the left paren
* @param rightMark where to stop searching for the right paren
*/
private void markSpacesWithinParentheses(Node outerNode, int leftStart,
int rightStart, FormatToken.Kind leftMark, FormatToken.Kind rightMark) {
FormatToken leftParen = tokenUtils.getNextToken(leftStart,
JsTokenId.BRACKET_LEFT_PAREN, getFinish(outerNode));
if (leftParen != null) {
FormatToken mark = leftParen.next();
assert mark != null && mark.getKind() == FormatToken.Kind.AFTER_LEFT_PARENTHESIS : mark;
if (mark.getKind() == FormatToken.Kind.AFTER_LEFT_PARENTHESIS) {
tokenStream.removeToken(mark);
}
TokenUtils.appendToken(leftParen, FormatToken.forFormat(leftMark));
FormatToken rightParen = tokenUtils.getPreviousToken(rightStart,
JsTokenId.BRACKET_RIGHT_PAREN, getStart(outerNode));
if (rightParen != null) {
FormatToken previous = rightParen.previous();
assert previous != null && previous.getKind() == FormatToken.Kind.BEFORE_RIGHT_PARENTHESIS : previous;
if (previous.getKind() == FormatToken.Kind.BEFORE_RIGHT_PARENTHESIS) {
tokenStream.removeToken(previous);
}
previous = rightParen.previous();
if (previous != null) {
TokenUtils.appendToken(previous, FormatToken.forFormat(rightMark));
}
}
}
}
private void markSpacesBeforeBrace(Block block, FormatToken.Kind mark) {
FormatToken brace = tokenUtils.getPreviousToken(getStart(block), null,
getStart(block) - 1);
if (brace != null) {
FormatToken previous = brace.previous();
if (previous != null) {
TokenUtils.appendToken(previous, FormatToken.forFormat(mark));
}
}
}
private void markPropertyFinish(int finish, int objectFinish, boolean checkDuplicity) {
FormatToken formatToken = tokenUtils.getNextToken(finish, JsTokenId.OPERATOR_COMMA, objectFinish - 1);
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken,
FormatToken.forFormat(FormatToken.Kind.AFTER_PROPERTY), checkDuplicity);
}
}
private void markClassElementFinish(int start, int finish, int classFinish,
boolean checkDuplicity, Expression value) {
FormatToken formatToken;
if (value instanceof FunctionNode) {
// method
formatToken = tokenUtils.getPreviousToken(finish, JsTokenId.BRACKET_RIGHT_CURLY, classFinish);
} else {
// property
formatToken = tokenUtils.getPreviousToken(start < finish ? finish - 1 : finish, null);
while (formatToken != null && (formatToken.getKind() == FormatToken.Kind.EOL
|| formatToken.getKind() == FormatToken.Kind.WHITESPACE
|| formatToken.getKind() == FormatToken.Kind.LINE_COMMENT
|| formatToken.getKind() == FormatToken.Kind.BLOCK_COMMENT
|| formatToken.getKind() == FormatToken.Kind.DOC_COMMENT)) {
formatToken = formatToken.previous();
}
}
if (formatToken != null) {
TokenUtils.appendTokenAfterLastVirtual(formatToken,
FormatToken.forFormat(FormatToken.Kind.AFTER_ELEMENT), checkDuplicity);
}
}
private void markEndCurlyBrace(Node node) {
if (node != null) {
FormatToken formatToken = tokenStream.getToken(getFinish(node) - 1);
if (formatToken != null) {
while (formatToken.isVirtual()) {
formatToken = formatToken.previous();
if (formatToken == null) {
return;
}
}
if (formatToken.getId() == JsTokenId.BRACKET_RIGHT_CURLY) {
TokenUtils.appendToken(formatToken, FormatToken.forFormat(FormatToken.Kind.AFTER_END_BRACE));
}
}
}
}
/**
* Finds the next non empty token first and then move back to non whitespace
* token.
*
* @param block case block
* @return format token
*/
private FormatToken getCaseEndToken(int start, int finish) {
ts.move(finish);
if (!ts.moveNext() && !ts.movePrevious()) {
return null;
}
Token ret = null;
while (ts.moveNext()) {
Token token = ts.token();
if ((token.id() != JsTokenId.BLOCK_COMMENT && token.id() != JsTokenId.DOC_COMMENT
&& token.id() != JsTokenId.LINE_COMMENT && token.id() != JsTokenId.EOL
&& token.id() != JsTokenId.WHITESPACE)) {
ret = token;
break;
}
}
if (ret != null) {
while (ts.movePrevious() && ts.offset() >= start) {
Token current = ts.token();
if (current.id() != JsTokenId.WHITESPACE) {
ret = current;
break;
}
}
return tokenUtils.getFallback(ts.offset(), true);
}
return null;
}
private int getStart(Node node) {
// unfortunately in binary node the token represents operator
// so string fix would not work
if (node instanceof BinaryNode) {
return getStart((BinaryNode) node);
}
if (node instanceof FunctionNode) {
return getFunctionStart((FunctionNode) node);
}
// All this magic is because nashorn nodes and tokens don't contain the
// quotes for string. Due to this we call this method to add 1 to start
// in case it is string literal.
int start = node.getStart();
long firstToken = node.getToken();
TokenType type = com.oracle.js.parser.Token.descType(firstToken);
if (type.equals(TokenType.STRING) || type.equals(TokenType.ESCSTRING)) {
ts.move(start - 1);
if (ts.moveNext()) {
Token<? extends JsTokenId> token = ts.token();
if (token.id() == JsTokenId.STRING_BEGIN) {
start--;
}
}
}
return start;
}
private int getStart(BinaryNode node) {
return getStart(node.lhs());
}
private static int getFunctionStart(FunctionNode node) {
return com.oracle.js.parser.Token.descPosition(node.getFirstToken());
}
private int getFinish(Node node) {
// we are fixing the wrong finish offset here
// only function node has last token
if (node instanceof FunctionNode) {
FunctionNode function = (FunctionNode) node;
if (node.getStart() == node.getFinish()) {
long lastToken = function.getLastToken();
TokenType type = com.oracle.js.parser.Token.descType(lastToken);
int finish;
if (type == TokenType.EOL) {
// when eol token length just stores line number
finish = com.oracle.js.parser.Token.descPosition(lastToken);
} else {
finish = com.oracle.js.parser.Token.descPosition(lastToken)
+ com.oracle.js.parser.Token.descLength(lastToken);
}
// check if it is a string
if (com.oracle.js.parser.Token.descType(lastToken).equals(TokenType.STRING)) {
finish++;
}
return finish;
} else {
return node.getFinish();
}
} else if (node instanceof Block) {
// XXX in truffle the function body finish is at last statement
FunctionNode fn = lc.getCurrentFunction();
if (fn != null) {
if (fn.getBody() == node) {
return getFinish(fn);
}
}
} else if (node instanceof VarNode) {
VarNode var = (VarNode) node;
if (var.getInit() != null && (var.getInit() instanceof ClassNode)) {
return getFinish(var.getInit());
}
Token token = tokenUtils.getNextNonEmptyToken(getFinishFixed(node) - 1);
if (token != null && JsTokenId.OPERATOR_SEMICOLON == token.id()) {
return ts.offset() + 1;
} else {
return getFinishFixed(node);
}
}
return getFinishFixed(node);
}
private int getFinishFixed(Node node) {
// All this magic is because nashorn nodes and tokens don't contain the
// quotes for string. Due to this we call this method to add 1 to finish
// in case it is string literal.
int finish = node.getFinish();
ts.move(finish);
if (!ts.moveNext()) {
return finish;
}
Token<? extends JsTokenId> token = ts.token();
if (token.id() == JsTokenId.STRING_END || token.id() == JsTokenId.TEMPLATE_END) {
return finish + 1;
}
return finish;
}
private boolean isScript(Block node) {
if(!node.isFunctionBody()) {
return false;
}
FunctionNode functionNode = getLexicalContext().getCurrentFunction();
return functionNode != null && functionNode.isProgram();
}
private boolean isVirtual(Block block) {
return block.getStart() == block.getFinish()
|| com.oracle.js.parser.Token.descType(block.getToken()) != TokenType.LBRACE
|| block.isCatchBlock();
}
private boolean isDeclaration(VarNode varNode) {
if (varNode.isFunctionDeclaration() || varNode.isExport() || varNode.isDestructuring()) {
return true;
}
if (varNode.getInit() instanceof ClassNode) {
IdentNode cIdent = ((ClassNode) varNode.getInit()).getIdent();
IdentNode vIdent = varNode.getName();
// this is artificial var node for simple class declaration
if (cIdent != null
&& cIdent.getStart() == vIdent.getStart()
&& cIdent.getFinish() == vIdent.getFinish()) {
return true;
}
}
return false;
}
}