
com.google.javascript.refactoring.ApplySuggestedFixes Maven / Gradle / Ivy
/*
* Copyright 2014 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.refactoring;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Ordering;
import com.google.common.collect.SetMultimap;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Class that applies suggested fixes to code or files.
*/
public final class ApplySuggestedFixes {
private static final Ordering ORDER_CODE_REPLACEMENTS = Ordering.natural()
.onResultOf(new Function() {
@Override public Integer apply(CodeReplacement replacement) {
return replacement.getStartPosition();
}
})
.compound(Ordering.natural().onResultOf(new Function() {
@Override public Integer apply(CodeReplacement replacement) {
return replacement.getLength();
}
}))
.compound(Ordering.natural().onResultOf(new Function() {
@Override public String apply(CodeReplacement replacement) {
return replacement.getSortKey();
}
}));
/**
* Applies the provided set of suggested fixes to the files listed in the suggested fixes.
* The fixes can be provided in any order, but they may not have any overlapping modifications
* for the same file.
*/
public static void applySuggestedFixesToFiles(Iterable fixes)
throws IOException {
Set filenames = new HashSet<>();
for (SuggestedFix fix : fixes) {
filenames.addAll(fix.getReplacements().keySet());
}
Map filenameToCodeMap = new HashMap<>();
for (String filename : filenames) {
filenameToCodeMap.put(filename, Files.toString(new File(filename), UTF_8));
}
Map newCode = applySuggestedFixesToCode(fixes, filenameToCodeMap);
for (Map.Entry entry : newCode.entrySet()) {
Files.write(entry.getValue(), new File(entry.getKey()), UTF_8);
}
}
/**
* Applies the provided set of suggested fixes to the provided code and returns the new code.
* The {@code filenameToCodeMap} must contain all the files that the provided fixes apply to.
* The fixes can be provided in any order, but they may not have any overlapping modifications
* for the same file.
* This function will return new code only for the files that have been modified.
*/
public static Map applySuggestedFixesToCode(
Iterable fixes, Map filenameToCodeMap) {
ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder();
for (SuggestedFix fix : fixes) {
builder.putAll(fix.getReplacements());
}
SetMultimap replacementsMap = builder.build();
ImmutableMap.Builder newCodeMap = ImmutableMap.builder();
for (Map.Entry> entry
: Multimaps.asMap(replacementsMap).entrySet()) {
String filename = entry.getKey();
if (!filenameToCodeMap.containsKey(filename)) {
throw new IllegalArgumentException("filenameToCodeMap missing code for file: " + filename);
}
Set replacements = entry.getValue();
String newCode = applyCodeReplacements(replacements, filenameToCodeMap.get(filename));
newCodeMap.put(filename, newCode);
}
return newCodeMap.build();
}
/**
* 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 replacements, String code) {
List 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.substring(lastIndex));
}
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 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());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy