
com.google.javascript.refactoring.ErrorToFixMapper 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 com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.AbstractCompiler;
import com.google.javascript.jscomp.JSError;
import com.google.javascript.jscomp.NodeTraversal;
import com.google.javascript.jscomp.NodeUtil;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Maps a JSError to a SuggestedFix.
* TODO(tbreisacher): Move this into the compiler itself (i.e. into the jscomp package). This will
* make it easier for people adding new warnings to also add fixes for them.
*/
public final class ErrorToFixMapper {
private ErrorToFixMapper() {} // All static
private static final Pattern DID_YOU_MEAN = Pattern.compile(".*Did you mean (.*)\\?");
private static final Pattern MISSING_REQUIRE =
Pattern.compile("'([^']+)' used but not goog\\.require'd");
private static final Pattern EXTRA_REQUIRE =
Pattern.compile("'([^']+)' goog\\.require'd but not used");
public static List getFixesForJsError(JSError error, AbstractCompiler compiler) {
SuggestedFix fix = getFixForJsError(error, compiler);
if (fix != null) {
return ImmutableList.of(fix);
}
switch (error.getType().key) {
case "JSC_IMPLICITLY_NULLABLE_JSDOC":
return getFixesForImplicitlyNullableJsDoc(error);
default:
return ImmutableList.of();
}
}
/**
* Creates a SuggestedFix for the given error. Note that some errors have multiple fixes
* so getFixesForJsError should often be used instead of this.
*/
public static SuggestedFix getFixForJsError(JSError error, AbstractCompiler compiler) {
switch (error.getType().key) {
case "JSC_REQUIRES_NOT_SORTED":
return getFixForUnsortedRequiresOrProvides("goog.require", error, compiler);
case "JSC_PROVIDES_NOT_SORTED":
return getFixForUnsortedRequiresOrProvides("goog.provide", error, compiler);
case "JSC_DEBUGGER_STATEMENT_PRESENT":
case "JSC_USELESS_EMPTY_STATEMENT":
return removeNode(error);
case "JSC_INEXISTENT_PROPERTY":
return getFixForInexistentProperty(error);
case "JSC_MISSING_REQUIRE_WARNING":
return getFixForMissingRequire(error, compiler);
case "JSC_DUPLICATE_REQUIRE_WARNING":
case "JSC_EXTRA_REQUIRE_WARNING":
return getFixForExtraRequire(error, compiler);
case "JSC_UNNECESSARY_CAST":
return getFixForUnnecessaryCast(error, compiler);
default:
return null;
}
}
private static List getFixesForImplicitlyNullableJsDoc(JSError error) {
SuggestedFix qmark = new SuggestedFix.Builder()
.setOriginalMatchedNode(error.node)
.insertBefore(error.node, "?")
.setDescription("Make nullability explicit")
.build();
SuggestedFix bang = new SuggestedFix.Builder()
.setOriginalMatchedNode(error.node)
.insertBefore(error.node, "!")
.setDescription("Make type non-nullable")
.build();
return ImmutableList.of(qmark, bang);
}
private static SuggestedFix removeNode(JSError error) {
return new SuggestedFix.Builder()
.setOriginalMatchedNode(error.node)
.delete(error.node).build();
}
private static SuggestedFix getFixForInexistentProperty(JSError error) {
Matcher m = DID_YOU_MEAN.matcher(error.description);
if (m.matches()) {
String suggestedPropName = m.group(1);
return new SuggestedFix.Builder()
.setOriginalMatchedNode(error.node)
.rename(error.node, suggestedPropName).build();
}
return null;
}
private static SuggestedFix getFixForMissingRequire(JSError error, AbstractCompiler compiler) {
Matcher regexMatcher = MISSING_REQUIRE.matcher(error.description);
Preconditions.checkState(regexMatcher.matches(),
"Unexpected error description: %s", error.description);
String namespaceToRequire = regexMatcher.group(1);
NodeMetadata metadata = new NodeMetadata(compiler);
Match match = new Match(error.node, metadata);
return new SuggestedFix.Builder()
.setOriginalMatchedNode(error.node)
.addGoogRequire(match, namespaceToRequire)
.build();
}
private static SuggestedFix getFixForExtraRequire(JSError error, AbstractCompiler compiler) {
Matcher regexMatcher = EXTRA_REQUIRE.matcher(error.description);
Preconditions.checkState(regexMatcher.matches(),
"Unexpected error description: %s", error.description);
String namespace = regexMatcher.group(1);
NodeMetadata metadata = new NodeMetadata(compiler);
Match match = new Match(error.node, metadata);
return new SuggestedFix.Builder()
.setOriginalMatchedNode(error.node)
.removeGoogRequire(match, namespace)
.build();
}
private static SuggestedFix getFixForUnnecessaryCast(JSError error, AbstractCompiler compiler) {
return new SuggestedFix.Builder()
.setOriginalMatchedNode(error.node)
.removeCast(error.node, compiler).build();
}
private static SuggestedFix getFixForUnsortedRequiresOrProvides(
String closureFunction, JSError error, AbstractCompiler compiler) {
SuggestedFix.Builder fix = new SuggestedFix.Builder();
fix.setOriginalMatchedNode(error.node);
Node script = NodeUtil.getEnclosingScript(error.node);
RequireProvideSorter cb = new RequireProvideSorter(closureFunction);
NodeTraversal.traverseEs6(compiler, script, cb);
Node first = cb.calls.get(0);
Node last = cb.calls.get(cb.calls.size() - 1);
cb.sortCallsAlphabetically();
StringBuilder sb = new StringBuilder();
for (Node n : cb.calls) {
String statement = fix.generateCode(compiler, n);
JSDocInfo jsDoc = NodeUtil.getBestJSDocInfo(n);
if (jsDoc != null) {
statement = jsDoc.getOriginalCommentString() + "\n" + statement;
}
sb.append(statement);
}
// Trim to remove the newline after the last goog.require/provide.
String newContent = sb.toString().trim();
return fix.replaceRange(first, last, newContent).build();
}
private static String getNamespaceFromClosureNode(Node exprResult) {
Preconditions.checkState(exprResult.isExprResult());
return exprResult.getFirstChild().getLastChild().getString();
}
private static class RequireProvideSorter extends NodeTraversal.AbstractShallowCallback
implements Comparator {
private final String closureFunction;
private final List calls = new ArrayList<>();
RequireProvideSorter(String closureFunction) {
this.closureFunction = closureFunction;
}
@Override
public final void visit(NodeTraversal nodeTraversal, Node n, Node parent) {
if (n.isCall()
&& parent.isExprResult()
&& n.getFirstChild().matchesQualifiedName(closureFunction)) {
calls.add(parent);
}
}
public void sortCallsAlphabetically() {
Collections.sort(calls, this);
}
@Override
public int compare(Node n1, Node n2) {
String namespace1 = getNamespaceFromClosureNode(n1);
String namespace2 = getNamespaceFromClosureNode(n2);
return namespace1.compareTo(namespace2);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy