public static FilePosition between(FilePosition a, FilePosition b) { return instance( a.source(), a.endLineNo(), a.endCharInFile(), a.endCharInLine(), b.startLineNo(), b.startCharInFile(), b.startCharInLine()); }
void processToken(String text) { TokenClassification tClass = TokenClassification.classify(text); if (tClass == null) { return; } switch (tClass) { case LINEBREAK: // Allow external code to force line-breaks. // This allows us to create a composite-renderer that renders // original source code next to translated source code. emit("\n"); return; case SPACE: pendingSpace = true; return; case COMMENT: if (mark != null && lastLine != mark.startLineNo()) { newline(); lastLine = mark.startLineNo(); } else if ("/".equals(lastToken) || pendingSpace) { space(); } pendingSpace = false; emit(text); if (text.startsWith("//")) { newline(); pendingSpace = false; } else { pendingSpace = true; } return; default: break; } boolean spaceBefore = pendingSpace; pendingSpace = false; boolean spaceAfter = false; // Determine which pairs of tokens cannot be adjacent and put a space // between them. if (tClass == lastClass) { // Adjacent punctuation, strings, and words require space. // Numbers and words are both of type OTHER. // This decision may be revisited in the following to prevent // excessive space inside parentheses. spaceBefore = !"(".equals(lastToken); } else if (lastClass == TokenClassification.REGEX) { if (tClass == TokenClassification.OTHER || "/".equals(text)) { // Make sure words don't run into regex flags, and that / operator // does not combine with end of regex to make a line comment. spaceBefore = true; } } else if (tClass == TokenClassification.REGEX && "/".equals(lastToken)) { // Allowing these two tokens to run together could introduce a line // comment. spaceBefore = true; } else if (tClass == TokenClassification.OTHER && Character.isDigit(text.charAt(0)) && ".".equals(lastToken)) { // Following a dot operator with a number is illegal syntactically, but // this renderer should not allow any lexical confusion. spaceBefore = true; } if (tClass == TokenClassification.OTHER) { if ("}".equals(lastToken)) { spaceBefore = true; } if (isKeyword(text.toString())) { // Put a space between if and other keywords and the parenthesis. spaceAfter = true; } } // If this token is an open bracket, we want to indent, but not before // writing the token to avoid over-indenting the open bracket. if (text.length() == 1) { char ch0 = text.charAt(0); switch (ch0) { case '{': if (lastClass == TokenClassification.PUNCTUATION) { if (":".equals(lastToken)) { // See JSON test. spaceBefore = true; } else if (!(")".equals(lastToken) || "=".equals(lastToken))) { // If starting a block following a parenthesized condition, or // an object literal assigned. spaceBefore = !("(".equals(lastToken) || "[".equals(lastToken)); } } spaceAfter = true; break; case '[': if (")".equals(lastToken)) { spaceBefore = false; } spaceAfter = true; break; case '(': if (")".equals(lastToken)) { // Calling a parenthesized value. spaceBefore = false; } break; case '}': spaceBefore = !"{".equals(lastToken); spaceAfter = true; break; case ')': spaceBefore = false; spaceAfter = true; break; case ']': spaceBefore = !"}".equals(lastToken); spaceAfter = true; break; case ',': spaceBefore = false; spaceAfter = true; break; case ';': spaceBefore = false; spaceAfter = true; break; case ':': spaceBefore = ":".equals(lastToken); // Since :: is a token in ES4 spaceAfter = true; break; case '=': spaceBefore = true; spaceAfter = true; break; case '.': spaceBefore = lastToken != null && (TokenClassification.isNumber(lastToken) || ".".equals(lastToken)); spaceAfter = false; break; } } // Write any whitespace before the token. if (spaceBefore) { space(); } // Actually write the token. emit(text); pendingSpace = spaceAfter; lastClass = tClass; lastToken = text; if (mark != null) { lastLine = mark.startLineNo(); } }