com.google.javascript.jscomp.CodeConsumer Maven / Gradle / Ivy
Show all versions of closure-compiler-unshaded Show documentation
/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed 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 com.google.javascript.jscomp;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.jscomp.base.JSCompDoubles.isPositive;
import com.google.errorprone.annotations.ForOverride;
import com.google.javascript.rhino.Node;
/**
* Abstracted consumer of the CodeGenerator output.
*
* @see CodeGenerator
* @see CodePrinter
* @see InlineCostEstimator
*/
public abstract class CodeConsumer {
boolean statementNeedsEnded = false;
boolean statementStarted = false;
boolean sawFunction = false;
// State tracking for template literals. Remember that template literal substitutions can contain
// additional template literals.
private int templateLitDepth = 0;
private int templateLitSubDepth = 0;
/**
* Starts the source mapping for the given
* node at the current position.
*/
void startSourceMapping(Node node) {
}
/**
* Finishes the source mapping for the given
* node at the current position.
*/
void endSourceMapping(Node node) {
}
/**
* Indicates to the CodeConsumer that this Node might carry licensing information, and allows the
* code consumer to manage licenses as it sees fit.
*/
void trackLicenses(Node node) {}
/**
* Provides a means of interrupting the CodeGenerator. Derived classes should return false to stop
* further processing.
*/
boolean continueProcessing() {
return true;
}
/**
* Retrieve the last character of the last string sent to append.
*/
abstract char getLastChar();
/**
* Appends a (possibly multiline) string to the code, keeping track of the current line length and
* count.
*
* Clients should use this method rather than {@link #append()}. It sanitizes the input to
* {@link #append()}.
*/
final void add(String newcode) {
maybeEndStatement();
if (newcode.isEmpty()) {
return;
}
char c = newcode.charAt(0);
if ((isWordChar(c) || c == '\\') && isWordChar(getLastChar())) {
// need space to separate. This is not pretty printing.
// For example: "return foo;"
append(" ");
} else if (c == '/' && getLastChar() == '/') {
// Do not allow a forward slash to appear after a DIV.
// For example,
// REGEXP DIV REGEXP
// is valid and should print like
// / // / /
append(" ");
} else if ((c == '"' || c == '\'') && isWordChar(getLastChar())) {
maybeInsertSpace();
}
// Iterate through the new code and add each contained line, followed by a break. Remember that
// the string may start and end in the middle of a line. We do this rather primitively because
// this method is called frequently and most invocations have only one line.
// TODO(nickreid): There are other possible newline characters recognized by the JS spec. We
// should also be considering them.
int startOfLine = 0;
int endOfLine = newcode.indexOf('\n');
while (endOfLine >= 0) {
if (endOfLine > startOfLine) {
// Append line only if it is non-empty.
append(newcode.substring(startOfLine, endOfLine));
}
// Breaking is non-optional. Newlines added this way must be preserved (e.g. newlines in
// template literals).
startNewLine();
startOfLine = endOfLine + 1; // Jump over the newline char.
endOfLine = newcode.indexOf('\n', startOfLine);
}
if (newcode.length() > startOfLine) {
// Append line only if it is non-empty.
append(newcode.substring(startOfLine)); // Append the last or only line without breaking.
}
}
/**
* Appends a string to the code, keeping track of the current line length.
*
*
Clients should not call this method directly, but instead call add {@link #add()}.
*
*
The string must be a complete token; partial strings or partial regexes will run the risk of
* being split across lines.
*
*
Implementations of this method need not consider newline characters in {@code str}. Such
* characters should either be ignored or cause an {@link Exception}.
*/
@ForOverride
abstract void append(String str);
void addIdentifier(String identifier) {
add(identifier);
}
void appendBlockStart() {
append("{");
}
void appendBlockEnd() {
append("}");
}
void startNewLine() {
}
void maybeLineBreak() {
maybeCutLine();
}
void maybeCutLine() {
}
void endLine() {
}
void notePreferredLineBreak() {
}
void beginBlock() {
if (statementNeedsEnded) {
append(";");
maybeLineBreak();
}
appendBlockStart();
endLine();
statementNeedsEnded = false;
}
void endBlock() {
endBlock(false);
}
void endBlock(boolean shouldEndLine) {
appendBlockEnd();
if (shouldEndLine) {
endLine();
}
statementNeedsEnded = false;
}
void listSeparator() {
add(",");
maybeLineBreak();
}
void optionalListSeparator() {}
/**
* Indicates the end of a statement and a ';' may need to be added. But we don't add it now, in
* case we're at the end of a block (in which case we don't have to add the ';'). See
* maybeEndStatement()
*/
void endStatement(boolean hasTrailingCommentOnSameLine) {
endStatement(false, hasTrailingCommentOnSameLine);
}
void endStatement(boolean needSemiColon, boolean hasTrailingCommentOnSameLine) {
if (needSemiColon) {
append(";");
if (!hasTrailingCommentOnSameLine) {
maybeLineBreak();
}
statementNeedsEnded = false;
} else if (statementStarted) {
statementNeedsEnded = true;
}
}
/**
* This is to be called when we're in a statement. If the prev statement
* needs to be ended, add a ';'.
*/
void maybeEndStatement() {
// Add a ';' if we need to.
if (statementNeedsEnded) {
append(";");
maybeLineBreak();
endLine();
statementNeedsEnded = false;
}
statementStarted = true;
}
void endFunction(boolean statementContext) {
sawFunction = true;
if (statementContext) {
endLine();
}
}
void endClass(boolean statementContext) {
if (statementContext) {
endLine();
}
}
void beginCaseBody() {
append(":");
}
void endCaseBody() {
}
final void beginTemplateLit() {
checkState(templateLitDepth == templateLitSubDepth);
maybeEndStatement();
append("`");
templateLitDepth++;
}
final void beginTemplateLitSub() {
// This method pair exists because '$' behaves differently inside template literals. We want to
// append it without sanitizing in the generic way {@link #add()} would.
checkState(isInTemplateLiteral());
append("${");
templateLitSubDepth++;
}
final void endTemplateLitSub() {
// This method pair exists because '$' behaves differently inside template literals. We want to
// append it without sanitizing in the generic way {@link #add()} would.
checkState(templateLitSubDepth > 0);
checkState(templateLitDepth == templateLitSubDepth);
append("}");
templateLitSubDepth--;
}
final void endTemplateLit() {
checkState(templateLitDepth > 0);
checkState(isInTemplateLiteral());
append("`");
templateLitDepth--;
}
final boolean isInTemplateLiteral() {
// We're inside a template literal but not a substitution within that literal.
return templateLitDepth == templateLitSubDepth + 1;
}
void appendOp(String op, boolean binOp) {
append(op);
}
void addOp(String op, boolean binOp) {
maybeEndStatement();
char first = op.charAt(0);
char prev = getLastChar();
if ((first == '+' || first == '-') && prev == first) {
// This is not pretty printing. This is to prevent misparsing of
// things like "x + ++y" or "x++ + ++y"
append(" ");
} else if (Character.isLetter(first) && isWordChar(prev)) {
// Make sure there is a space after e.g. instanceof , typeof
append(" ");
} else if ((prev == '-' && first == '>') || (prev == '<' && first == '!')) {
// Make sure that we don't emit ""
append(" ");
}
// Allow formatting around the operator.
appendOp(op, binOp);
// Line breaking after an operator is always safe. Line breaking before an
// operator on the other hand is not. We only line break after a bin op
// because it looks strange.
if (binOp) {
maybeCutLine();
}
}
void addNumber(double x, Node n) {
checkState(isPositive(x), x);
if ((long) x != x) {
addConstant(String.valueOf(x).replace(".0E", "E").replaceFirst("^0\\.", "."));
return;
}
long value = (long) x;
long mantissa = value;
int exp = 0;
if (x >= 100) {
while (mantissa % 10 == 0) {
mantissa /= 10;
exp++;
}
}
if (exp > 2) {
addConstant(mantissa + "E" + exp);
return;
}
String decValueString = Long.toString(value);
if (value <= 1000000000000L) {
// Values <1E12 are shorter in decimal
addConstant(decValueString);
return;
}
String hexValueString = Long.toHexString(value);
if (hexValueString.length() + 2 < decValueString.length()) {
addConstant("0x" + hexValueString);
} else {
addConstant(decValueString);
}
}
void addConstant(String newcode) {
add(newcode);
}
static boolean isWordChar(char ch) {
return (ch == '_' ||
ch == '$' ||
Character.isLetterOrDigit(ch));
}
/**
* If the body of a for loop or the then clause of an if statement has a single statement, should
* it be wrapped in a block? Doing so can help when pretty-printing the code, and permits putting
* a debugging breakpoint on the statement inside the condition.
*
* @param n node to process
* @return {@boolean true} if such expressions should be wrapped
*/
boolean shouldPreserveExtras(Node n) {
return false;
}
/**
* Allows a consumer to insert spaces in locations where it is unnecessary
* but may improve the readability of the code. This will be called in such
* places as after a statement and before opening parentheses, or after the
* end of a if block before the start of an else block.
*/
void maybeInsertSpace() {}
/**
* @return Whether the a line break can be added after the specified BLOCK.
*/
boolean breakAfterBlockFor(Node n, boolean statementContext) {
return statementContext;
}
/** Called when we're at the end of a file. */
void endFile() {}
}