blob: 893e48e7ca46a32c79c919f2ac5e87284131e1b5 [file] [log] [blame]
// Copyright 2017 The Bazel Authors. All rights reserved.
//
// 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.devtools.skylark.skylint;
import com.google.devtools.build.lib.syntax.BuildFileAST;
import com.google.devtools.build.lib.syntax.FunctionDefStatement;
import com.google.devtools.build.lib.syntax.Identifier;
import com.google.devtools.build.lib.syntax.LoadStatement;
import com.google.devtools.build.lib.syntax.StringLiteral;
import com.google.devtools.skylark.skylint.DependencyAnalyzer.DependencyCollector;
import com.google.devtools.skylark.skylint.DocstringUtils.DocstringInfo;
import com.google.devtools.skylark.skylint.Environment.NameInfo;
import com.google.devtools.skylark.skylint.Environment.NameInfo.Kind;
import com.google.devtools.skylark.skylint.Linter.FileFacade;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/** Checks for usage of deprecated symbols. */
public class DeprecationChecker extends AstVisitorWithNameResolution {
private static final String DEPRECATED_SYMBOL_CATEGORY = "deprecated-symbol";
private final List<Issue> issues = new ArrayList<>();
/** Maps a global function name to its deprecation information, if any. */
private final Map<String, DeprecatedSymbol> symbolToDeprecation;
/** Path of the file that is checked. */
private final Path path;
private DeprecationChecker(Path path, Map<String, DeprecatedSymbol> symbolToDeprecation) {
this.path = path;
this.symbolToDeprecation = symbolToDeprecation;
}
public static List<Issue> check(Path path, BuildFileAST ast, FileFacade fileFacade) {
Map<String, DeprecatedSymbol> symbolToDeprecationWarning =
DeprecationCollector.getDeprecations(path, fileFacade);
DeprecationChecker checker = new DeprecationChecker(path, symbolToDeprecationWarning);
checker.visit(ast);
return checker.issues;
}
@Override
public void visit(FunctionDefStatement node) {
// Don't issue deprecation warnings inside of deprecated functions:
if (!symbolToDeprecation.containsKey(node.getIdentifier().getName())) {
super.visit(node);
}
}
@Override
void use(Identifier ident) {
NameInfo info = env.resolveName(ident.getName());
if (info == null) {
return;
}
DeprecatedSymbol deprecation = symbolToDeprecation.get(info.name);
if (deprecation != null && info.kind != Kind.LOCAL && info.kind != Kind.PARAMETER) {
String originInfo = "";
if (deprecation.origin != path) {
originInfo = "(imported from " + deprecation.origin;
if (!deprecation.originalName.equals(info.name)) {
originInfo += ", named '" + deprecation.originalName + "' there";
}
originInfo += ") ";
}
String message =
"usage of '"
+ info.name
+ "' "
+ originInfo
+ "is deprecated: "
+ deprecation.deprecationMessage;
issues.add(Issue.create(DEPRECATED_SYMBOL_CATEGORY, message, ident.getLocation()));
}
}
/** Holds information about a deprecated symbol. */
private static class DeprecatedSymbol {
final Path origin;
final String originalName;
final String deprecationMessage;
public DeprecatedSymbol(Path origin, String originalName, String deprecationMessage) {
this.origin = origin;
this.originalName = originalName;
this.deprecationMessage = deprecationMessage;
}
}
/** Collects information about deprecated symbols (including dependencies). */
private static class DeprecationCollector
implements DependencyCollector<Map<String, DeprecatedSymbol>> {
/**
* Returns deprecation information (including dependencies) about symbols in the given file.
*
* @param path the path of the file to collect deprecation from
* @param fileFacade to access files
* @return a map: symbol name -> deprecation info
*/
public static Map<String, DeprecatedSymbol> getDeprecations(Path path, FileFacade fileFacade) {
return new DependencyAnalyzer<>(fileFacade, new DeprecationCollector())
.collectTransitiveInfo(path);
}
@Override
public Map<String, DeprecatedSymbol> initInfo(Path path) {
return new LinkedHashMap<>();
}
@Override
public Map<String, DeprecatedSymbol> loadDependency(
Map<String, DeprecatedSymbol> currentFileInfo,
LoadStatement stmt,
Path loadedPath,
Map<String, DeprecatedSymbol> loadedFileInfo) {
for (Map.Entry<Identifier, String> entry : stmt.getSymbolMap().entrySet()) {
String originalName = entry.getValue();
String alias = entry.getKey().getName();
DeprecatedSymbol originalDeprecation = loadedFileInfo.get(originalName);
if (originalDeprecation != null) {
currentFileInfo.put(alias, originalDeprecation);
}
}
return currentFileInfo;
}
@Override
public Map<String, DeprecatedSymbol> collectInfo(
Path path, BuildFileAST ast, Map<String, DeprecatedSymbol> deprecationInfos) {
Map<String, StringLiteral> docstrings = DocstringUtils.collectDocstringLiterals(ast);
for (Map.Entry<String, StringLiteral> entry : docstrings.entrySet()) {
String symbol = entry.getKey();
StringLiteral docstring = entry.getValue();
DocstringInfo info = DocstringUtils.parseDocstring(docstring, new ArrayList<>());
if (!info.deprecated.isEmpty()) {
deprecationInfos.put(symbol, new DeprecatedSymbol(path, symbol, info.deprecated));
}
}
return deprecationInfos;
}
}
}