/** * Inserts a new node as the first child of the provided node. */ public Builder addChildToFront(Node parentNode, String content) { Preconditions.checkState(parentNode.isBlock(), "addChildToFront is only supported for BLOCK statements."); int startPosition = parentNode.getSourceOffset() + 1; replacements.put( parentNode.getSourceFileName(), new CodeReplacement(startPosition, 0, "\n" + content)); return this; }
/** * Applies the provided set of code replacements to the code and returns the transformed code. * The code replacements may not have any overlap. */ public static String applyCodeReplacements(Iterable<CodeReplacement> replacements, String code) { List<CodeReplacement> sortedReplacements = ORDER_CODE_REPLACEMENTS.sortedCopy(replacements); validateNoOverlaps(sortedReplacements); StringBuilder sb = new StringBuilder(); int lastIndex = 0; for (CodeReplacement replacement : sortedReplacements) { sb.append(code, lastIndex, replacement.getStartPosition()); sb.append(replacement.getNewContent()); lastIndex = replacement.getStartPosition() + replacement.getLength(); } if (lastIndex <= code.length()) { sb.append(code, lastIndex, code.length()); } return sb.toString(); }
/** * Validates that none of the CodeReplacements have any overlap, since applying * changes that have overlap will produce malformed results. * The replacements must be provided in order sorted by start position, as sorted * by ORDER_CODE_REPLACEMENTS. */ private static void validateNoOverlaps(List<CodeReplacement> replacements) { int start = -1; for (CodeReplacement replacement : replacements) { if (replacement.getStartPosition() < start) { throw new IllegalArgumentException( "Found overlap between code replacements!\n" + Joiner.on("\n\n").join(replacements)); } start = Math.max(start, replacement.getStartPosition() + replacement.getLength()); } } }
@Override public Integer apply(CodeReplacement replacement) { return replacement.getLength(); } }))
/** * Inserts the text after the given node */ public Builder insertAfter(Node node, String text) { int position = node.getSourceOffset() + node.getLength(); replacements.put( node.getSourceFileName(), new CodeReplacement(position, 0, text)); return this; }
/** * Changes the JS Doc Type of the given node. */ public Builder changeJsDocType(Node n, AbstractCompiler compiler, String type) { JSDocInfo info = NodeUtil.getBestJSDocInfo(n); Preconditions.checkNotNull(info, "Node %s does not have JS Doc associated with it.", n); Node typeNode = JsDocInfoParser.parseTypeString(type); Preconditions.checkNotNull(typeNode, "Invalid type: %s", type); JSTypeExpression typeExpr = new JSTypeExpression(typeNode, "jsflume"); JSType newJsType = typeExpr.evaluate(null, compiler.getTypeRegistry()); if (newJsType == null) { throw new RuntimeException("JS Compiler does not recognize type: " + type); } String originalComment = info.getOriginalCommentString(); int originalPosition = info.getOriginalCommentPosition(); // TODO(mknichel): Support multiline @type annotations. Pattern typeDocPattern = Pattern.compile( "@(type|private|protected|public|const|return) *\\{?[^\\s}]+\\}?"); Matcher m = typeDocPattern.matcher(originalComment); while (m.find()) { replacements.put( n.getSourceFileName(), new CodeReplacement( originalPosition + m.start(), m.end() - m.start(), "@" + m.group(1) + " {" + type + "}")); } return this; }
private Builder insertBefore(Node nodeToInsertBefore, String content, String sortKey) { int startPosition = nodeToInsertBefore.getSourceOffset(); JSDocInfo jsDoc = NodeUtil.getBestJSDocInfo(nodeToInsertBefore); if (jsDoc != null) { startPosition = jsDoc.getOriginalCommentPosition(); } Preconditions.checkNotNull(nodeToInsertBefore.getSourceFileName(), "No source file name for node: %s", nodeToInsertBefore); replacements.put( nodeToInsertBefore.getSourceFileName(), new CodeReplacement(startPosition, 0, content, sortKey)); return this; }
/** * Adds a cast of the given type to the provided node. */ public Builder addCast(Node n, AbstractCompiler compiler, String type) { // TODO(mknichel): Figure out the best way to output the typecast. replacements.put( n.getSourceFileName(), new CodeReplacement( n.getSourceOffset(), n.getLength(), "/** @type {" + type + "} */ (" + generateCode(compiler, n) + ")")); return this; }
/** * Replaces the provided node with new node in the source file. */ public Builder replace(Node original, Node newNode, AbstractCompiler compiler) { Node parent = original.getParent(); // EXPR_RESULT nodes will contain the trailing semicolons, but the child node // will not. Replace the EXPR_RESULT node to ensure that the semicolons are // correct in the final output. if (original.getParent().isExprResult()) { original = original.getParent(); } // TODO(mknichel): Move this logic to CodePrinter. String newCode = generateCode(compiler, newNode); // The generated code may contain a trailing newline but that is never wanted. if (newCode.endsWith("\n")) { newCode = newCode.substring(0, newCode.length() - 1); } // Most replacements don't need the semicolon in the new generated code - however, some // statements that are blocks or expressions will need the semicolon. boolean needsSemicolon = parent.isExprResult() || parent.isBlock() || parent.isScript(); if (newCode.endsWith(";") && !needsSemicolon) { newCode = newCode.substring(0, newCode.length() - 1); } replacements.put( original.getSourceFileName(), new CodeReplacement(original.getSourceOffset(), original.getLength(), newCode)); return this; }
/** * Removes a cast from the given node. */ public Builder removeCast(Node n, AbstractCompiler compiler) { Preconditions.checkArgument(n.isCast()); JSDocInfo jsDoc = n.getJSDocInfo(); replacements.put( n.getSourceFileName(), new CodeReplacement( jsDoc.getOriginalCommentPosition(), n.getFirstChild().getSourceOffset() - jsDoc.getOriginalCommentPosition(), "")); replacements.put( n.getSourceFileName(), new CodeReplacement( n.getSourceOffset() + n.getLength() - 1, 1 /* length */, "")); return this; }
newContent = ", " + newContent; replacements.put(n.getSourceFileName(), new CodeReplacement(startPosition, 0, newContent));
/** * Replaces a range of nodes with the given content. */ public Builder replaceRange(Node first, Node last, String newContent) { Preconditions.checkState(first.getParent() == last.getParent()); int start; JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(first); if (jsdoc == null) { start = first.getSourceOffset(); } else { start = jsdoc.getOriginalCommentPosition(); } int end = last.getSourceOffset() + last.getLength(); int length = end - start; replacements.put(first.getSourceFileName(), new CodeReplacement(start, length, newContent)); return this; }
/** * Adds or replaces the JS Doc for the given node. */ public Builder addOrReplaceJsDoc(Node n, String newJsDoc) { int startPosition = n.getSourceOffset(); int length = 0; JSDocInfo jsDoc = NodeUtil.getBestJSDocInfo(n); if (jsDoc != null) { startPosition = jsDoc.getOriginalCommentPosition(); length = n.getSourceOffset() - jsDoc.getOriginalCommentPosition(); } replacements.put(n.getSourceFileName(), new CodeReplacement(startPosition, length, newJsDoc)); return this; }
new CodeReplacement(startOfArgumentToRemove, lengthOfArgumentToRemove, "")); return this;
new CodeReplacement(nodeToRename.getSourceOffset(), nodeToRename.getLength(), name)); return this;
int startPosition = nodeToInsertAfter.getSourceOffset() + nodeToInsertAfter.getLength() + 2; replacements.put(nodeToInsertAfter.getSourceFileName(), new CodeReplacement( startPosition, 0, nodeToInsertBefore = script.getFirstChild(); } else { replacements.put(script.getSourceFileName(), new CodeReplacement( 0, 0, generateCode(m.getMetadata().getCompiler(), googRequireNode))); return this;
replacements.put(n.getSourceFileName(), new CodeReplacement(startPosition, length, "")); return this;