Imported Upstream version 6.10.0.49

Former-commit-id: 1d6753294b2993e1fbf92de9366bb9544db4189b
This commit is contained in:
Xamarin Public Jenkins (auto-signing)
2020-01-16 16:38:04 +00:00
parent d94e79959b
commit 468663ddbb
48518 changed files with 2789335 additions and 61176 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
set(LLVM_LINK_COMPONENTS
Support
)
add_clang_library(clangToolingASTDiff
ASTDiff.cpp
LINK_LIBS
clangBasic
clangAST
clangLex
)

View File

@ -0,0 +1,109 @@
//===--- ArgumentsAdjusters.cpp - Command line arguments adjuster ---------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file contains definitions of classes which implement ArgumentsAdjuster
// interface.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/ArgumentsAdjusters.h"
namespace clang {
namespace tooling {
/// Add -fsyntax-only option to the command line arguments.
ArgumentsAdjuster getClangSyntaxOnlyAdjuster() {
return [](const CommandLineArguments &Args, StringRef /*unused*/) {
CommandLineArguments AdjustedArgs;
for (size_t i = 0, e = Args.size(); i != e; ++i) {
StringRef Arg = Args[i];
// FIXME: Remove options that generate output.
if (!Arg.startswith("-fcolor-diagnostics") &&
!Arg.startswith("-fdiagnostics-color"))
AdjustedArgs.push_back(Args[i]);
}
AdjustedArgs.push_back("-fsyntax-only");
return AdjustedArgs;
};
}
ArgumentsAdjuster getClangStripOutputAdjuster() {
return [](const CommandLineArguments &Args, StringRef /*unused*/) {
CommandLineArguments AdjustedArgs;
for (size_t i = 0, e = Args.size(); i < e; ++i) {
StringRef Arg = Args[i];
if (!Arg.startswith("-o"))
AdjustedArgs.push_back(Args[i]);
if (Arg == "-o") {
// Output is specified as -o foo. Skip the next argument too.
++i;
}
// Else, the output is specified as -ofoo. Just do nothing.
}
return AdjustedArgs;
};
}
ArgumentsAdjuster getClangStripDependencyFileAdjuster() {
return [](const CommandLineArguments &Args, StringRef /*unused*/) {
CommandLineArguments AdjustedArgs;
for (size_t i = 0, e = Args.size(); i < e; ++i) {
StringRef Arg = Args[i];
// All dependency-file options begin with -M. These include -MM,
// -MF, -MG, -MP, -MT, -MQ, -MD, and -MMD.
if (!Arg.startswith("-M")) {
AdjustedArgs.push_back(Args[i]);
continue;
}
if (Arg == "-MF" || Arg == "-MT" || Arg == "-MQ")
// These flags take an argument: -MX foo. Skip the next argument also.
++i;
}
return AdjustedArgs;
};
}
ArgumentsAdjuster getInsertArgumentAdjuster(const CommandLineArguments &Extra,
ArgumentInsertPosition Pos) {
return [Extra, Pos](const CommandLineArguments &Args, StringRef /*unused*/) {
CommandLineArguments Return(Args);
CommandLineArguments::iterator I;
if (Pos == ArgumentInsertPosition::END) {
I = Return.end();
} else {
I = Return.begin();
++I; // To leave the program name in place
}
Return.insert(I, Extra.begin(), Extra.end());
return Return;
};
}
ArgumentsAdjuster getInsertArgumentAdjuster(const char *Extra,
ArgumentInsertPosition Pos) {
return getInsertArgumentAdjuster(CommandLineArguments(1, Extra), Pos);
}
ArgumentsAdjuster combineAdjusters(ArgumentsAdjuster First,
ArgumentsAdjuster Second) {
if (!First)
return Second;
if (!Second)
return First;
return [First, Second](const CommandLineArguments &Args, StringRef File) {
return Second(First(Args, File), File);
};
}
} // end namespace tooling
} // end namespace clang

View File

@ -0,0 +1,36 @@
set(LLVM_LINK_COMPONENTS
Option
Support
)
add_subdirectory(Core)
add_subdirectory(Refactoring)
add_subdirectory(ASTDiff)
add_clang_library(clangTooling
ArgumentsAdjusters.cpp
CommonOptionsParser.cpp
CompilationDatabase.cpp
Execution.cpp
FileMatchTrie.cpp
FixIt.cpp
JSONCompilationDatabase.cpp
Refactoring.cpp
RefactoringCallbacks.cpp
StandaloneExecution.cpp
Tooling.cpp
DEPENDS
ClangDriverOptions
LINK_LIBS
clangAST
clangASTMatchers
clangBasic
clangDriver
clangFormat
clangFrontend
clangLex
clangRewrite
clangToolingCore
)

View File

@ -0,0 +1,180 @@
//===--- CommonOptionsParser.cpp - common options for clang tools ---------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements the CommonOptionsParser class used to parse common
// command-line options for clang tools, so that they can be run as separate
// command-line applications with a consistent common interface for handling
// compilation database and input files.
//
// It provides a common subset of command-line options, common algorithm
// for locating a compilation database and source files, and help messages
// for the basic command-line interface.
//
// It creates a CompilationDatabase and reads common command-line options.
//
// This class uses the Clang Tooling infrastructure, see
// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
// for details on setting it up with LLVM source tree.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"
using namespace clang::tooling;
using namespace llvm;
const char *const CommonOptionsParser::HelpMessage =
"\n"
"-p <build-path> is used to read a compile command database.\n"
"\n"
"\tFor example, it can be a CMake build directory in which a file named\n"
"\tcompile_commands.json exists (use -DCMAKE_EXPORT_COMPILE_COMMANDS=ON\n"
"\tCMake option to get this output). When no build path is specified,\n"
"\ta search for compile_commands.json will be attempted through all\n"
"\tparent paths of the first input file . See:\n"
"\thttp://clang.llvm.org/docs/HowToSetupToolingForLLVM.html for an\n"
"\texample of setting up Clang Tooling on a source tree.\n"
"\n"
"<source0> ... specify the paths of source files. These paths are\n"
"\tlooked up in the compile command database. If the path of a file is\n"
"\tabsolute, it needs to point into CMake's source tree. If the path is\n"
"\trelative, the current working directory needs to be in the CMake\n"
"\tsource tree and the file must be in a subdirectory of the current\n"
"\tworking directory. \"./\" prefixes in the relative files will be\n"
"\tautomatically removed, but the rest of a relative path must be a\n"
"\tsuffix of a path in the compile command database.\n"
"\n";
void ArgumentsAdjustingCompilations::appendArgumentsAdjuster(
ArgumentsAdjuster Adjuster) {
Adjusters.push_back(std::move(Adjuster));
}
std::vector<CompileCommand> ArgumentsAdjustingCompilations::getCompileCommands(
StringRef FilePath) const {
return adjustCommands(Compilations->getCompileCommands(FilePath));
}
std::vector<std::string>
ArgumentsAdjustingCompilations::getAllFiles() const {
return Compilations->getAllFiles();
}
std::vector<CompileCommand>
ArgumentsAdjustingCompilations::getAllCompileCommands() const {
return adjustCommands(Compilations->getAllCompileCommands());
}
std::vector<CompileCommand> ArgumentsAdjustingCompilations::adjustCommands(
std::vector<CompileCommand> Commands) const {
for (CompileCommand &Command : Commands)
for (const auto &Adjuster : Adjusters)
Command.CommandLine = Adjuster(Command.CommandLine, Command.Filename);
return Commands;
}
llvm::Error CommonOptionsParser::init(
int &argc, const char **argv, cl::OptionCategory &Category,
llvm::cl::NumOccurrencesFlag OccurrencesFlag, const char *Overview) {
static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden,
cl::sub(*cl::AllSubCommands));
static cl::opt<std::string> BuildPath("p", cl::desc("Build path"),
cl::Optional, cl::cat(Category),
cl::sub(*cl::AllSubCommands));
static cl::list<std::string> SourcePaths(
cl::Positional, cl::desc("<source0> [... <sourceN>]"), OccurrencesFlag,
cl::cat(Category), cl::sub(*cl::AllSubCommands));
static cl::list<std::string> ArgsAfter(
"extra-arg",
cl::desc("Additional argument to append to the compiler command line"),
cl::cat(Category), cl::sub(*cl::AllSubCommands));
static cl::list<std::string> ArgsBefore(
"extra-arg-before",
cl::desc("Additional argument to prepend to the compiler command line"),
cl::cat(Category), cl::sub(*cl::AllSubCommands));
cl::ResetAllOptionOccurrences();
cl::HideUnrelatedOptions(Category);
std::string ErrorMessage;
Compilations =
FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage);
if (!ErrorMessage.empty())
ErrorMessage.append("\n");
llvm::raw_string_ostream OS(ErrorMessage);
// Stop initializing if command-line option parsing failed.
if (!cl::ParseCommandLineOptions(argc, argv, Overview, &OS)) {
OS.flush();
return llvm::make_error<llvm::StringError>("[CommonOptionsParser]: " +
ErrorMessage,
llvm::inconvertibleErrorCode());
}
cl::PrintOptionValues();
SourcePathList = SourcePaths;
if ((OccurrencesFlag == cl::ZeroOrMore || OccurrencesFlag == cl::Optional) &&
SourcePathList.empty())
return llvm::Error::success();
if (!Compilations) {
if (!BuildPath.empty()) {
Compilations =
CompilationDatabase::autoDetectFromDirectory(BuildPath, ErrorMessage);
} else {
Compilations = CompilationDatabase::autoDetectFromSource(SourcePaths[0],
ErrorMessage);
}
if (!Compilations) {
llvm::errs() << "Error while trying to load a compilation database:\n"
<< ErrorMessage << "Running without flags.\n";
Compilations.reset(
new FixedCompilationDatabase(".", std::vector<std::string>()));
}
}
auto AdjustingCompilations =
llvm::make_unique<ArgumentsAdjustingCompilations>(
std::move(Compilations));
Adjuster =
getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN);
Adjuster = combineAdjusters(
std::move(Adjuster),
getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END));
AdjustingCompilations->appendArgumentsAdjuster(Adjuster);
Compilations = std::move(AdjustingCompilations);
return llvm::Error::success();
}
llvm::Expected<CommonOptionsParser> CommonOptionsParser::create(
int &argc, const char **argv, llvm::cl::OptionCategory &Category,
llvm::cl::NumOccurrencesFlag OccurrencesFlag, const char *Overview) {
CommonOptionsParser Parser;
llvm::Error Err =
Parser.init(argc, argv, Category, OccurrencesFlag, Overview);
if (Err)
return std::move(Err);
return std::move(Parser);
}
CommonOptionsParser::CommonOptionsParser(
int &argc, const char **argv, cl::OptionCategory &Category,
llvm::cl::NumOccurrencesFlag OccurrencesFlag, const char *Overview) {
llvm::Error Err = init(argc, argv, Category, OccurrencesFlag, Overview);
if (Err) {
llvm::report_fatal_error(
"CommonOptionsParser: failed to parse command-line arguments. " +
llvm::toString(std::move(Err)));
}
}

View File

@ -0,0 +1,379 @@
//===--- CompilationDatabase.cpp - ----------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file contains implementations of the CompilationDatabase base class
// and the FixedCompilationDatabase.
//
// FIXME: Various functions that take a string &ErrorMessage should be upgraded
// to Expected.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/CompilationDatabase.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Driver/Action.h"
#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/DriverDiagnostic.h"
#include "clang/Driver/Job.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Tooling/CompilationDatabasePluginRegistry.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Option/Arg.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/LineIterator.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include <sstream>
#include <system_error>
using namespace clang;
using namespace tooling;
LLVM_INSTANTIATE_REGISTRY(CompilationDatabasePluginRegistry)
CompilationDatabase::~CompilationDatabase() {}
std::unique_ptr<CompilationDatabase>
CompilationDatabase::loadFromDirectory(StringRef BuildDirectory,
std::string &ErrorMessage) {
llvm::raw_string_ostream ErrorStream(ErrorMessage);
for (CompilationDatabasePluginRegistry::iterator
It = CompilationDatabasePluginRegistry::begin(),
Ie = CompilationDatabasePluginRegistry::end();
It != Ie; ++It) {
std::string DatabaseErrorMessage;
std::unique_ptr<CompilationDatabasePlugin> Plugin(It->instantiate());
if (std::unique_ptr<CompilationDatabase> DB =
Plugin->loadFromDirectory(BuildDirectory, DatabaseErrorMessage))
return DB;
ErrorStream << It->getName() << ": " << DatabaseErrorMessage << "\n";
}
return nullptr;
}
static std::unique_ptr<CompilationDatabase>
findCompilationDatabaseFromDirectory(StringRef Directory,
std::string &ErrorMessage) {
std::stringstream ErrorStream;
bool HasErrorMessage = false;
while (!Directory.empty()) {
std::string LoadErrorMessage;
if (std::unique_ptr<CompilationDatabase> DB =
CompilationDatabase::loadFromDirectory(Directory, LoadErrorMessage))
return DB;
if (!HasErrorMessage) {
ErrorStream << "No compilation database found in " << Directory.str()
<< " or any parent directory\n" << LoadErrorMessage;
HasErrorMessage = true;
}
Directory = llvm::sys::path::parent_path(Directory);
}
ErrorMessage = ErrorStream.str();
return nullptr;
}
std::unique_ptr<CompilationDatabase>
CompilationDatabase::autoDetectFromSource(StringRef SourceFile,
std::string &ErrorMessage) {
SmallString<1024> AbsolutePath(getAbsolutePath(SourceFile));
StringRef Directory = llvm::sys::path::parent_path(AbsolutePath);
std::unique_ptr<CompilationDatabase> DB =
findCompilationDatabaseFromDirectory(Directory, ErrorMessage);
if (!DB)
ErrorMessage = ("Could not auto-detect compilation database for file \"" +
SourceFile + "\"\n" + ErrorMessage).str();
return DB;
}
std::unique_ptr<CompilationDatabase>
CompilationDatabase::autoDetectFromDirectory(StringRef SourceDir,
std::string &ErrorMessage) {
SmallString<1024> AbsolutePath(getAbsolutePath(SourceDir));
std::unique_ptr<CompilationDatabase> DB =
findCompilationDatabaseFromDirectory(AbsolutePath, ErrorMessage);
if (!DB)
ErrorMessage = ("Could not auto-detect compilation database from directory \"" +
SourceDir + "\"\n" + ErrorMessage).str();
return DB;
}
std::vector<CompileCommand> CompilationDatabase::getAllCompileCommands() const {
std::vector<CompileCommand> Result;
for (const auto &File : getAllFiles()) {
auto C = getCompileCommands(File);
std::move(C.begin(), C.end(), std::back_inserter(Result));
}
return Result;
}
CompilationDatabasePlugin::~CompilationDatabasePlugin() {}
namespace {
// Helper for recursively searching through a chain of actions and collecting
// all inputs, direct and indirect, of compile jobs.
struct CompileJobAnalyzer {
void run(const driver::Action *A) {
runImpl(A, false);
}
SmallVector<std::string, 2> Inputs;
private:
void runImpl(const driver::Action *A, bool Collect) {
bool CollectChildren = Collect;
switch (A->getKind()) {
case driver::Action::CompileJobClass:
CollectChildren = true;
break;
case driver::Action::InputClass: {
if (Collect) {
const driver::InputAction *IA = cast<driver::InputAction>(A);
Inputs.push_back(IA->getInputArg().getSpelling());
}
} break;
default:
// Don't care about others
;
}
for (const driver::Action *AI : A->inputs())
runImpl(AI, CollectChildren);
}
};
// Special DiagnosticConsumer that looks for warn_drv_input_file_unused
// diagnostics from the driver and collects the option strings for those unused
// options.
class UnusedInputDiagConsumer : public DiagnosticConsumer {
public:
UnusedInputDiagConsumer(DiagnosticConsumer &Other) : Other(Other) {}
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) override {
if (Info.getID() == clang::diag::warn_drv_input_file_unused) {
// Arg 1 for this diagnostic is the option that didn't get used.
UnusedInputs.push_back(Info.getArgStdStr(0));
} else if (DiagLevel >= DiagnosticsEngine::Error) {
// If driver failed to create compilation object, show the diagnostics
// to user.
Other.HandleDiagnostic(DiagLevel, Info);
}
}
DiagnosticConsumer &Other;
SmallVector<std::string, 2> UnusedInputs;
};
// Unary functor for asking "Given a StringRef S1, does there exist a string
// S2 in Arr where S1 == S2?"
struct MatchesAny {
MatchesAny(ArrayRef<std::string> Arr) : Arr(Arr) {}
bool operator() (StringRef S) {
for (const std::string *I = Arr.begin(), *E = Arr.end(); I != E; ++I)
if (*I == S)
return true;
return false;
}
private:
ArrayRef<std::string> Arr;
};
} // namespace
/// \brief Strips any positional args and possible argv[0] from a command-line
/// provided by the user to construct a FixedCompilationDatabase.
///
/// FixedCompilationDatabase requires a command line to be in this format as it
/// constructs the command line for each file by appending the name of the file
/// to be compiled. FixedCompilationDatabase also adds its own argv[0] to the
/// start of the command line although its value is not important as it's just
/// ignored by the Driver invoked by the ClangTool using the
/// FixedCompilationDatabase.
///
/// FIXME: This functionality should probably be made available by
/// clang::driver::Driver although what the interface should look like is not
/// clear.
///
/// \param[in] Args Args as provided by the user.
/// \return Resulting stripped command line.
/// \li true if successful.
/// \li false if \c Args cannot be used for compilation jobs (e.g.
/// contains an option like -E or -version).
static bool stripPositionalArgs(std::vector<const char *> Args,
std::vector<std::string> &Result,
std::string &ErrorMsg) {
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
llvm::raw_string_ostream Output(ErrorMsg);
TextDiagnosticPrinter DiagnosticPrinter(Output, &*DiagOpts);
UnusedInputDiagConsumer DiagClient(DiagnosticPrinter);
DiagnosticsEngine Diagnostics(
IntrusiveRefCntPtr<clang::DiagnosticIDs>(new DiagnosticIDs()),
&*DiagOpts, &DiagClient, false);
// The clang executable path isn't required since the jobs the driver builds
// will not be executed.
std::unique_ptr<driver::Driver> NewDriver(new driver::Driver(
/* ClangExecutable= */ "", llvm::sys::getDefaultTargetTriple(),
Diagnostics));
NewDriver->setCheckInputsExist(false);
// This becomes the new argv[0]. The value is actually not important as it
// isn't used for invoking Tools.
Args.insert(Args.begin(), "clang-tool");
// By adding -c, we force the driver to treat compilation as the last phase.
// It will then issue warnings via Diagnostics about un-used options that
// would have been used for linking. If the user provided a compiler name as
// the original argv[0], this will be treated as a linker input thanks to
// insertng a new argv[0] above. All un-used options get collected by
// UnusedInputdiagConsumer and get stripped out later.
Args.push_back("-c");
// Put a dummy C++ file on to ensure there's at least one compile job for the
// driver to construct. If the user specified some other argument that
// prevents compilation, e.g. -E or something like -version, we may still end
// up with no jobs but then this is the user's fault.
Args.push_back("placeholder.cpp");
// Remove -no-integrated-as; it's not used for syntax checking,
// and it confuses targets which don't support this option.
Args.erase(std::remove_if(Args.begin(), Args.end(),
MatchesAny(std::string("-no-integrated-as"))),
Args.end());
const std::unique_ptr<driver::Compilation> Compilation(
NewDriver->BuildCompilation(Args));
if (!Compilation)
return false;
const driver::JobList &Jobs = Compilation->getJobs();
CompileJobAnalyzer CompileAnalyzer;
for (const auto &Cmd : Jobs) {
// Collect only for Assemble and Compile jobs. If we do all jobs we get
// duplicates since Link jobs point to Assemble jobs as inputs.
if (Cmd.getSource().getKind() == driver::Action::AssembleJobClass ||
Cmd.getSource().getKind() == driver::Action::CompileJobClass) {
CompileAnalyzer.run(&Cmd.getSource());
}
}
if (CompileAnalyzer.Inputs.empty()) {
ErrorMsg = "warning: no compile jobs found\n";
return false;
}
// Remove all compilation input files from the command line. This is
// necessary so that getCompileCommands() can construct a command line for
// each file.
std::vector<const char *>::iterator End = std::remove_if(
Args.begin(), Args.end(), MatchesAny(CompileAnalyzer.Inputs));
// Remove all inputs deemed unused for compilation.
End = std::remove_if(Args.begin(), End, MatchesAny(DiagClient.UnusedInputs));
// Remove the -c add above as well. It will be at the end right now.
assert(strcmp(*(End - 1), "-c") == 0);
--End;
Result = std::vector<std::string>(Args.begin() + 1, End);
return true;
}
std::unique_ptr<FixedCompilationDatabase>
FixedCompilationDatabase::loadFromCommandLine(int &Argc,
const char *const *Argv,
std::string &ErrorMsg,
Twine Directory) {
ErrorMsg.clear();
if (Argc == 0)
return nullptr;
const char *const *DoubleDash = std::find(Argv, Argv + Argc, StringRef("--"));
if (DoubleDash == Argv + Argc)
return nullptr;
std::vector<const char *> CommandLine(DoubleDash + 1, Argv + Argc);
Argc = DoubleDash - Argv;
std::vector<std::string> StrippedArgs;
if (!stripPositionalArgs(CommandLine, StrippedArgs, ErrorMsg))
return nullptr;
return llvm::make_unique<FixedCompilationDatabase>(Directory, StrippedArgs);
}
std::unique_ptr<FixedCompilationDatabase>
FixedCompilationDatabase::loadFromFile(StringRef Path, std::string &ErrorMsg) {
ErrorMsg.clear();
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
llvm::MemoryBuffer::getFile(Path);
if (std::error_code Result = File.getError()) {
ErrorMsg = "Error while opening fixed database: " + Result.message();
return nullptr;
}
std::vector<std::string> Args{llvm::line_iterator(**File),
llvm::line_iterator()};
return llvm::make_unique<FixedCompilationDatabase>(
llvm::sys::path::parent_path(Path), std::move(Args));
}
FixedCompilationDatabase::
FixedCompilationDatabase(Twine Directory, ArrayRef<std::string> CommandLine) {
std::vector<std::string> ToolCommandLine(1, "clang-tool");
ToolCommandLine.insert(ToolCommandLine.end(),
CommandLine.begin(), CommandLine.end());
CompileCommands.emplace_back(Directory, StringRef(),
std::move(ToolCommandLine),
StringRef());
}
std::vector<CompileCommand>
FixedCompilationDatabase::getCompileCommands(StringRef FilePath) const {
std::vector<CompileCommand> Result(CompileCommands);
Result[0].CommandLine.push_back(FilePath);
Result[0].Filename = FilePath;
return Result;
}
namespace {
class FixedCompilationDatabasePlugin : public CompilationDatabasePlugin {
std::unique_ptr<CompilationDatabase>
loadFromDirectory(StringRef Directory, std::string &ErrorMessage) override {
SmallString<1024> DatabasePath(Directory);
llvm::sys::path::append(DatabasePath, "compile_flags.txt");
return FixedCompilationDatabase::loadFromFile(DatabasePath, ErrorMessage);
}
};
static CompilationDatabasePluginRegistry::Add<FixedCompilationDatabasePlugin>
X("fixed-compilation-database", "Reads plain-text flags file");
} // namespace
namespace clang {
namespace tooling {
// This anchor is used to force the linker to link in the generated object file
// and thus register the JSONCompilationDatabasePlugin.
extern volatile int JSONAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED JSONAnchorDest = JSONAnchorSource;
} // end namespace tooling
} // end namespace clang

View File

@ -0,0 +1,13 @@
set(LLVM_LINK_COMPONENTS support)
add_clang_library(clangToolingCore
Lookup.cpp
Replacement.cpp
Diagnostic.cpp
LINK_LIBS
clangAST
clangBasic
clangLex
clangRewrite
)

View File

@ -0,0 +1,46 @@
//===--- Diagnostic.cpp - Framework for clang diagnostics tools ----------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Implements classes to support/store diagnostics refactoring.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Core/Diagnostic.h"
#include "clang/Basic/SourceManager.h"
namespace clang {
namespace tooling {
DiagnosticMessage::DiagnosticMessage(llvm::StringRef Message)
: Message(Message), FileOffset(0) {}
DiagnosticMessage::DiagnosticMessage(llvm::StringRef Message,
const SourceManager &Sources,
SourceLocation Loc)
: Message(Message) {
assert(Loc.isValid() && Loc.isFileID());
FilePath = Sources.getFilename(Loc);
FileOffset = Sources.getFileOffset(Loc);
}
Diagnostic::Diagnostic(llvm::StringRef DiagnosticName,
Diagnostic::Level DiagLevel, StringRef BuildDirectory)
: DiagnosticName(DiagnosticName), DiagLevel(DiagLevel),
BuildDirectory(BuildDirectory) {}
Diagnostic::Diagnostic(llvm::StringRef DiagnosticName,
const DiagnosticMessage &Message,
const llvm::StringMap<Replacements> &Fix,
const SmallVector<DiagnosticMessage, 1> &Notes,
Level DiagLevel, llvm::StringRef BuildDirectory)
: DiagnosticName(DiagnosticName), Message(Message), Fix(Fix), Notes(Notes),
DiagLevel(DiagLevel), BuildDirectory(BuildDirectory) {}
} // end namespace tooling
} // end namespace clang

View File

@ -0,0 +1,151 @@
//===--- Lookup.cpp - Framework for clang refactoring tools ---------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file defines helper methods for clang tools performing name lookup.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Core/Lookup.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
using namespace clang;
using namespace clang::tooling;
// Gets all namespaces that \p Context is in as a vector (ignoring anonymous
// namespaces). The inner namespaces come before outer namespaces in the vector.
// For example, if the context is in the following namespace:
// `namespace a { namespace b { namespace c ( ... ) } }`,
// the vector will be `{c, b, a}`.
static llvm::SmallVector<const NamespaceDecl *, 4>
getAllNamedNamespaces(const DeclContext *Context) {
llvm::SmallVector<const NamespaceDecl *, 4> Namespaces;
auto GetNextNamedNamespace = [](const DeclContext *Context) {
// Look past non-namespaces and anonymous namespaces on FromContext.
while (Context && (!isa<NamespaceDecl>(Context) ||
cast<NamespaceDecl>(Context)->isAnonymousNamespace()))
Context = Context->getParent();
return Context;
};
for (Context = GetNextNamedNamespace(Context); Context != nullptr;
Context = GetNextNamedNamespace(Context->getParent()))
Namespaces.push_back(cast<NamespaceDecl>(Context));
return Namespaces;
}
// Returns true if the context in which the type is used and the context in
// which the type is declared are the same semantical namespace but different
// lexical namespaces.
static bool
usingFromDifferentCanonicalNamespace(const DeclContext *FromContext,
const DeclContext *UseContext) {
// We can skip anonymous namespace because:
// 1. `FromContext` and `UseContext` must be in the same anonymous namespaces
// since referencing across anonymous namespaces is not possible.
// 2. If `FromContext` and `UseContext` are in the same anonymous namespace,
// the function will still return `false` as expected.
llvm::SmallVector<const NamespaceDecl *, 4> FromNamespaces =
getAllNamedNamespaces(FromContext);
llvm::SmallVector<const NamespaceDecl *, 4> UseNamespaces =
getAllNamedNamespaces(UseContext);
// If `UseContext` has fewer level of nested namespaces, it cannot be in the
// same canonical namespace as the `FromContext`.
if (UseNamespaces.size() < FromNamespaces.size())
return false;
unsigned Diff = UseNamespaces.size() - FromNamespaces.size();
auto FromIter = FromNamespaces.begin();
// Only compare `FromNamespaces` with namespaces in `UseNamespaces` that can
// collide, i.e. the top N namespaces where N is the number of namespaces in
// `FromNamespaces`.
auto UseIter = UseNamespaces.begin() + Diff;
for (; FromIter != FromNamespaces.end() && UseIter != UseNamespaces.end();
++FromIter, ++UseIter) {
// Literally the same namespace, not a collision.
if (*FromIter == *UseIter)
return false;
// Now check the names. If they match we have a different canonical
// namespace with the same name.
if (cast<NamespaceDecl>(*FromIter)->getDeclName() ==
cast<NamespaceDecl>(*UseIter)->getDeclName())
return true;
}
assert(FromIter == FromNamespaces.end() && UseIter == UseNamespaces.end());
return false;
}
static StringRef getBestNamespaceSubstr(const DeclContext *DeclA,
StringRef NewName,
bool HadLeadingColonColon) {
while (true) {
while (DeclA && !isa<NamespaceDecl>(DeclA))
DeclA = DeclA->getParent();
// Fully qualified it is! Leave :: in place if it's there already.
if (!DeclA)
return HadLeadingColonColon ? NewName : NewName.substr(2);
// Otherwise strip off redundant namespace qualifications from the new name.
// We use the fully qualified name of the namespace and remove that part
// from NewName if it has an identical prefix.
std::string NS =
"::" + cast<NamespaceDecl>(DeclA)->getQualifiedNameAsString() + "::";
if (NewName.startswith(NS))
return NewName.substr(NS.size());
// No match yet. Strip of a namespace from the end of the chain and try
// again. This allows to get optimal qualifications even if the old and new
// decl only share common namespaces at a higher level.
DeclA = DeclA->getParent();
}
}
/// Check if the name specifier begins with a written "::".
static bool isFullyQualified(const NestedNameSpecifier *NNS) {
while (NNS) {
if (NNS->getKind() == NestedNameSpecifier::Global)
return true;
NNS = NNS->getPrefix();
}
return false;
}
std::string tooling::replaceNestedName(const NestedNameSpecifier *Use,
const DeclContext *UseContext,
const NamedDecl *FromDecl,
StringRef ReplacementString) {
assert(ReplacementString.startswith("::") &&
"Expected fully-qualified name!");
// We can do a raw name replacement when we are not inside the namespace for
// the original class/function and it is not in the global namespace. The
// assumption is that outside the original namespace we must have a using
// statement that makes this work out and that other parts of this refactor
// will automatically fix using statements to point to the new class/function.
// However, if the `FromDecl` is a class forward declaration, the reference is
// still considered as referring to the original definition, so we can't do a
// raw name replacement in this case.
const bool class_name_only = !Use;
const bool in_global_namespace =
isa<TranslationUnitDecl>(FromDecl->getDeclContext());
const bool is_class_forward_decl =
isa<CXXRecordDecl>(FromDecl) &&
!cast<CXXRecordDecl>(FromDecl)->isCompleteDefinition();
if (class_name_only && !in_global_namespace && !is_class_forward_decl &&
!usingFromDifferentCanonicalNamespace(FromDecl->getDeclContext(),
UseContext)) {
auto Pos = ReplacementString.rfind("::");
return Pos != StringRef::npos ? ReplacementString.substr(Pos + 2)
: ReplacementString;
}
// We did not match this because of a using statement, so we will need to
// figure out how good a namespace match we have with our destination type.
// We work backwards (from most specific possible namespace to least
// specific).
return getBestNamespaceSubstr(UseContext, ReplacementString,
isFullyQualified(Use));
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,105 @@
//===- lib/Tooling/Execution.cpp - Implements tool execution framework. ---===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Execution.h"
#include "clang/Tooling/ToolExecutorPluginRegistry.h"
#include "clang/Tooling/Tooling.h"
LLVM_INSTANTIATE_REGISTRY(clang::tooling::ToolExecutorPluginRegistry)
namespace clang {
namespace tooling {
static llvm::cl::opt<std::string>
ExecutorName("executor", llvm::cl::desc("The name of the executor to use."),
llvm::cl::init("standalone"));
void InMemoryToolResults::addResult(StringRef Key, StringRef Value) {
KVResults.push_back({Key.str(), Value.str()});
}
std::vector<std::pair<std::string, std::string>>
InMemoryToolResults::AllKVResults() {
return KVResults;
}
void InMemoryToolResults::forEachResult(
llvm::function_ref<void(StringRef Key, StringRef Value)> Callback) {
for (const auto &KV : KVResults) {
Callback(KV.first, KV.second);
}
}
void ExecutionContext::reportResult(StringRef Key, StringRef Value) {
Results->addResult(Key, Value);
}
llvm::Error
ToolExecutor::execute(std::unique_ptr<FrontendActionFactory> Action) {
return execute(std::move(Action), ArgumentsAdjuster());
}
llvm::Error ToolExecutor::execute(std::unique_ptr<FrontendActionFactory> Action,
ArgumentsAdjuster Adjuster) {
std::vector<
std::pair<std::unique_ptr<FrontendActionFactory>, ArgumentsAdjuster>>
Actions;
Actions.emplace_back(std::move(Action), std::move(Adjuster));
return execute(Actions);
}
namespace internal {
llvm::Expected<std::unique_ptr<ToolExecutor>>
createExecutorFromCommandLineArgsImpl(int &argc, const char **argv,
llvm::cl::OptionCategory &Category,
const char *Overview) {
auto OptionsParser =
CommonOptionsParser::create(argc, argv, Category, llvm::cl::ZeroOrMore,
/*Overview=*/Overview);
if (!OptionsParser)
return OptionsParser.takeError();
for (auto I = ToolExecutorPluginRegistry::begin(),
E = ToolExecutorPluginRegistry::end();
I != E; ++I) {
if (I->getName() != ExecutorName) {
continue;
}
std::unique_ptr<ToolExecutorPlugin> Plugin(I->instantiate());
llvm::Expected<std::unique_ptr<ToolExecutor>> Executor =
Plugin->create(*OptionsParser);
if (!Executor) {
return llvm::make_error<llvm::StringError>(
llvm::Twine("Failed to create '") + I->getName() +
"': " + llvm::toString(Executor.takeError()) + "\n",
llvm::inconvertibleErrorCode());
}
return std::move(*Executor);
}
return llvm::make_error<llvm::StringError>(
llvm::Twine("Executor \"") + ExecutorName + "\" is not registered.",
llvm::inconvertibleErrorCode());
}
} // end namespace internal
llvm::Expected<std::unique_ptr<ToolExecutor>>
createExecutorFromCommandLineArgs(int &argc, const char **argv,
llvm::cl::OptionCategory &Category,
const char *Overview) {
return internal::createExecutorFromCommandLineArgsImpl(argc, argv, Category,
Overview);
}
// This anchor is used to force the linker to link in the generated object file
// and thus register the StandaloneToolExecutorPlugin.
extern volatile int StandaloneToolExecutorAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED StandaloneToolExecutorAnchorDest =
StandaloneToolExecutorAnchorSource;
} // end namespace tooling
} // end namespace clang

View File

@ -0,0 +1,189 @@
//===--- FileMatchTrie.cpp - ----------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file contains the implementation of a FileMatchTrie.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/FileMatchTrie.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include <sstream>
using namespace clang;
using namespace tooling;
namespace {
/// \brief Default \c PathComparator using \c llvm::sys::fs::equivalent().
struct DefaultPathComparator : public PathComparator {
bool equivalent(StringRef FileA, StringRef FileB) const override {
return FileA == FileB || llvm::sys::fs::equivalent(FileA, FileB);
}
};
}
namespace clang {
namespace tooling {
/// \brief A node of the \c FileMatchTrie.
///
/// Each node has storage for up to one path and a map mapping a path segment to
/// child nodes. The trie starts with an empty root node.
class FileMatchTrieNode {
public:
/// \brief Inserts 'NewPath' into this trie. \c ConsumedLength denotes
/// the number of \c NewPath's trailing characters already consumed during
/// recursion.
///
/// An insert of a path
/// 'p'starts at the root node and does the following:
/// - If the node is empty, insert 'p' into its storage and abort.
/// - If the node has a path 'p2' but no children, take the last path segment
/// 's' of 'p2', put a new child into the map at 's' an insert the rest of
/// 'p2' there.
/// - Insert a new child for the last segment of 'p' and insert the rest of
/// 'p' there.
///
/// An insert operation is linear in the number of a path's segments.
void insert(StringRef NewPath, unsigned ConsumedLength = 0) {
// We cannot put relative paths into the FileMatchTrie as then a path can be
// a postfix of another path, violating a core assumption of the trie.
if (llvm::sys::path::is_relative(NewPath))
return;
if (Path.empty()) {
// This is an empty leaf. Store NewPath and return.
Path = NewPath;
return;
}
if (Children.empty()) {
// This is a leaf, ignore duplicate entry if 'Path' equals 'NewPath'.
if (NewPath == Path)
return;
// Make this a node and create a child-leaf with 'Path'.
StringRef Element(llvm::sys::path::filename(
StringRef(Path).drop_back(ConsumedLength)));
Children[Element].Path = Path;
}
StringRef Element(llvm::sys::path::filename(
StringRef(NewPath).drop_back(ConsumedLength)));
Children[Element].insert(NewPath, ConsumedLength + Element.size() + 1);
}
/// \brief Tries to find the node under this \c FileMatchTrieNode that best
/// matches 'FileName'.
///
/// If multiple paths fit 'FileName' equally well, \c IsAmbiguous is set to
/// \c true and an empty string is returned. If no path fits 'FileName', an
/// empty string is returned. \c ConsumedLength denotes the number of
/// \c Filename's trailing characters already consumed during recursion.
///
/// To find the best matching node for a given path 'p', the
/// \c findEquivalent() function is called recursively for each path segment
/// (back to fron) of 'p' until a node 'n' is reached that does not ..
/// - .. have children. In this case it is checked
/// whether the stored path is equivalent to 'p'. If yes, the best match is
/// found. Otherwise continue with the parent node as if this node did not
/// exist.
/// - .. a child matching the next path segment. In this case, all children of
/// 'n' are an equally good match for 'p'. All children are of 'n' are found
/// recursively and their equivalence to 'p' is determined. If none are
/// equivalent, continue with the parent node as if 'n' didn't exist. If one
/// is equivalent, the best match is found. Otherwise, report and ambigiuity
/// error.
StringRef findEquivalent(const PathComparator& Comparator,
StringRef FileName,
bool &IsAmbiguous,
unsigned ConsumedLength = 0) const {
if (Children.empty()) {
if (Comparator.equivalent(StringRef(Path), FileName))
return StringRef(Path);
return StringRef();
}
StringRef Element(llvm::sys::path::filename(FileName.drop_back(
ConsumedLength)));
llvm::StringMap<FileMatchTrieNode>::const_iterator MatchingChild =
Children.find(Element);
if (MatchingChild != Children.end()) {
StringRef Result = MatchingChild->getValue().findEquivalent(
Comparator, FileName, IsAmbiguous,
ConsumedLength + Element.size() + 1);
if (!Result.empty() || IsAmbiguous)
return Result;
}
std::vector<StringRef> AllChildren;
getAll(AllChildren, MatchingChild);
StringRef Result;
for (unsigned i = 0; i < AllChildren.size(); i++) {
if (Comparator.equivalent(AllChildren[i], FileName)) {
if (Result.empty()) {
Result = AllChildren[i];
} else {
IsAmbiguous = true;
return StringRef();
}
}
}
return Result;
}
private:
/// \brief Gets all paths under this FileMatchTrieNode.
void getAll(std::vector<StringRef> &Results,
llvm::StringMap<FileMatchTrieNode>::const_iterator Except) const {
if (Path.empty())
return;
if (Children.empty()) {
Results.push_back(StringRef(Path));
return;
}
for (llvm::StringMap<FileMatchTrieNode>::const_iterator
It = Children.begin(), E = Children.end();
It != E; ++It) {
if (It == Except)
continue;
It->getValue().getAll(Results, Children.end());
}
}
// The stored absolute path in this node. Only valid for leaf nodes, i.e.
// nodes where Children.empty().
std::string Path;
// The children of this node stored in a map based on the next path segment.
llvm::StringMap<FileMatchTrieNode> Children;
};
} // end namespace tooling
} // end namespace clang
FileMatchTrie::FileMatchTrie()
: Root(new FileMatchTrieNode), Comparator(new DefaultPathComparator()) {}
FileMatchTrie::FileMatchTrie(PathComparator *Comparator)
: Root(new FileMatchTrieNode), Comparator(Comparator) {}
FileMatchTrie::~FileMatchTrie() {
delete Root;
}
void FileMatchTrie::insert(StringRef NewPath) {
Root->insert(NewPath);
}
StringRef FileMatchTrie::findEquivalent(StringRef FileName,
raw_ostream &Error) const {
if (llvm::sys::path::is_relative(FileName)) {
Error << "Cannot resolve relative paths";
return StringRef();
}
bool IsAmbiguous = false;
StringRef Result = Root->findEquivalent(*Comparator, FileName, IsAmbiguous);
if (IsAmbiguous)
Error << "Path is ambiguous";
return Result;
}

View File

@ -0,0 +1,31 @@
//===--- FixIt.cpp - FixIt Hint utilities -----------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file contains implementations of utitilies to ease source code rewriting
// by providing helper functions related to FixItHint.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/FixIt.h"
#include "clang/Lex/Lexer.h"
namespace clang {
namespace tooling {
namespace fixit {
namespace internal {
StringRef getText(SourceRange Range, const ASTContext &Context) {
return Lexer::getSourceText(CharSourceRange::getTokenRange(Range),
Context.getSourceManager(),
Context.getLangOpts());
}
} // end namespace internal
} // end namespace fixit
} // end namespace tooling
} // end namespace clang

View File

@ -0,0 +1,375 @@
//===--- JSONCompilationDatabase.cpp - ------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file contains the implementation of the JSONCompilationDatabase.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/JSONCompilationDatabase.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "clang/Tooling/CompilationDatabasePluginRegistry.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/StringSaver.h"
#include <system_error>
namespace clang {
namespace tooling {
namespace {
/// \brief A parser for escaped strings of command line arguments.
///
/// Assumes \-escaping for quoted arguments (see the documentation of
/// unescapeCommandLine(...)).
class CommandLineArgumentParser {
public:
CommandLineArgumentParser(StringRef CommandLine)
: Input(CommandLine), Position(Input.begin()-1) {}
std::vector<std::string> parse() {
bool HasMoreInput = true;
while (HasMoreInput && nextNonWhitespace()) {
std::string Argument;
HasMoreInput = parseStringInto(Argument);
CommandLine.push_back(Argument);
}
return CommandLine;
}
private:
// All private methods return true if there is more input available.
bool parseStringInto(std::string &String) {
do {
if (*Position == '"') {
if (!parseDoubleQuotedStringInto(String)) return false;
} else if (*Position == '\'') {
if (!parseSingleQuotedStringInto(String)) return false;
} else {
if (!parseFreeStringInto(String)) return false;
}
} while (*Position != ' ');
return true;
}
bool parseDoubleQuotedStringInto(std::string &String) {
if (!next()) return false;
while (*Position != '"') {
if (!skipEscapeCharacter()) return false;
String.push_back(*Position);
if (!next()) return false;
}
return next();
}
bool parseSingleQuotedStringInto(std::string &String) {
if (!next()) return false;
while (*Position != '\'') {
String.push_back(*Position);
if (!next()) return false;
}
return next();
}
bool parseFreeStringInto(std::string &String) {
do {
if (!skipEscapeCharacter()) return false;
String.push_back(*Position);
if (!next()) return false;
} while (*Position != ' ' && *Position != '"' && *Position != '\'');
return true;
}
bool skipEscapeCharacter() {
if (*Position == '\\') {
return next();
}
return true;
}
bool nextNonWhitespace() {
do {
if (!next()) return false;
} while (*Position == ' ');
return true;
}
bool next() {
++Position;
return Position != Input.end();
}
const StringRef Input;
StringRef::iterator Position;
std::vector<std::string> CommandLine;
};
std::vector<std::string> unescapeCommandLine(JSONCommandLineSyntax Syntax,
StringRef EscapedCommandLine) {
if (Syntax == JSONCommandLineSyntax::AutoDetect) {
Syntax = JSONCommandLineSyntax::Gnu;
llvm::Triple Triple(llvm::sys::getProcessTriple());
if (Triple.getOS() == llvm::Triple::OSType::Win32) {
// Assume Windows command line parsing on Win32 unless the triple
// explicitly tells us otherwise.
if (!Triple.hasEnvironment() ||
Triple.getEnvironment() == llvm::Triple::EnvironmentType::MSVC)
Syntax = JSONCommandLineSyntax::Windows;
}
}
if (Syntax == JSONCommandLineSyntax::Windows) {
llvm::BumpPtrAllocator Alloc;
llvm::StringSaver Saver(Alloc);
llvm::SmallVector<const char *, 64> T;
llvm::cl::TokenizeWindowsCommandLine(EscapedCommandLine, Saver, T);
std::vector<std::string> Result(T.begin(), T.end());
return Result;
}
assert(Syntax == JSONCommandLineSyntax::Gnu);
CommandLineArgumentParser parser(EscapedCommandLine);
return parser.parse();
}
class JSONCompilationDatabasePlugin : public CompilationDatabasePlugin {
std::unique_ptr<CompilationDatabase>
loadFromDirectory(StringRef Directory, std::string &ErrorMessage) override {
SmallString<1024> JSONDatabasePath(Directory);
llvm::sys::path::append(JSONDatabasePath, "compile_commands.json");
return JSONCompilationDatabase::loadFromFile(
JSONDatabasePath, ErrorMessage, JSONCommandLineSyntax::AutoDetect);
}
};
} // end namespace
// Register the JSONCompilationDatabasePlugin with the
// CompilationDatabasePluginRegistry using this statically initialized variable.
static CompilationDatabasePluginRegistry::Add<JSONCompilationDatabasePlugin>
X("json-compilation-database", "Reads JSON formatted compilation databases");
// This anchor is used to force the linker to link in the generated object file
// and thus register the JSONCompilationDatabasePlugin.
volatile int JSONAnchorSource = 0;
std::unique_ptr<JSONCompilationDatabase>
JSONCompilationDatabase::loadFromFile(StringRef FilePath,
std::string &ErrorMessage,
JSONCommandLineSyntax Syntax) {
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> DatabaseBuffer =
llvm::MemoryBuffer::getFile(FilePath);
if (std::error_code Result = DatabaseBuffer.getError()) {
ErrorMessage = "Error while opening JSON database: " + Result.message();
return nullptr;
}
std::unique_ptr<JSONCompilationDatabase> Database(
new JSONCompilationDatabase(std::move(*DatabaseBuffer), Syntax));
if (!Database->parse(ErrorMessage))
return nullptr;
return Database;
}
std::unique_ptr<JSONCompilationDatabase>
JSONCompilationDatabase::loadFromBuffer(StringRef DatabaseString,
std::string &ErrorMessage,
JSONCommandLineSyntax Syntax) {
std::unique_ptr<llvm::MemoryBuffer> DatabaseBuffer(
llvm::MemoryBuffer::getMemBuffer(DatabaseString));
std::unique_ptr<JSONCompilationDatabase> Database(
new JSONCompilationDatabase(std::move(DatabaseBuffer), Syntax));
if (!Database->parse(ErrorMessage))
return nullptr;
return Database;
}
std::vector<CompileCommand>
JSONCompilationDatabase::getCompileCommands(StringRef FilePath) const {
SmallString<128> NativeFilePath;
llvm::sys::path::native(FilePath, NativeFilePath);
std::string Error;
llvm::raw_string_ostream ES(Error);
StringRef Match = MatchTrie.findEquivalent(NativeFilePath, ES);
if (Match.empty())
return std::vector<CompileCommand>();
llvm::StringMap< std::vector<CompileCommandRef> >::const_iterator
CommandsRefI = IndexByFile.find(Match);
if (CommandsRefI == IndexByFile.end())
return std::vector<CompileCommand>();
std::vector<CompileCommand> Commands;
getCommands(CommandsRefI->getValue(), Commands);
return Commands;
}
std::vector<std::string>
JSONCompilationDatabase::getAllFiles() const {
std::vector<std::string> Result;
llvm::StringMap< std::vector<CompileCommandRef> >::const_iterator
CommandsRefI = IndexByFile.begin();
const llvm::StringMap< std::vector<CompileCommandRef> >::const_iterator
CommandsRefEnd = IndexByFile.end();
for (; CommandsRefI != CommandsRefEnd; ++CommandsRefI) {
Result.push_back(CommandsRefI->first().str());
}
return Result;
}
std::vector<CompileCommand>
JSONCompilationDatabase::getAllCompileCommands() const {
std::vector<CompileCommand> Commands;
getCommands(AllCommands, Commands);
return Commands;
}
static std::vector<std::string>
nodeToCommandLine(JSONCommandLineSyntax Syntax,
const std::vector<llvm::yaml::ScalarNode *> &Nodes) {
SmallString<1024> Storage;
if (Nodes.size() == 1) {
return unescapeCommandLine(Syntax, Nodes[0]->getValue(Storage));
}
std::vector<std::string> Arguments;
for (auto *Node : Nodes) {
Arguments.push_back(Node->getValue(Storage));
}
return Arguments;
}
void JSONCompilationDatabase::getCommands(
ArrayRef<CompileCommandRef> CommandsRef,
std::vector<CompileCommand> &Commands) const {
for (int I = 0, E = CommandsRef.size(); I != E; ++I) {
SmallString<8> DirectoryStorage;
SmallString<32> FilenameStorage;
SmallString<32> OutputStorage;
auto Output = std::get<3>(CommandsRef[I]);
Commands.emplace_back(
std::get<0>(CommandsRef[I])->getValue(DirectoryStorage),
std::get<1>(CommandsRef[I])->getValue(FilenameStorage),
nodeToCommandLine(Syntax, std::get<2>(CommandsRef[I])),
Output ? Output->getValue(OutputStorage) : "");
}
}
bool JSONCompilationDatabase::parse(std::string &ErrorMessage) {
llvm::yaml::document_iterator I = YAMLStream.begin();
if (I == YAMLStream.end()) {
ErrorMessage = "Error while parsing YAML.";
return false;
}
llvm::yaml::Node *Root = I->getRoot();
if (!Root) {
ErrorMessage = "Error while parsing YAML.";
return false;
}
llvm::yaml::SequenceNode *Array = dyn_cast<llvm::yaml::SequenceNode>(Root);
if (!Array) {
ErrorMessage = "Expected array.";
return false;
}
for (auto& NextObject : *Array) {
llvm::yaml::MappingNode *Object = dyn_cast<llvm::yaml::MappingNode>(&NextObject);
if (!Object) {
ErrorMessage = "Expected object.";
return false;
}
llvm::yaml::ScalarNode *Directory = nullptr;
llvm::Optional<std::vector<llvm::yaml::ScalarNode *>> Command;
llvm::yaml::ScalarNode *File = nullptr;
llvm::yaml::ScalarNode *Output = nullptr;
for (auto& NextKeyValue : *Object) {
llvm::yaml::ScalarNode *KeyString =
dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
if (!KeyString) {
ErrorMessage = "Expected strings as key.";
return false;
}
SmallString<10> KeyStorage;
StringRef KeyValue = KeyString->getValue(KeyStorage);
llvm::yaml::Node *Value = NextKeyValue.getValue();
if (!Value) {
ErrorMessage = "Expected value.";
return false;
}
llvm::yaml::ScalarNode *ValueString =
dyn_cast<llvm::yaml::ScalarNode>(Value);
llvm::yaml::SequenceNode *SequenceString =
dyn_cast<llvm::yaml::SequenceNode>(Value);
if (KeyValue == "arguments" && !SequenceString) {
ErrorMessage = "Expected sequence as value.";
return false;
} else if (KeyValue != "arguments" && !ValueString) {
ErrorMessage = "Expected string as value.";
return false;
}
if (KeyValue == "directory") {
Directory = ValueString;
} else if (KeyValue == "arguments") {
Command = std::vector<llvm::yaml::ScalarNode *>();
for (auto &Argument : *SequenceString) {
auto Scalar = dyn_cast<llvm::yaml::ScalarNode>(&Argument);
if (!Scalar) {
ErrorMessage = "Only strings are allowed in 'arguments'.";
return false;
}
Command->push_back(Scalar);
}
} else if (KeyValue == "command") {
if (!Command)
Command = std::vector<llvm::yaml::ScalarNode *>(1, ValueString);
} else if (KeyValue == "file") {
File = ValueString;
} else if (KeyValue == "output") {
Output = ValueString;
} else {
ErrorMessage = ("Unknown key: \"" +
KeyString->getRawValue() + "\"").str();
return false;
}
}
if (!File) {
ErrorMessage = "Missing key: \"file\".";
return false;
}
if (!Command) {
ErrorMessage = "Missing key: \"command\" or \"arguments\".";
return false;
}
if (!Directory) {
ErrorMessage = "Missing key: \"directory\".";
return false;
}
SmallString<8> FileStorage;
StringRef FileName = File->getValue(FileStorage);
SmallString<128> NativeFilePath;
if (llvm::sys::path::is_relative(FileName)) {
SmallString<8> DirectoryStorage;
SmallString<128> AbsolutePath(
Directory->getValue(DirectoryStorage));
llvm::sys::path::append(AbsolutePath, FileName);
llvm::sys::path::native(AbsolutePath, NativeFilePath);
} else {
llvm::sys::path::native(FileName, NativeFilePath);
}
auto Cmd = CompileCommandRef(Directory, File, *Command, Output);
IndexByFile[NativeFilePath].push_back(Cmd);
AllCommands.push_back(Cmd);
MatchTrie.insert(NativeFilePath);
}
return true;
}
} // end namespace tooling
} // end namespace clang

View File

@ -0,0 +1,104 @@
//===--- Refactoring.cpp - Framework for clang refactoring tools ----------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Implements tools to support refactorings.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Refactoring.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Format/Format.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Lex/Lexer.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_os_ostream.h"
namespace clang {
namespace tooling {
RefactoringTool::RefactoringTool(
const CompilationDatabase &Compilations, ArrayRef<std::string> SourcePaths,
std::shared_ptr<PCHContainerOperations> PCHContainerOps)
: ClangTool(Compilations, SourcePaths, std::move(PCHContainerOps)) {}
std::map<std::string, Replacements> &RefactoringTool::getReplacements() {
return FileToReplaces;
}
int RefactoringTool::runAndSave(FrontendActionFactory *ActionFactory) {
if (int Result = run(ActionFactory)) {
return Result;
}
LangOptions DefaultLangOptions;
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts);
DiagnosticsEngine Diagnostics(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()),
&*DiagOpts, &DiagnosticPrinter, false);
SourceManager Sources(Diagnostics, getFiles());
Rewriter Rewrite(Sources, DefaultLangOptions);
if (!applyAllReplacements(Rewrite)) {
llvm::errs() << "Skipped some replacements.\n";
}
return saveRewrittenFiles(Rewrite);
}
bool RefactoringTool::applyAllReplacements(Rewriter &Rewrite) {
bool Result = true;
for (const auto &Entry : groupReplacementsByFile(
Rewrite.getSourceMgr().getFileManager(), FileToReplaces))
Result = tooling::applyAllReplacements(Entry.second, Rewrite) && Result;
return Result;
}
int RefactoringTool::saveRewrittenFiles(Rewriter &Rewrite) {
return Rewrite.overwriteChangedFiles() ? 1 : 0;
}
bool formatAndApplyAllReplacements(
const std::map<std::string, Replacements> &FileToReplaces,
Rewriter &Rewrite, StringRef Style) {
SourceManager &SM = Rewrite.getSourceMgr();
FileManager &Files = SM.getFileManager();
bool Result = true;
for (const auto &FileAndReplaces : groupReplacementsByFile(
Rewrite.getSourceMgr().getFileManager(), FileToReplaces)) {
const std::string &FilePath = FileAndReplaces.first;
auto &CurReplaces = FileAndReplaces.second;
const FileEntry *Entry = Files.getFile(FilePath);
FileID ID = SM.getOrCreateFileID(Entry, SrcMgr::C_User);
StringRef Code = SM.getBufferData(ID);
auto CurStyle = format::getStyle(Style, FilePath, "LLVM");
if (!CurStyle) {
llvm::errs() << llvm::toString(CurStyle.takeError()) << "\n";
return false;
}
auto NewReplacements =
format::formatReplacements(Code, CurReplaces, *CurStyle);
if (!NewReplacements) {
llvm::errs() << llvm::toString(NewReplacements.takeError()) << "\n";
return false;
}
Result = applyAllReplacements(*NewReplacements, Rewrite) && Result;
}
return Result;
}
} // end namespace tooling
} // end namespace clang

View File

@ -0,0 +1,453 @@
//===--- ASTSelection.cpp - Clang refactoring library ---------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Refactoring/ASTSelection.h"
#include "clang/AST/LexicallyOrderedRecursiveASTVisitor.h"
#include "clang/Lex/Lexer.h"
#include "llvm/Support/SaveAndRestore.h"
using namespace clang;
using namespace tooling;
using ast_type_traits::DynTypedNode;
namespace {
CharSourceRange getLexicalDeclRange(Decl *D, const SourceManager &SM,
const LangOptions &LangOpts) {
if (!isa<ObjCImplDecl>(D))
return CharSourceRange::getTokenRange(D->getSourceRange());
// Objective-C implementation declarations end at the '@' instead of the 'end'
// keyword. Use the lexer to find the location right after 'end'.
SourceRange R = D->getSourceRange();
SourceLocation LocAfterEnd = Lexer::findLocationAfterToken(
R.getEnd(), tok::raw_identifier, SM, LangOpts,
/*SkipTrailingWhitespaceAndNewLine=*/false);
return LocAfterEnd.isValid()
? CharSourceRange::getCharRange(R.getBegin(), LocAfterEnd)
: CharSourceRange::getTokenRange(R);
}
/// Constructs the tree of selected AST nodes that either contain the location
/// of the cursor or overlap with the selection range.
class ASTSelectionFinder
: public LexicallyOrderedRecursiveASTVisitor<ASTSelectionFinder> {
public:
ASTSelectionFinder(SourceRange Selection, FileID TargetFile,
const ASTContext &Context)
: LexicallyOrderedRecursiveASTVisitor(Context.getSourceManager()),
SelectionBegin(Selection.getBegin()),
SelectionEnd(Selection.getBegin() == Selection.getEnd()
? SourceLocation()
: Selection.getEnd()),
TargetFile(TargetFile), Context(Context) {
// The TU decl is the root of the selected node tree.
SelectionStack.push_back(
SelectedASTNode(DynTypedNode::create(*Context.getTranslationUnitDecl()),
SourceSelectionKind::None));
}
Optional<SelectedASTNode> getSelectedASTNode() {
assert(SelectionStack.size() == 1 && "stack was not popped");
SelectedASTNode Result = std::move(SelectionStack.back());
SelectionStack.pop_back();
if (Result.Children.empty())
return None;
return std::move(Result);
}
bool TraversePseudoObjectExpr(PseudoObjectExpr *E) {
// Avoid traversing the semantic expressions. They should be handled by
// looking through the appropriate opaque expressions in order to build
// a meaningful selection tree.
llvm::SaveAndRestore<bool> LookThrough(LookThroughOpaqueValueExprs, true);
return TraverseStmt(E->getSyntacticForm());
}
bool TraverseOpaqueValueExpr(OpaqueValueExpr *E) {
if (!LookThroughOpaqueValueExprs)
return true;
llvm::SaveAndRestore<bool> LookThrough(LookThroughOpaqueValueExprs, false);
return TraverseStmt(E->getSourceExpr());
}
bool TraverseDecl(Decl *D) {
if (isa<TranslationUnitDecl>(D))
return LexicallyOrderedRecursiveASTVisitor::TraverseDecl(D);
if (D->isImplicit())
return true;
// Check if this declaration is written in the file of interest.
const SourceRange DeclRange = D->getSourceRange();
const SourceManager &SM = Context.getSourceManager();
SourceLocation FileLoc;
if (DeclRange.getBegin().isMacroID() && !DeclRange.getEnd().isMacroID())
FileLoc = DeclRange.getEnd();
else
FileLoc = SM.getSpellingLoc(DeclRange.getBegin());
if (SM.getFileID(FileLoc) != TargetFile)
return true;
SourceSelectionKind SelectionKind =
selectionKindFor(getLexicalDeclRange(D, SM, Context.getLangOpts()));
SelectionStack.push_back(
SelectedASTNode(DynTypedNode::create(*D), SelectionKind));
LexicallyOrderedRecursiveASTVisitor::TraverseDecl(D);
popAndAddToSelectionIfSelected(SelectionKind);
if (DeclRange.getEnd().isValid() &&
SM.isBeforeInTranslationUnit(SelectionEnd.isValid() ? SelectionEnd
: SelectionBegin,
DeclRange.getEnd())) {
// Stop early when we've reached a declaration after the selection.
return false;
}
return true;
}
bool TraverseStmt(Stmt *S) {
if (!S)
return true;
if (auto *Opaque = dyn_cast<OpaqueValueExpr>(S))
return TraverseOpaqueValueExpr(Opaque);
// Avoid selecting implicit 'this' expressions.
if (auto *TE = dyn_cast<CXXThisExpr>(S)) {
if (TE->isImplicit())
return true;
}
// FIXME (Alex Lorenz): Improve handling for macro locations.
SourceSelectionKind SelectionKind =
selectionKindFor(CharSourceRange::getTokenRange(S->getSourceRange()));
SelectionStack.push_back(
SelectedASTNode(DynTypedNode::create(*S), SelectionKind));
LexicallyOrderedRecursiveASTVisitor::TraverseStmt(S);
popAndAddToSelectionIfSelected(SelectionKind);
return true;
}
private:
void popAndAddToSelectionIfSelected(SourceSelectionKind SelectionKind) {
SelectedASTNode Node = std::move(SelectionStack.back());
SelectionStack.pop_back();
if (SelectionKind != SourceSelectionKind::None || !Node.Children.empty())
SelectionStack.back().Children.push_back(std::move(Node));
}
SourceSelectionKind selectionKindFor(CharSourceRange Range) {
SourceLocation End = Range.getEnd();
const SourceManager &SM = Context.getSourceManager();
if (Range.isTokenRange())
End = Lexer::getLocForEndOfToken(End, 0, SM, Context.getLangOpts());
if (!SourceLocation::isPairOfFileLocations(Range.getBegin(), End))
return SourceSelectionKind::None;
if (!SelectionEnd.isValid()) {
// Do a quick check when the selection is of length 0.
if (SM.isPointWithin(SelectionBegin, Range.getBegin(), End))
return SourceSelectionKind::ContainsSelection;
return SourceSelectionKind::None;
}
bool HasStart = SM.isPointWithin(SelectionBegin, Range.getBegin(), End);
bool HasEnd = SM.isPointWithin(SelectionEnd, Range.getBegin(), End);
if (HasStart && HasEnd)
return SourceSelectionKind::ContainsSelection;
if (SM.isPointWithin(Range.getBegin(), SelectionBegin, SelectionEnd) &&
SM.isPointWithin(End, SelectionBegin, SelectionEnd))
return SourceSelectionKind::InsideSelection;
// Ensure there's at least some overlap with the 'start'/'end' selection
// types.
if (HasStart && SelectionBegin != End)
return SourceSelectionKind::ContainsSelectionStart;
if (HasEnd && SelectionEnd != Range.getBegin())
return SourceSelectionKind::ContainsSelectionEnd;
return SourceSelectionKind::None;
}
const SourceLocation SelectionBegin, SelectionEnd;
FileID TargetFile;
const ASTContext &Context;
std::vector<SelectedASTNode> SelectionStack;
/// Controls whether we can traverse through the OpaqueValueExpr. This is
/// typically enabled during the traversal of syntactic form for
/// PseudoObjectExprs.
bool LookThroughOpaqueValueExprs = false;
};
} // end anonymous namespace
Optional<SelectedASTNode>
clang::tooling::findSelectedASTNodes(const ASTContext &Context,
SourceRange SelectionRange) {
assert(SelectionRange.isValid() &&
SourceLocation::isPairOfFileLocations(SelectionRange.getBegin(),
SelectionRange.getEnd()) &&
"Expected a file range");
FileID TargetFile =
Context.getSourceManager().getFileID(SelectionRange.getBegin());
assert(Context.getSourceManager().getFileID(SelectionRange.getEnd()) ==
TargetFile &&
"selection range must span one file");
ASTSelectionFinder Visitor(SelectionRange, TargetFile, Context);
Visitor.TraverseDecl(Context.getTranslationUnitDecl());
return Visitor.getSelectedASTNode();
}
static const char *selectionKindToString(SourceSelectionKind Kind) {
switch (Kind) {
case SourceSelectionKind::None:
return "none";
case SourceSelectionKind::ContainsSelection:
return "contains-selection";
case SourceSelectionKind::ContainsSelectionStart:
return "contains-selection-start";
case SourceSelectionKind::ContainsSelectionEnd:
return "contains-selection-end";
case SourceSelectionKind::InsideSelection:
return "inside";
}
llvm_unreachable("invalid selection kind");
}
static void dump(const SelectedASTNode &Node, llvm::raw_ostream &OS,
unsigned Indent = 0) {
OS.indent(Indent * 2);
if (const Decl *D = Node.Node.get<Decl>()) {
OS << D->getDeclKindName() << "Decl";
if (const auto *ND = dyn_cast<NamedDecl>(D))
OS << " \"" << ND->getNameAsString() << '"';
} else if (const Stmt *S = Node.Node.get<Stmt>()) {
OS << S->getStmtClassName();
}
OS << ' ' << selectionKindToString(Node.SelectionKind) << "\n";
for (const auto &Child : Node.Children)
dump(Child, OS, Indent + 1);
}
void SelectedASTNode::dump(llvm::raw_ostream &OS) const { ::dump(*this, OS); }
/// Returns true if the given node has any direct children with the following
/// selection kind.
///
/// Note: The direct children also include children of direct children with the
/// "None" selection kind.
static bool hasAnyDirectChildrenWithKind(const SelectedASTNode &Node,
SourceSelectionKind Kind) {
assert(Kind != SourceSelectionKind::None && "invalid predicate!");
for (const auto &Child : Node.Children) {
if (Child.SelectionKind == Kind)
return true;
if (Child.SelectionKind == SourceSelectionKind::None)
return hasAnyDirectChildrenWithKind(Child, Kind);
}
return false;
}
namespace {
struct SelectedNodeWithParents {
SelectedNodeWithParents(SelectedNodeWithParents &&) = default;
SelectedNodeWithParents &operator=(SelectedNodeWithParents &&) = default;
SelectedASTNode::ReferenceType Node;
llvm::SmallVector<SelectedASTNode::ReferenceType, 8> Parents;
/// Canonicalizes the given selection by selecting different related AST nodes
/// when it makes sense to do so.
void canonicalize();
};
enum SelectionCanonicalizationAction { KeepSelection, SelectParent };
/// Returns the canonicalization action which should be applied to the
/// selected statement.
SelectionCanonicalizationAction
getSelectionCanonizalizationAction(const Stmt *S, const Stmt *Parent) {
// Select the parent expression when:
// - The string literal in ObjC string literal is selected, e.g.:
// @"test" becomes @"test"
// ~~~~~~ ~~~~~~~
if (isa<StringLiteral>(S) && isa<ObjCStringLiteral>(Parent))
return SelectParent;
// The entire call should be selected when just the member expression
// that refers to the method or the decl ref that refers to the function
// is selected.
// f.call(args) becomes f.call(args)
// ~~~~ ~~~~~~~~~~~~
// func(args) becomes func(args)
// ~~~~ ~~~~~~~~~~
else if (const auto *CE = dyn_cast<CallExpr>(Parent)) {
if ((isa<MemberExpr>(S) || isa<DeclRefExpr>(S)) &&
CE->getCallee()->IgnoreImpCasts() == S)
return SelectParent;
}
// FIXME: Syntactic form -> Entire pseudo-object expr.
return KeepSelection;
}
} // end anonymous namespace
void SelectedNodeWithParents::canonicalize() {
const Stmt *S = Node.get().Node.get<Stmt>();
assert(S && "non statement selection!");
const Stmt *Parent = Parents[Parents.size() - 1].get().Node.get<Stmt>();
if (!Parent)
return;
// Look through the implicit casts in the parents.
unsigned ParentIndex = 1;
for (; (ParentIndex + 1) <= Parents.size() && isa<ImplicitCastExpr>(Parent);
++ParentIndex) {
const Stmt *NewParent =
Parents[Parents.size() - ParentIndex - 1].get().Node.get<Stmt>();
if (!NewParent)
break;
Parent = NewParent;
}
switch (getSelectionCanonizalizationAction(S, Parent)) {
case SelectParent:
Node = Parents[Parents.size() - ParentIndex];
for (; ParentIndex != 0; --ParentIndex)
Parents.pop_back();
break;
case KeepSelection:
break;
}
}
/// Finds the set of bottom-most selected AST nodes that are in the selection
/// tree with the specified selection kind.
///
/// For example, given the following selection tree:
///
/// FunctionDecl "f" contains-selection
/// CompoundStmt contains-selection [#1]
/// CallExpr inside
/// ImplicitCastExpr inside
/// DeclRefExpr inside
/// IntegerLiteral inside
/// IntegerLiteral inside
/// FunctionDecl "f2" contains-selection
/// CompoundStmt contains-selection [#2]
/// CallExpr inside
/// ImplicitCastExpr inside
/// DeclRefExpr inside
/// IntegerLiteral inside
/// IntegerLiteral inside
///
/// This function will find references to nodes #1 and #2 when searching for the
/// \c ContainsSelection kind.
static void findDeepestWithKind(
const SelectedASTNode &ASTSelection,
llvm::SmallVectorImpl<SelectedNodeWithParents> &MatchingNodes,
SourceSelectionKind Kind,
llvm::SmallVectorImpl<SelectedASTNode::ReferenceType> &ParentStack) {
if (ASTSelection.Node.get<DeclStmt>()) {
// Select the entire decl stmt when any of its child declarations is the
// bottom-most.
for (const auto &Child : ASTSelection.Children) {
if (!hasAnyDirectChildrenWithKind(Child, Kind)) {
MatchingNodes.push_back(SelectedNodeWithParents{
std::cref(ASTSelection), {ParentStack.begin(), ParentStack.end()}});
return;
}
}
} else {
if (!hasAnyDirectChildrenWithKind(ASTSelection, Kind)) {
// This node is the bottom-most.
MatchingNodes.push_back(SelectedNodeWithParents{
std::cref(ASTSelection), {ParentStack.begin(), ParentStack.end()}});
return;
}
}
// Search in the children.
ParentStack.push_back(std::cref(ASTSelection));
for (const auto &Child : ASTSelection.Children)
findDeepestWithKind(Child, MatchingNodes, Kind, ParentStack);
ParentStack.pop_back();
}
static void findDeepestWithKind(
const SelectedASTNode &ASTSelection,
llvm::SmallVectorImpl<SelectedNodeWithParents> &MatchingNodes,
SourceSelectionKind Kind) {
llvm::SmallVector<SelectedASTNode::ReferenceType, 16> ParentStack;
findDeepestWithKind(ASTSelection, MatchingNodes, Kind, ParentStack);
}
Optional<CodeRangeASTSelection>
CodeRangeASTSelection::create(SourceRange SelectionRange,
const SelectedASTNode &ASTSelection) {
// Code range is selected when the selection range is not empty.
if (SelectionRange.getBegin() == SelectionRange.getEnd())
return None;
llvm::SmallVector<SelectedNodeWithParents, 4> ContainSelection;
findDeepestWithKind(ASTSelection, ContainSelection,
SourceSelectionKind::ContainsSelection);
// We are looking for a selection in one body of code, so let's focus on
// one matching result.
if (ContainSelection.size() != 1)
return None;
SelectedNodeWithParents &Selected = ContainSelection[0];
if (!Selected.Node.get().Node.get<Stmt>())
return None;
const Stmt *CodeRangeStmt = Selected.Node.get().Node.get<Stmt>();
if (!isa<CompoundStmt>(CodeRangeStmt)) {
Selected.canonicalize();
return CodeRangeASTSelection(Selected.Node, Selected.Parents,
/*AreChildrenSelected=*/false);
}
// FIXME (Alex L): First selected SwitchCase means that first case statement.
// is selected actually
// (See https://github.com/apple/swift-clang & CompoundStmtRange).
// FIXME (Alex L): Tweak selection rules for compound statements, see:
// https://github.com/apple/swift-clang/blob/swift-4.1-branch/lib/Tooling/
// Refactor/ASTSlice.cpp#L513
// The user selected multiple statements in a compound statement.
Selected.Parents.push_back(Selected.Node);
return CodeRangeASTSelection(Selected.Node, Selected.Parents,
/*AreChildrenSelected=*/true);
}
static bool isFunctionLikeDeclaration(const Decl *D) {
// FIXME (Alex L): Test for BlockDecl.
return isa<FunctionDecl>(D) || isa<ObjCMethodDecl>(D);
}
bool CodeRangeASTSelection::isInFunctionLikeBodyOfCode() const {
bool IsPrevCompound = false;
// Scan through the parents (bottom-to-top) and check if the selection is
// contained in a compound statement that's a body of a function/method
// declaration.
for (const auto &Parent : llvm::reverse(Parents)) {
const DynTypedNode &Node = Parent.get().Node;
if (const auto *D = Node.get<Decl>()) {
if (isFunctionLikeDeclaration(D))
return IsPrevCompound;
// Stop the search at any type declaration to avoid returning true for
// expressions in type declarations in functions, like:
// function foo() { struct X {
// int m = /*selection:*/ 1 + 2 /*selection end*/; }; };
if (isa<TypeDecl>(D))
return false;
}
IsPrevCompound = Node.get<CompoundStmt>() != nullptr;
}
return false;
}
const Decl *CodeRangeASTSelection::getFunctionLikeNearestParent() const {
for (const auto &Parent : llvm::reverse(Parents)) {
const DynTypedNode &Node = Parent.get().Node;
if (const auto *D = Node.get<Decl>()) {
if (isFunctionLikeDeclaration(D))
return D;
}
}
return nullptr;
}

View File

@ -0,0 +1,48 @@
//===--- ASTSelectionRequirements.cpp - Clang refactoring library ---------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h"
using namespace clang;
using namespace tooling;
Expected<SelectedASTNode>
ASTSelectionRequirement::evaluate(RefactoringRuleContext &Context) const {
// FIXME: Memoize so that selection is evaluated only once.
Expected<SourceRange> Range =
SourceRangeSelectionRequirement::evaluate(Context);
if (!Range)
return Range.takeError();
Optional<SelectedASTNode> Selection =
findSelectedASTNodes(Context.getASTContext(), *Range);
if (!Selection)
return Context.createDiagnosticError(
Range->getBegin(), diag::err_refactor_selection_invalid_ast);
return std::move(*Selection);
}
Expected<CodeRangeASTSelection> CodeRangeASTSelectionRequirement::evaluate(
RefactoringRuleContext &Context) const {
// FIXME: Memoize so that selection is evaluated only once.
Expected<SelectedASTNode> ASTSelection =
ASTSelectionRequirement::evaluate(Context);
if (!ASTSelection)
return ASTSelection.takeError();
std::unique_ptr<SelectedASTNode> StoredSelection =
llvm::make_unique<SelectedASTNode>(std::move(*ASTSelection));
Optional<CodeRangeASTSelection> CodeRange = CodeRangeASTSelection::create(
Context.getSelectionRange(), *StoredSelection);
if (!CodeRange)
return Context.createDiagnosticError(
Context.getSelectionRange().getBegin(),
diag::err_refactor_selection_invalid_ast);
Context.setASTSelection(std::move(StoredSelection));
return std::move(*CodeRange);
}

View File

@ -0,0 +1,365 @@
//===--- AtomicChange.cpp - AtomicChange implementation -----------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Refactoring/AtomicChange.h"
#include "clang/Tooling/ReplacementsYaml.h"
#include "llvm/Support/YAMLTraits.h"
#include <string>
LLVM_YAML_IS_SEQUENCE_VECTOR(clang::tooling::AtomicChange)
namespace {
/// \brief Helper to (de)serialize an AtomicChange since we don't have direct
/// access to its data members.
/// Data members of a normalized AtomicChange can be directly mapped from/to
/// YAML string.
struct NormalizedAtomicChange {
NormalizedAtomicChange() = default;
NormalizedAtomicChange(const llvm::yaml::IO &) {}
// This converts AtomicChange's internal implementation of the replacements
// set to a vector of replacements.
NormalizedAtomicChange(const llvm::yaml::IO &,
const clang::tooling::AtomicChange &E)
: Key(E.getKey()), FilePath(E.getFilePath()), Error(E.getError()),
InsertedHeaders(E.getInsertedHeaders()),
RemovedHeaders(E.getRemovedHeaders()),
Replaces(E.getReplacements().begin(), E.getReplacements().end()) {}
// This is not expected to be called but needed for template instantiation.
clang::tooling::AtomicChange denormalize(const llvm::yaml::IO &) {
llvm_unreachable("Do not convert YAML to AtomicChange directly with '>>'. "
"Use AtomicChange::convertFromYAML instead.");
}
std::string Key;
std::string FilePath;
std::string Error;
std::vector<std::string> InsertedHeaders;
std::vector<std::string> RemovedHeaders;
std::vector<clang::tooling::Replacement> Replaces;
};
} // anonymous namespace
namespace llvm {
namespace yaml {
/// \brief Specialized MappingTraits to describe how an AtomicChange is
/// (de)serialized.
template <> struct MappingTraits<NormalizedAtomicChange> {
static void mapping(IO &Io, NormalizedAtomicChange &Doc) {
Io.mapRequired("Key", Doc.Key);
Io.mapRequired("FilePath", Doc.FilePath);
Io.mapRequired("Error", Doc.Error);
Io.mapRequired("InsertedHeaders", Doc.InsertedHeaders);
Io.mapRequired("RemovedHeaders", Doc.RemovedHeaders);
Io.mapRequired("Replacements", Doc.Replaces);
}
};
/// \brief Specialized MappingTraits to describe how an AtomicChange is
/// (de)serialized.
template <> struct MappingTraits<clang::tooling::AtomicChange> {
static void mapping(IO &Io, clang::tooling::AtomicChange &Doc) {
MappingNormalization<NormalizedAtomicChange, clang::tooling::AtomicChange>
Keys(Io, Doc);
Io.mapRequired("Key", Keys->Key);
Io.mapRequired("FilePath", Keys->FilePath);
Io.mapRequired("Error", Keys->Error);
Io.mapRequired("InsertedHeaders", Keys->InsertedHeaders);
Io.mapRequired("RemovedHeaders", Keys->RemovedHeaders);
Io.mapRequired("Replacements", Keys->Replaces);
}
};
} // end namespace yaml
} // end namespace llvm
namespace clang {
namespace tooling {
namespace {
// Returns true if there is any line that violates \p ColumnLimit in range
// [Start, End].
bool violatesColumnLimit(llvm::StringRef Code, unsigned ColumnLimit,
unsigned Start, unsigned End) {
auto StartPos = Code.rfind('\n', Start);
StartPos = (StartPos == llvm::StringRef::npos) ? 0 : StartPos + 1;
auto EndPos = Code.find("\n", End);
if (EndPos == llvm::StringRef::npos)
EndPos = Code.size();
llvm::SmallVector<llvm::StringRef, 8> Lines;
Code.substr(StartPos, EndPos - StartPos).split(Lines, '\n');
for (llvm::StringRef Line : Lines)
if (Line.size() > ColumnLimit)
return true;
return false;
}
std::vector<Range>
getRangesForFormating(llvm::StringRef Code, unsigned ColumnLimit,
ApplyChangesSpec::FormatOption Format,
const clang::tooling::Replacements &Replaces) {
// kNone suppresses formatting entirely.
if (Format == ApplyChangesSpec::kNone)
return {};
std::vector<clang::tooling::Range> Ranges;
// This works assuming that replacements are ordered by offset.
// FIXME: use `getAffectedRanges()` to calculate when it does not include '\n'
// at the end of an insertion in affected ranges.
int Offset = 0;
for (const clang::tooling::Replacement &R : Replaces) {
int Start = R.getOffset() + Offset;
int End = Start + R.getReplacementText().size();
if (!R.getReplacementText().empty() &&
R.getReplacementText().back() == '\n' && R.getLength() == 0 &&
R.getOffset() > 0 && R.getOffset() <= Code.size() &&
Code[R.getOffset() - 1] == '\n')
// If we are inserting at the start of a line and the replacement ends in
// a newline, we don't need to format the subsequent line.
--End;
Offset += R.getReplacementText().size() - R.getLength();
if (Format == ApplyChangesSpec::kAll ||
violatesColumnLimit(Code, ColumnLimit, Start, End))
Ranges.emplace_back(Start, End - Start);
}
return Ranges;
}
inline llvm::Error make_string_error(const llvm::Twine &Message) {
return llvm::make_error<llvm::StringError>(Message,
llvm::inconvertibleErrorCode());
}
// Creates replacements for inserting/deleting #include headers.
llvm::Expected<Replacements>
createReplacementsForHeaders(llvm::StringRef FilePath, llvm::StringRef Code,
llvm::ArrayRef<AtomicChange> Changes,
const format::FormatStyle &Style) {
// Create header insertion/deletion replacements to be cleaned up
// (i.e. converted to real insertion/deletion replacements).
Replacements HeaderReplacements;
for (const auto &Change : Changes) {
for (llvm::StringRef Header : Change.getInsertedHeaders()) {
std::string EscapedHeader =
Header.startswith("<") || Header.startswith("\"")
? Header.str()
: ("\"" + Header + "\"").str();
std::string ReplacementText = "#include " + EscapedHeader;
// Offset UINT_MAX and length 0 indicate that the replacement is a header
// insertion.
llvm::Error Err = HeaderReplacements.add(
tooling::Replacement(FilePath, UINT_MAX, 0, ReplacementText));
if (Err)
return std::move(Err);
}
for (const std::string &Header : Change.getRemovedHeaders()) {
// Offset UINT_MAX and length 1 indicate that the replacement is a header
// deletion.
llvm::Error Err =
HeaderReplacements.add(Replacement(FilePath, UINT_MAX, 1, Header));
if (Err)
return std::move(Err);
}
}
// cleanupAroundReplacements() converts header insertions/deletions into
// actual replacements that add/remove headers at the right location.
return clang::format::cleanupAroundReplacements(Code, HeaderReplacements,
Style);
}
// Combine replacements in all Changes as a `Replacements`. This ignores the
// file path in all replacements and replaces them with \p FilePath.
llvm::Expected<Replacements>
combineReplacementsInChanges(llvm::StringRef FilePath,
llvm::ArrayRef<AtomicChange> Changes) {
Replacements Replaces;
for (const auto &Change : Changes)
for (const auto &R : Change.getReplacements())
if (auto Err = Replaces.add(Replacement(
FilePath, R.getOffset(), R.getLength(), R.getReplacementText())))
return std::move(Err);
return Replaces;
}
} // end namespace
AtomicChange::AtomicChange(const SourceManager &SM,
SourceLocation KeyPosition) {
const FullSourceLoc FullKeyPosition(KeyPosition, SM);
std::pair<FileID, unsigned> FileIDAndOffset =
FullKeyPosition.getSpellingLoc().getDecomposedLoc();
const FileEntry *FE = SM.getFileEntryForID(FileIDAndOffset.first);
assert(FE && "Cannot create AtomicChange with invalid location.");
FilePath = FE->getName();
Key = FilePath + ":" + std::to_string(FileIDAndOffset.second);
}
AtomicChange::AtomicChange(std::string Key, std::string FilePath,
std::string Error,
std::vector<std::string> InsertedHeaders,
std::vector<std::string> RemovedHeaders,
clang::tooling::Replacements Replaces)
: Key(std::move(Key)), FilePath(std::move(FilePath)),
Error(std::move(Error)), InsertedHeaders(std::move(InsertedHeaders)),
RemovedHeaders(std::move(RemovedHeaders)), Replaces(std::move(Replaces)) {
}
bool AtomicChange::operator==(const AtomicChange &Other) const {
if (Key != Other.Key || FilePath != Other.FilePath || Error != Other.Error)
return false;
if (!(Replaces == Other.Replaces))
return false;
// FXIME: Compare header insertions/removals.
return true;
}
std::string AtomicChange::toYAMLString() {
std::string YamlContent;
llvm::raw_string_ostream YamlContentStream(YamlContent);
llvm::yaml::Output YAML(YamlContentStream);
YAML << *this;
YamlContentStream.flush();
return YamlContent;
}
AtomicChange AtomicChange::convertFromYAML(llvm::StringRef YAMLContent) {
NormalizedAtomicChange NE;
llvm::yaml::Input YAML(YAMLContent);
YAML >> NE;
AtomicChange E(NE.Key, NE.FilePath, NE.Error, NE.InsertedHeaders,
NE.RemovedHeaders, tooling::Replacements());
for (const auto &R : NE.Replaces) {
llvm::Error Err = E.Replaces.add(R);
if (Err)
llvm_unreachable(
"Failed to add replacement when Converting YAML to AtomicChange.");
llvm::consumeError(std::move(Err));
}
return E;
}
llvm::Error AtomicChange::replace(const SourceManager &SM,
const CharSourceRange &Range,
llvm::StringRef ReplacementText) {
return Replaces.add(Replacement(SM, Range, ReplacementText));
}
llvm::Error AtomicChange::replace(const SourceManager &SM, SourceLocation Loc,
unsigned Length, llvm::StringRef Text) {
return Replaces.add(Replacement(SM, Loc, Length, Text));
}
llvm::Error AtomicChange::insert(const SourceManager &SM, SourceLocation Loc,
llvm::StringRef Text, bool InsertAfter) {
if (Text.empty())
return llvm::Error::success();
Replacement R(SM, Loc, 0, Text);
llvm::Error Err = Replaces.add(R);
if (Err) {
return llvm::handleErrors(
std::move(Err), [&](const ReplacementError &RE) -> llvm::Error {
if (RE.get() != replacement_error::insert_conflict)
return llvm::make_error<ReplacementError>(RE);
unsigned NewOffset = Replaces.getShiftedCodePosition(R.getOffset());
if (!InsertAfter)
NewOffset -=
RE.getExistingReplacement()->getReplacementText().size();
Replacement NewR(R.getFilePath(), NewOffset, 0, Text);
Replaces = Replaces.merge(Replacements(NewR));
return llvm::Error::success();
});
}
return llvm::Error::success();
}
void AtomicChange::addHeader(llvm::StringRef Header) {
InsertedHeaders.push_back(Header);
}
void AtomicChange::removeHeader(llvm::StringRef Header) {
RemovedHeaders.push_back(Header);
}
llvm::Expected<std::string>
applyAtomicChanges(llvm::StringRef FilePath, llvm::StringRef Code,
llvm::ArrayRef<AtomicChange> Changes,
const ApplyChangesSpec &Spec) {
llvm::Expected<Replacements> HeaderReplacements =
createReplacementsForHeaders(FilePath, Code, Changes, Spec.Style);
if (!HeaderReplacements)
return make_string_error(
"Failed to create replacements for header changes: " +
llvm::toString(HeaderReplacements.takeError()));
llvm::Expected<Replacements> Replaces =
combineReplacementsInChanges(FilePath, Changes);
if (!Replaces)
return make_string_error("Failed to combine replacements in all changes: " +
llvm::toString(Replaces.takeError()));
Replacements AllReplaces = std::move(*Replaces);
for (const auto &R : *HeaderReplacements) {
llvm::Error Err = AllReplaces.add(R);
if (Err)
return make_string_error(
"Failed to combine existing replacements with header replacements: " +
llvm::toString(std::move(Err)));
}
if (Spec.Cleanup) {
llvm::Expected<Replacements> CleanReplaces =
format::cleanupAroundReplacements(Code, AllReplaces, Spec.Style);
if (!CleanReplaces)
return make_string_error("Failed to cleanup around replacements: " +
llvm::toString(CleanReplaces.takeError()));
AllReplaces = std::move(*CleanReplaces);
}
// Apply all replacements.
llvm::Expected<std::string> ChangedCode =
applyAllReplacements(Code, AllReplaces);
if (!ChangedCode)
return make_string_error("Failed to apply all replacements: " +
llvm::toString(ChangedCode.takeError()));
// Sort inserted headers. This is done even if other formatting is turned off
// as incorrectly sorted headers are always just wrong, it's not a matter of
// taste.
Replacements HeaderSortingReplacements = format::sortIncludes(
Spec.Style, *ChangedCode, AllReplaces.getAffectedRanges(), FilePath);
ChangedCode = applyAllReplacements(*ChangedCode, HeaderSortingReplacements);
if (!ChangedCode)
return make_string_error(
"Failed to apply replacements for sorting includes: " +
llvm::toString(ChangedCode.takeError()));
AllReplaces = AllReplaces.merge(HeaderSortingReplacements);
std::vector<Range> FormatRanges = getRangesForFormating(
*ChangedCode, Spec.Style.ColumnLimit, Spec.Format, AllReplaces);
if (!FormatRanges.empty()) {
Replacements FormatReplacements =
format::reformat(Spec.Style, *ChangedCode, FormatRanges, FilePath);
ChangedCode = applyAllReplacements(*ChangedCode, FormatReplacements);
if (!ChangedCode)
return make_string_error(
"Failed to apply replacements for formatting changed code: " +
llvm::toString(ChangedCode.takeError()));
}
return ChangedCode;
}
} // end namespace tooling
} // end namespace clang

View File

@ -0,0 +1,25 @@
set(LLVM_LINK_COMPONENTS Support)
add_clang_library(clangToolingRefactor
ASTSelection.cpp
ASTSelectionRequirements.cpp
AtomicChange.cpp
Extract/Extract.cpp
Extract/SourceExtraction.cpp
RefactoringActions.cpp
Rename/RenamingAction.cpp
Rename/SymbolOccurrences.cpp
Rename/USRFinder.cpp
Rename/USRFindingAction.cpp
Rename/USRLocFinder.cpp
LINK_LIBS
clangAST
clangASTMatchers
clangBasic
clangFormat
clangIndex
clangLex
clangRewrite
clangToolingCore
)

View File

@ -0,0 +1,199 @@
//===--- Extract.cpp - Clang refactoring library --------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief Implements the "extract" refactoring that can pull code into
/// new functions, methods or declare new variables.
///
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Refactoring/Extract/Extract.h"
#include "SourceExtraction.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprObjC.h"
#include "clang/Rewrite/Core/Rewriter.h"
namespace clang {
namespace tooling {
namespace {
/// Returns true if \c E is a simple literal or a reference expression that
/// should not be extracted.
bool isSimpleExpression(const Expr *E) {
if (!E)
return false;
switch (E->IgnoreParenCasts()->getStmtClass()) {
case Stmt::DeclRefExprClass:
case Stmt::PredefinedExprClass:
case Stmt::IntegerLiteralClass:
case Stmt::FloatingLiteralClass:
case Stmt::ImaginaryLiteralClass:
case Stmt::CharacterLiteralClass:
case Stmt::StringLiteralClass:
return true;
default:
return false;
}
}
SourceLocation computeFunctionExtractionLocation(const Decl *D) {
if (isa<CXXMethodDecl>(D)) {
// Code from method that is defined in class body should be extracted to a
// function defined just before the class.
while (const auto *RD = dyn_cast<CXXRecordDecl>(D->getLexicalDeclContext()))
D = RD;
}
return D->getLocStart();
}
} // end anonymous namespace
const RefactoringDescriptor &ExtractFunction::describe() {
static const RefactoringDescriptor Descriptor = {
"extract-function",
"Extract Function",
"(WIP action; use with caution!) Extracts code into a new function",
};
return Descriptor;
}
Expected<ExtractFunction>
ExtractFunction::initiate(RefactoringRuleContext &Context,
CodeRangeASTSelection Code,
Optional<std::string> DeclName) {
// We would like to extract code out of functions/methods/blocks.
// Prohibit extraction from things like global variable / field
// initializers and other top-level expressions.
if (!Code.isInFunctionLikeBodyOfCode())
return Context.createDiagnosticError(
diag::err_refactor_code_outside_of_function);
if (Code.size() == 1) {
// Avoid extraction of simple literals and references.
if (isSimpleExpression(dyn_cast<Expr>(Code[0])))
return Context.createDiagnosticError(
diag::err_refactor_extract_simple_expression);
// Property setters can't be extracted.
if (const auto *PRE = dyn_cast<ObjCPropertyRefExpr>(Code[0])) {
if (!PRE->isMessagingGetter())
return Context.createDiagnosticError(
diag::err_refactor_extract_prohibited_expression);
}
}
return ExtractFunction(std::move(Code), DeclName);
}
// FIXME: Support C++ method extraction.
// FIXME: Support Objective-C method extraction.
Expected<AtomicChanges>
ExtractFunction::createSourceReplacements(RefactoringRuleContext &Context) {
const Decl *ParentDecl = Code.getFunctionLikeNearestParent();
assert(ParentDecl && "missing parent");
// Compute the source range of the code that should be extracted.
SourceRange ExtractedRange(Code[0]->getLocStart(),
Code[Code.size() - 1]->getLocEnd());
// FIXME (Alex L): Add code that accounts for macro locations.
ASTContext &AST = Context.getASTContext();
SourceManager &SM = AST.getSourceManager();
const LangOptions &LangOpts = AST.getLangOpts();
Rewriter ExtractedCodeRewriter(SM, LangOpts);
// FIXME: Capture used variables.
// Compute the return type.
QualType ReturnType = AST.VoidTy;
// FIXME (Alex L): Account for the return statement in extracted code.
// FIXME (Alex L): Check for lexical expression instead.
bool IsExpr = Code.size() == 1 && isa<Expr>(Code[0]);
if (IsExpr) {
// FIXME (Alex L): Get a more user-friendly type if needed.
ReturnType = cast<Expr>(Code[0])->getType();
}
// FIXME: Rewrite the extracted code performing any required adjustments.
// FIXME: Capture any field if necessary (method -> function extraction).
// FIXME: Sort captured variables by name.
// FIXME: Capture 'this' / 'self' if necessary.
// FIXME: Compute the actual parameter types.
// Compute the location of the extracted declaration.
SourceLocation ExtractedDeclLocation =
computeFunctionExtractionLocation(ParentDecl);
// FIXME: Adjust the location to account for any preceding comments.
// FIXME: Adjust with PP awareness like in Sema to get correct 'bool'
// treatment.
PrintingPolicy PP = AST.getPrintingPolicy();
// FIXME: PP.UseStdFunctionForLambda = true;
PP.SuppressStrongLifetime = true;
PP.SuppressLifetimeQualifiers = true;
PP.SuppressUnwrittenScope = true;
ExtractionSemicolonPolicy Semicolons = ExtractionSemicolonPolicy::compute(
Code[Code.size() - 1], ExtractedRange, SM, LangOpts);
AtomicChange Change(SM, ExtractedDeclLocation);
// Create the replacement for the extracted declaration.
{
std::string ExtractedCode;
llvm::raw_string_ostream OS(ExtractedCode);
// FIXME: Use 'inline' in header.
OS << "static ";
ReturnType.print(OS, PP, DeclName);
OS << '(';
// FIXME: Arguments.
OS << ')';
// Function body.
OS << " {\n";
if (IsExpr && !ReturnType->isVoidType())
OS << "return ";
OS << ExtractedCodeRewriter.getRewrittenText(ExtractedRange);
if (Semicolons.isNeededInExtractedFunction())
OS << ';';
OS << "\n}\n\n";
auto Err = Change.insert(SM, ExtractedDeclLocation, OS.str());
if (Err)
return std::move(Err);
}
// Create the replacement for the call to the extracted declaration.
{
std::string ReplacedCode;
llvm::raw_string_ostream OS(ReplacedCode);
OS << DeclName << '(';
// FIXME: Forward arguments.
OS << ')';
if (Semicolons.isNeededInOriginalFunction())
OS << ';';
auto Err = Change.replace(
SM, CharSourceRange::getTokenRange(ExtractedRange), OS.str());
if (Err)
return std::move(Err);
}
// FIXME: Add support for assocciated symbol location to AtomicChange to mark
// the ranges of the name of the extracted declaration.
return AtomicChanges{std::move(Change)};
}
} // end namespace tooling
} // end namespace clang

Some files were not shown because too many files have changed in this diff Show More