blob: 424d130d14f866ad1b78a84817245f4baed3d954 [file] [log] [blame]
//===--- ClangRefactorTest.cpp - ------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements a clang-refactor-test tool that is used to test the
// refactoring library in Clang.
//
//===----------------------------------------------------------------------===//
#include "clang-c/Refactor.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Frontend/CommandLineSourceLoc.h"
#include "clang/Tooling/Refactor/SymbolName.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/LineIterator.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Regex.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
using namespace clang;
namespace opts {
static cl::OptionCategory
ClangRefactorTestOptions("clang-refactor-test common options");
cl::SubCommand RenameInitiateSubcommand(
"rename-initiate", "Initiate renaming in an initial translation unit");
cl::SubCommand RenameInitiateUSRSubcommand(
"rename-initiate-usr",
"Initiate renaming in an translation unit on a specific declaration");
cl::SubCommand RenameIndexedFileSubcommand(
"rename-indexed-file",
"Initiate renaming and find occurrences in an indexed file");
cl::SubCommand ListRefactoringActionsSubcommand("list-actions",
"Print the list of the "
"refactoring actions that can "
"be performed at the specified "
"location");
cl::SubCommand InitiateActionSubcommand("initiate",
"Initiate a refactoring action");
cl::SubCommand
PerformActionSubcommand("perform",
"Initiate and perform a refactoring action");
const cl::desc
AtOptionDescription("The location at which the refactoring should be "
"initiated (<file>:<line>:<column>)");
const cl::desc InRangeOptionDescription(
"The location(s) at which the refactoring should be "
"initiated (<file>:<line>:<column>-<last-column>)");
const cl::desc SelectedRangeOptionDescription(
"The selected source range in which the refactoring should be "
"initiated (<file>:<line>:<column>-<line>:<column>)");
static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
namespace rename {
static cl::list<std::string> AtLocation("at", AtOptionDescription, cl::Required,
cl::cat(ClangRefactorTestOptions),
cl::sub(RenameInitiateSubcommand),
cl::OneOrMore);
static cl::opt<std::string>
USR("usr", cl::desc("The USR of the declaration that should be renamed"),
cl::cat(ClangRefactorTestOptions), cl::sub(RenameInitiateUSRSubcommand),
cl::Required);
static cl::opt<std::string>
NewName("new-name", cl::desc("The new name to change the symbol to."),
cl::Required, cl::cat(ClangRefactorTestOptions),
cl::sub(RenameInitiateSubcommand),
cl::sub(RenameInitiateUSRSubcommand));
static cl::list<std::string>
IndexedNames("name", cl::desc("The names of the renamed symbols"),
cl::Required, cl::OneOrMore, cl::cat(ClangRefactorTestOptions),
cl::sub(RenameIndexedFileSubcommand));
static cl::list<std::string> IndexedNewNames(
"new-name", cl::desc("The new name to change the symbol to."), cl::Required,
cl::OneOrMore, cl::cat(ClangRefactorTestOptions),
cl::sub(RenameIndexedFileSubcommand));
static cl::opt<std::string>
IndexedSymbolKind("indexed-symbol-kind",
cl::desc("The kind of the indexed symbol."), cl::Optional,
cl::cat(ClangRefactorTestOptions),
cl::sub(RenameIndexedFileSubcommand));
static cl::opt<std::string>
IndexedFileName("indexed-file", cl::desc("The name of the indexed file"),
cl::Required, cl::cat(ClangRefactorTestOptions),
cl::sub(RenameIndexedFileSubcommand));
static cl::list<std::string>
IndexedLocations("indexed-at",
cl::desc("The location of an indexed occurrence "
"([<kind>|<symbol-index>:]<line>:<column>)"),
cl::ZeroOrMore, cl::cat(ClangRefactorTestOptions),
cl::sub(RenameIndexedFileSubcommand));
static cl::opt<bool> AvoidTextual(
"no-textual-matches", cl::desc("Avoid searching for textual matches"),
cl::cat(ClangRefactorTestOptions), cl::sub(RenameIndexedFileSubcommand));
static cl::opt<bool> DumpSymbols(
"dump-symbols", cl::desc("Dump the information about the renamed symbols"),
cl::cat(ClangRefactorTestOptions), cl::sub(RenameInitiateSubcommand),
cl::sub(RenameInitiateUSRSubcommand));
}
namespace listActions {
cl::opt<std::string> AtLocation("at", AtOptionDescription, cl::Required,
cl::cat(ClangRefactorTestOptions),
cl::sub(ListRefactoringActionsSubcommand));
cl::opt<std::string> SelectedRange("selected", SelectedRangeOptionDescription,
cl::cat(ClangRefactorTestOptions),
cl::sub(ListRefactoringActionsSubcommand));
cl::opt<bool> DumpRawActionType(
"dump-raw-action-type",
cl::desc("Prints the action type integer value for each listed action"),
cl::cat(ClangRefactorTestOptions),
cl::sub(ListRefactoringActionsSubcommand));
}
namespace initiateAndPerform {
cl::list<std::string> InLocationRanges("in", cl::ZeroOrMore,
InRangeOptionDescription,
cl::cat(ClangRefactorTestOptions),
cl::sub(InitiateActionSubcommand));
cl::list<std::string> AtLocations("at", cl::ZeroOrMore, AtOptionDescription,
cl::cat(ClangRefactorTestOptions),
cl::sub(InitiateActionSubcommand),
cl::sub(PerformActionSubcommand));
cl::list<std::string> SelectedRanges("selected", cl::ZeroOrMore,
SelectedRangeOptionDescription,
cl::cat(ClangRefactorTestOptions),
cl::sub(InitiateActionSubcommand),
cl::sub(PerformActionSubcommand));
cl::opt<std::string> ActionName("action", cl::Required,
cl::desc("The name of the refactoring action"),
cl::cat(ClangRefactorTestOptions),
cl::sub(InitiateActionSubcommand),
cl::sub(PerformActionSubcommand));
cl::opt<bool> LocationAgnostic(
"location-agnostic",
cl::desc(
"Ignore the location of initiation when verifying result consistency"),
cl::cat(ClangRefactorTestOptions), cl::sub(InitiateActionSubcommand));
cl::opt<unsigned> CandidateIndex(
"candidate",
cl::desc(
"The index of the refactoring candidate which should be performed"),
cl::cat(ClangRefactorTestOptions), cl::sub(PerformActionSubcommand));
cl::opt<std::string> ContinuationFile(
"continuation-file",
cl::desc("The source file in which the continuation should run"),
cl::cat(ClangRefactorTestOptions), cl::sub(PerformActionSubcommand));
cl::opt<std::string> QueryResults(
"query-results", cl::desc("The indexer query results that should be passed "
"into the continuation"),
cl::cat(ClangRefactorTestOptions), cl::sub(PerformActionSubcommand));
cl::opt<bool> EmitAssociatedInfo(
"emit-associated", cl::desc("Dump additional associated information"),
cl::cat(ClangRefactorTestOptions), cl::sub(PerformActionSubcommand));
}
cl::opt<bool> Apply(
"apply",
cl::desc(
"Apply the changes and print the modified file to standard output"),
cl::cat(ClangRefactorTestOptions), cl::sub(PerformActionSubcommand),
cl::sub(RenameInitiateSubcommand), cl::sub(RenameIndexedFileSubcommand));
cl::opt<bool>
Diff("diff",
cl::desc("Display the replaced text in red when -apply is specified"),
cl::cat(ClangRefactorTestOptions), cl::sub(PerformActionSubcommand),
cl::sub(RenameInitiateSubcommand),
cl::sub(RenameIndexedFileSubcommand));
cl::opt<int> Context("context", cl::desc("How many lines of context should be "
"displayed when -apply is specified"),
cl::cat(ClangRefactorTestOptions),
cl::sub(PerformActionSubcommand),
cl::sub(RenameInitiateSubcommand),
cl::sub(RenameIndexedFileSubcommand));
static cl::opt<std::string> FileName(
cl::Positional, cl::desc("<filename>"), cl::Required,
cl::cat(ClangRefactorTestOptions), cl::sub(RenameInitiateSubcommand),
cl::sub(RenameInitiateUSRSubcommand), cl::sub(RenameIndexedFileSubcommand),
cl::sub(ListRefactoringActionsSubcommand),
cl::sub(InitiateActionSubcommand), cl::sub(PerformActionSubcommand));
static cl::opt<bool> IgnoreFilenameForInitiationTU(
"ignore-filename-for-initiation-tu", cl::Optional,
cl::cat(ClangRefactorTestOptions), cl::sub(RenameIndexedFileSubcommand));
static cl::list<std::string> CompilerArguments(
cl::ConsumeAfter, cl::desc("<arguments to be passed to the compiler>"),
cl::cat(ClangRefactorTestOptions), cl::sub(RenameInitiateSubcommand),
cl::sub(RenameInitiateUSRSubcommand), cl::sub(RenameIndexedFileSubcommand),
cl::sub(ListRefactoringActionsSubcommand),
cl::sub(InitiateActionSubcommand), cl::sub(PerformActionSubcommand));
static cl::opt<std::string> ImplementationTU(
"implementation-tu", cl::desc("The name of the implementation TU"),
cl::cat(ClangRefactorTestOptions), cl::sub(RenameInitiateSubcommand));
}
static const char *renameOccurrenceKindString(CXSymbolOccurrenceKind Kind,
bool IsLocal,
bool IsMacroExpansion) {
switch (Kind) {
case CXSymbolOccurrence_MatchingSymbol:
return IsMacroExpansion ? "macro" : IsLocal ? "rename local" : "rename";
case CXSymbolOccurrence_MatchingSelector:
assert(!IsLocal && "Objective-C selector renames must be global");
return IsMacroExpansion ? "selector in macro" : "selector";
case CXSymbolOccurrence_MatchingImplicitProperty:
assert(!IsLocal);
return IsMacroExpansion ? "implicit-property in macro"
: "implicit-property";
case CXSymbolOccurrence_MatchingCommentString:
return "comment";
case CXSymbolOccurrence_MatchingDocCommentString:
return "documentation";
case CXSymbolOccurrence_MatchingFilename:
return "filename";
case CXSymbolOccurrence_ExtractedDeclaration:
return "extracted-decl";
case CXSymbolOccurrence_ExtractedDeclaration_Reference:
return "extracted-decl-ref";
}
}
static int apply(ArrayRef<CXRefactoringReplacement> Replacements,
StringRef Filename) {
// Assume that the replacements are sorted.
auto Result = MemoryBuffer::getFile(Filename);
if (!Result) {
errs() << "Failed to open " << Filename << "\n";
return 1;
}
raw_ostream &OS = outs();
int Context = opts::Context;
MemoryBuffer &Buffer = **Result;
std::vector<std::pair<StringRef, std::vector<CXRefactoringReplacement>>>
Lines;
for (auto I = line_iterator(Buffer, /*SkipBlanks=*/false),
E = line_iterator();
I != E; ++I)
Lines.push_back(
std::make_pair(*I, std::vector<CXRefactoringReplacement>()));
unsigned FlushedLine = 1;
auto FlushUntil = [&](unsigned Line) {
// Adjust the first flushed line if needed when printing in context mode.
if (FlushedLine == 1 && Context)
FlushedLine = std::max(int(Line) - Context, 1);
for (; FlushedLine < Line; ++FlushedLine) {
const auto &Line = Lines[FlushedLine - 1];
if (Line.second.empty()) {
OS << Line.first << "\n";
continue;
}
unsigned I = 0;
for (const CXRefactoringReplacement &Replacement : Line.second) {
OS << Line.first.substr(I, Replacement.Range.Begin.Column - 1 - I);
if (opts::Diff) {
OS.changeColor(raw_ostream::RED, false, true);
OS << Line.first.substr(Replacement.Range.Begin.Column - 1,
Replacement.Range.End.Column - 1 -
(Replacement.Range.Begin.Column - 1));
}
OS.changeColor(raw_ostream::GREEN);
OS << clang_getCString(Replacement.ReplacementString);
OS.resetColor();
I = Replacement.Range.End.Column - 1;
}
OS << Line.first.substr(I);
if (I < Line.first.size() || opts::Diff)
OS << "\n";
}
};
int EndLineMax = 0;
for (const CXRefactoringReplacement &Replacement : Replacements) {
EndLineMax = std::max(int(Replacement.Range.End.Line), EndLineMax);
unsigned StartingLine = Replacement.Range.Begin.Line;
FlushUntil(StartingLine);
if (Replacement.Range.End.Line == StartingLine) {
Lines[StartingLine - 1].second.push_back(Replacement);
continue;
}
// Multi-line replacements have to be split
for (unsigned I = StartingLine; I <= Replacement.Range.End.Line; ++I) {
CXRefactoringReplacement NewReplacement;
if (I == Replacement.Range.End.Line)
NewReplacement.ReplacementString = Replacement.ReplacementString;
else
// FIXME: This is a hack to workaround the fact that the API doesn't
// provide a way to create a null string. This should be fixed when
// upstreaming.
NewReplacement.ReplacementString = {0, 0};
NewReplacement.Range.Begin.Line = I;
NewReplacement.Range.Begin.Column =
I == StartingLine ? Replacement.Range.Begin.Column : 1;
NewReplacement.Range.End.Line = I;
NewReplacement.Range.End.Column = I == Replacement.Range.End.Line
? Replacement.Range.End.Column
: Lines[I - 1].first.size() + 1;
NewReplacement.AssociatedData = nullptr;
Lines[I - 1].second.push_back(NewReplacement);
}
}
FlushUntil(Context ? std::min(int(Lines.size()), EndLineMax + Context) + 1
: Lines.size() + 2);
// Print out a dividor when printing in the context mode.
if (Context) {
for (int I = 0; I < 80; ++I)
OS << '-';
OS << "\n";
}
return 0;
}
/// Converts the given renamed \p Occurrence into a string value that represents
/// this occurrence.
static std::string
occurrenceToString(const CXSymbolOccurrence &Occurrence, bool IsLocal,
const tooling::SymbolName &NewName,
const tooling::SymbolName &ExpectedReplacementStrings,
StringRef Filename) {
std::string Str;
llvm::raw_string_ostream OS(Str);
OS << renameOccurrenceKindString(Occurrence.Kind, IsLocal,
Occurrence.IsMacroExpansion)
<< ' ';
if (!Filename.empty())
OS << '"' << Filename << "\" ";
bool FirstRange = true;
assert(NewName.size() >= Occurrence.NumNamePieces &&
"new name doesn't match the number of pieces");
for (unsigned J = 0; J != Occurrence.NumNamePieces; ++J) {
if (!FirstRange) // TODO
OS << ", ";
// Print the replacement string if it doesn't match the expected string.
if (NewName[J] != ExpectedReplacementStrings[J])
OS << '"' << NewName[J] << "\" ";
CXFileRange Range = Occurrence.NamePieces[J];
OS << Range.Begin.Line << ":" << Range.Begin.Column << " -> "
<< Range.End.Line << ":" << Range.End.Column;
FirstRange = false;
}
return OS.str();
}
static CXCursorKind
renameIndexedOccurrenceKindStringToKind(StringRef Str, CXCursorKind Default) {
return llvm::StringSwitch<CXCursorKind>(Str)
.Case("objc-im", CXCursor_ObjCInstanceMethodDecl)
.Case("objc-cm", CXCursor_ObjCClassMethodDecl)
.Case("objc-message", CXCursor_ObjCMessageExpr)
.Case("include", CXCursor_InclusionDirective)
.Default(Default);
}
/// Parses the string passed as the -indexed-at argument.
std::pair<CXRenamedIndexedSymbolLocation, unsigned>
parseIndexedOccurrence(StringRef IndexedOccurrence,
CXCursorKind DefaultCursorKind) {
StringRef LineColumnLoc = IndexedOccurrence;
CXCursorKind Kind = DefaultCursorKind;
unsigned SymbolIndex = 0;
if (LineColumnLoc.count(':') > 1) {
std::pair<StringRef, StringRef> Split = LineColumnLoc.split(':');
// The first value is either the kind or the symbol index.
if (Split.first.getAsInteger(10, SymbolIndex)) {
if (Split.second.count(':') > 1) {
std::pair<StringRef, StringRef> SecondSplit = Split.second.split(':');
if (SecondSplit.first.getAsInteger(10, SymbolIndex))
assert(false && "expected symbol index");
Split.second = SecondSplit.second;
}
Kind = renameIndexedOccurrenceKindStringToKind(Split.first, Kind);
}
LineColumnLoc = Split.second;
}
auto Loc = std::string("-:") + LineColumnLoc.str();
auto Location = ParsedSourceLocation::FromString(Loc);
return std::make_pair(
CXRenamedIndexedSymbolLocation{{Location.Line, Location.Column}, Kind},
SymbolIndex);
}
/// Compare the produced occurrences to the expected occurrences that were
/// gathered at the first location. Return true if the occurrences are
/// different.
static bool compareOccurrences(ArrayRef<std::string> ExpectedReplacements,
CXSymbolOccurrencesResult Occurrences,
bool IsLocal,
const tooling::SymbolName &NewSymbolName,
bool PrintFilenames) {
unsigned NumFiles = clang_SymbolOccurrences_getNumFiles(Occurrences);
size_t ExpectedReplacementIndex = 0;
for (unsigned FileIndex = 0; FileIndex < NumFiles; ++FileIndex) {
CXSymbolOccurrencesInFile FileResult;
clang_SymbolOccurrences_getOccurrencesForFile(Occurrences, FileIndex,
&FileResult);
StringRef Filename =
PrintFilenames ? clang_getCString(FileResult.Filename) : "";
for (unsigned I = 0; I != FileResult.NumOccurrences; ++I) {
std::string Replacement =
occurrenceToString(FileResult.Occurrences[I], IsLocal, NewSymbolName,
NewSymbolName, Filename);
if (ExpectedReplacementIndex >= ExpectedReplacements.size() ||
Replacement != ExpectedReplacements[ExpectedReplacementIndex])
return true;
++ExpectedReplacementIndex;
}
}
// Verify that all of the expected replacements were checked.
return ExpectedReplacementIndex != ExpectedReplacements.size();
}
struct ImplementationTUWrapper {
CXTranslationUnit TU = nullptr;
ImplementationTUWrapper() {}
~ImplementationTUWrapper() { clang_disposeTranslationUnit(TU); }
ImplementationTUWrapper(const ImplementationTUWrapper &) = delete;
ImplementationTUWrapper &operator=(const ImplementationTUWrapper &) = delete;
bool load(CXRefactoringAction Action, CXIndex CIdx,
ArrayRef<const char *> Args);
};
bool ImplementationTUWrapper::load(CXRefactoringAction Action, CXIndex CIdx,
ArrayRef<const char *> Args) {
if (!clang_RefactoringAction_requiresImplementationTU(Action))
return false;
CXString USR =
clang_RefactoringAction_getUSRThatRequiresImplementationTU(Action);
outs() << "Implementation TU USR: '" << clang_getCString(USR) << "'\n";
clang_disposeString(USR);
if (!TU) {
CXErrorCode Err = clang_parseTranslationUnit2(
CIdx, opts::ImplementationTU.c_str(), Args.data(), Args.size(), 0, 0,
CXTranslationUnit_KeepGoing, &TU);
if (Err != CXError_Success) {
errs() << "error: failed to load implementation TU '"
<< opts::ImplementationTU << "'\n";
return true;
}
}
CXErrorCode Err = clang_RefactoringAction_addImplementationTU(Action, TU);
if (Err != CXError_Success) {
errs() << "error: failed to add implementation TU '"
<< opts::ImplementationTU << "'\n";
return true;
}
return false;
}
static bool reportNewNameError(CXErrorCode Err) {
std::string NewName = opts::RenameIndexedFileSubcommand
? opts::rename::IndexedNewNames[0]
: opts::rename::NewName;
if (Err == CXError_RefactoringNameSizeMismatch)
errs() << "error: the number of strings in the new name '" << NewName
<< "' doesn't match the the number of strings in the old name\n";
else if (Err == CXError_RefactoringNameInvalid)
errs() << "error: invalid new name '" << NewName << "'\n";
else
return true;
return false;
}
int rename(CXTranslationUnit TU, CXIndex CIdx, ArrayRef<const char *> Args) {
assert(!opts::RenameIndexedFileSubcommand);
// Contains the renamed source replacements for the first location. It is
// compared to replacements from follow-up renames to ensure that all renames
// give the same result.
std::vector<std::string> ExpectedReplacements;
// Should we print out the filenames. False by default, but true when multiple
// files are modified.
bool PrintFilenames = false;
ImplementationTUWrapper ImplementationTU;
auto RenameAt = [&](const ParsedSourceLocation &Location,
const std::string &USR) -> int {
CXRefactoringAction RenamingAction;
CXErrorCode Err;
CXDiagnosticSet Diags = nullptr;
if (USR.empty()) {
CXSourceLocation Loc =
clang_getLocation(TU, clang_getFile(TU, Location.FileName.c_str()),
Location.Line, Location.Column);
Err = clang_Refactoring_initiateAction(
TU, Loc, clang_getNullRange(), CXRefactor_Rename,
/*Options=*/nullptr, &RenamingAction, &Diags);
} else {
Err = clang_Refactoring_initiateActionOnDecl(
TU, USR.c_str(), CXRefactor_Rename, /*Options=*/nullptr,
&RenamingAction, nullptr);
}
if (Err != CXError_Success) {
errs() << "error: could not rename symbol "
<< (USR.empty() ? "at the given location\n"
: "with the given USR\n");
if (USR.empty()) {
unsigned NumDiags = clang_getNumDiagnosticsInSet(Diags);
for (unsigned DiagID = 0; DiagID < NumDiags; ++DiagID) {
CXDiagnostic Diag = clang_getDiagnosticInSet(Diags, DiagID);
CXString Spelling = clang_getDiagnosticSpelling(Diag);
errs() << clang_getCString(Spelling) << "\n";
clang_disposeString(Spelling);
}
}
clang_disposeDiagnosticSet(Diags);
return 1;
}
clang_disposeDiagnosticSet(Diags);
if (ImplementationTU.load(RenamingAction, CIdx, Args))
return 1;
Err = clang_Refactoring_initiateRenamingOperation(RenamingAction);
if (Err != CXError_Success) {
errs() << "error: failed to initiate the renaming operation!\n";
return 1;
}
bool IsLocal = clang_RefactoringAction_getInitiatedActionType(
RenamingAction) == CXRefactor_Rename_Local;
unsigned NumSymbols = clang_RenamingOperation_getNumSymbols(RenamingAction);
if (opts::rename::DumpSymbols) {
outs() << "Renaming " << NumSymbols << " symbols\n";
for (unsigned I = 0; I < NumSymbols; ++I) {
CXString USR =
clang_RenamingOperation_getUSRForSymbol(RenamingAction, I);
outs() << "'" << clang_getCString(USR) << "'\n";
clang_disposeString(USR);
}
}
CXSymbolOccurrencesResult Occurrences;
Occurrences = clang_Refactoring_findSymbolOccurrencesInInitiationTU(
RenamingAction, Args.data(), Args.size(), 0, 0);
clang_RefactoringAction_dispose(RenamingAction);
// FIXME: This is a hack
LangOptions LangOpts;
LangOpts.ObjC1 = true;
tooling::SymbolName NewSymbolName(opts::rename::NewName, LangOpts);
if (ExpectedReplacements.empty()) {
if (opts::Apply) {
// FIXME: support --apply.
}
unsigned NumFiles = clang_SymbolOccurrences_getNumFiles(Occurrences);
if (NumFiles > 1)
PrintFilenames = true;
// Convert the occurrences to strings
for (unsigned FileIndex = 0; FileIndex < NumFiles; ++FileIndex) {
CXSymbolOccurrencesInFile FileResult;
clang_SymbolOccurrences_getOccurrencesForFile(Occurrences, FileIndex,
&FileResult);
StringRef Filename =
PrintFilenames ? clang_getCString(FileResult.Filename) : "";
for (unsigned I = 0; I != FileResult.NumOccurrences; ++I)
ExpectedReplacements.push_back(
occurrenceToString(FileResult.Occurrences[I], IsLocal,
NewSymbolName, NewSymbolName, Filename));
}
clang_SymbolOccurrences_dispose(Occurrences);
return 0;
}
// Compare the produced occurrences to the expected occurrences that were
// gathered at the first location.
bool AreOccurrencesDifferent =
compareOccurrences(ExpectedReplacements, Occurrences, IsLocal,
NewSymbolName, PrintFilenames);
clang_SymbolOccurrences_dispose(Occurrences);
if (!AreOccurrencesDifferent)
return 0;
errs() << "error: occurrences for a rename at " << Location.FileName << ":"
<< Location.Line << ":" << Location.Column
<< " differ to occurrences from the rename at the first location!\n";
return 1;
};
std::vector<ParsedSourceLocation> ParsedLocations;
for (const auto &I : enumerate(opts::rename::AtLocation)) {
auto Location = ParsedSourceLocation::FromString(I.value());
if (Location.FileName.empty()) {
errs()
<< "error: The -at option must use the <file:line:column> format\n";
return 1;
}
ParsedLocations.push_back(Location);
}
if (opts::RenameInitiateUSRSubcommand) {
if (RenameAt(ParsedSourceLocation(), opts::rename::USR))
return 1;
} else {
assert(!ParsedLocations.empty() && "No -at locations");
for (const auto &Location : ParsedLocations) {
if (RenameAt(Location, ""))
return 1;
}
}
// Print the produced renamed replacements
if (opts::Apply)
return 0;
for (const auto &Replacement : ExpectedReplacements)
outs() << Replacement << "\n";
if (ExpectedReplacements.empty())
outs() << "no replacements found\n";
return 0;
}
int renameIndexedFile(CXIndex CIdx, ArrayRef<const char *> Args) {
assert(opts::RenameIndexedFileSubcommand);
// Compute the number of symbols.
unsigned NumSymbols = opts::rename::IndexedNames.size();
// Get the occurrences of a symbol.
CXCursorKind DefaultCursorKind = renameIndexedOccurrenceKindStringToKind(
opts::rename::IndexedSymbolKind, CXCursor_NotImplemented);
std::vector<std::vector<CXIndexedSymbolLocation>> IndexedOccurrences(
NumSymbols, std::vector<CXIndexedSymbolLocation>());
for (const auto &IndexedOccurrence : opts::rename::IndexedLocations) {
auto Occurrence =
parseIndexedOccurrence(IndexedOccurrence, DefaultCursorKind);
unsigned SymbolIndex = Occurrence.second;
assert(SymbolIndex < IndexedOccurrences.size() && "Invalid symbol index");
IndexedOccurrences[SymbolIndex].push_back(CXIndexedSymbolLocation{
Occurrence.first.Location, Occurrence.first.CursorKind});
}
// Create the indexed symbols.
std::vector<CXIndexedSymbol> IndexedSymbols;
for (const auto &I : llvm::enumerate(IndexedOccurrences)) {
const auto &Occurrences = I.value();
const char *Name =
opts::rename::IndexedNames[opts::rename::IndexedNames.size() < 2
? 0
: I.index()]
.c_str();
IndexedSymbols.push_back({Occurrences.data(), (unsigned)Occurrences.size(),
DefaultCursorKind, Name});
}
CXRefactoringOptionSet Options = nullptr;
if (opts::rename::AvoidTextual) {
Options = clang_RefactoringOptionSet_create();
clang_RefactoringOptionSet_add(Options,
CXRefactorOption_AvoidTextualMatches);
}
CXSymbolOccurrencesResult Occurrences;
CXErrorCode Err = clang_Refactoring_findSymbolOccurrencesInIndexedFile(
IndexedSymbols.data(), IndexedSymbols.size(), CIdx,
opts::rename::IndexedFileName.c_str(), Args.data(), Args.size(), 0, 0,
Options, &Occurrences);
if (Err != CXError_Success) {
if (reportNewNameError(Err))
errs() << "error: failed to perform indexed file rename\n";
return 1;
}
if (Options)
clang_RefactoringOptionSet_dispose(Options);
// Should we print out the filenames. False by default, but true when multiple
// files are modified.
bool PrintFilenames = false;
unsigned NumFiles = clang_SymbolOccurrences_getNumFiles(Occurrences);
if (NumFiles > 1)
PrintFilenames = true;
LangOptions LangOpts;
LangOpts.ObjC1 = true;
tooling::SymbolName ExpectedReplacementStrings(
opts::rename::IndexedNewNames[0], LangOpts);
// Print the occurrences.
bool HasReplacements = false;
for (unsigned FileIndex = 0; FileIndex < NumFiles; ++FileIndex) {
CXSymbolOccurrencesInFile FileResult;
clang_SymbolOccurrences_getOccurrencesForFile(Occurrences, FileIndex,
&FileResult);
StringRef Filename =
PrintFilenames ? clang_getCString(FileResult.Filename) : "";
HasReplacements = FileResult.NumOccurrences;
for (unsigned I = 0; I != FileResult.NumOccurrences; ++I) {
unsigned SymbolIndex = FileResult.Occurrences[I].SymbolIndex;
const char *NewName =
opts::rename::IndexedNewNames[opts::rename::IndexedNewNames.size() < 2
? 0
: SymbolIndex]
.c_str();
LangOptions LangOpts;
LangOpts.ObjC1 = true;
tooling::SymbolName NewSymbolName(NewName, LangOpts);
outs() << occurrenceToString(FileResult.Occurrences[I], /*IsLocal*/ false,
NewSymbolName, ExpectedReplacementStrings,
Filename)
<< "\n";
}
}
if (!HasReplacements)
outs() << "no replacements found\n";
clang_SymbolOccurrences_dispose(Occurrences);
return 0;
}
/// Returns the last column number of a line in a file.
static unsigned lastColumnForFile(StringRef Filename, unsigned LineNo) {
auto Buf = llvm::MemoryBuffer::getFile(Filename);
if (!Buf)
return 0;
unsigned LineCount = 1;
for (llvm::line_iterator Lines(**Buf, /*SkipBlanks=*/false);
!Lines.is_at_end(); ++Lines, ++LineCount) {
if (LineNo == LineCount)
return Lines->size() + 1;
}
return 0;
}
struct ParsedSourceLineRange : ParsedSourceLocation {
unsigned MaxColumn;
ParsedSourceLineRange() {}
ParsedSourceLineRange(const ParsedSourceLocation &Loc)
: ParsedSourceLocation(Loc), MaxColumn(Loc.Column) {}
static Optional<ParsedSourceLineRange> FromString(StringRef Str) {
std::pair<StringRef, StringRef> RangeSplit = Str.rsplit('-');
auto PSL = ParsedSourceLocation::FromString(RangeSplit.first);
ParsedSourceLineRange Result;
Result.FileName = std::move(PSL.FileName);
Result.Line = PSL.Line;
Result.Column = PSL.Column;
if (Result.FileName.empty())
return None;
if (RangeSplit.second == "end")
Result.MaxColumn = lastColumnForFile(Result.FileName, Result.Line);
else if (RangeSplit.second.getAsInteger(10, Result.MaxColumn))
return None;
if (Result.MaxColumn < Result.Column)
return None;
return Result;
}
};
struct ParsedSourceRange {
ParsedSourceLocation Begin, End;
ParsedSourceRange(const ParsedSourceLocation &Begin,
const ParsedSourceLocation &End)
: Begin(Begin), End(End) {}
static Optional<ParsedSourceRange> FromString(StringRef Str) {
std::pair<StringRef, StringRef> RangeSplit = Str.rsplit('-');
auto Begin = ParsedSourceLocation::FromString(RangeSplit.first);
if (Begin.FileName.empty())
return None;
std::string EndString = Begin.FileName + ":" + RangeSplit.second.str();
auto End = ParsedSourceLocation::FromString(EndString);
if (End.FileName.empty())
return None;
return ParsedSourceRange(Begin, End);
}
};
int listRefactoringActions(CXTranslationUnit TU) {
auto Location =
ParsedSourceLocation::FromString(opts::listActions::AtLocation);
if (Location.FileName.empty()) {
errs() << "error: The -at option must use the <file:line:column> format\n";
return 1;
}
CXSourceRange Range;
if (!opts::listActions::SelectedRange.empty()) {
auto SelectionRange =
ParsedSourceRange::FromString(opts::listActions::SelectedRange);
if (!SelectionRange) {
errs() << "error: The -selected option must use the "
"<file:line:column-line:column> format\n";
return 1;
}
auto Begin = SelectionRange.getValue().Begin;
auto End = SelectionRange.getValue().End;
CXFile File = clang_getFile(TU, Begin.FileName.c_str());
Range =
clang_getRange(clang_getLocation(TU, File, Begin.Line, Begin.Column),
clang_getLocation(TU, File, End.Line, End.Column));
} else
Range = clang_getNullRange();
CXSourceLocation Loc =
clang_getLocation(TU, clang_getFile(TU, Location.FileName.c_str()),
Location.Line, Location.Column);
CXRefactoringActionSet ActionSet;
CXRefactoringActionSetWithDiagnostics FailedActionSet;
CXErrorCode Err =
clang_Refactoring_findActionsWithInitiationFailureDiagnosicsAt(
TU, Loc, Range, /*Options=*/nullptr, &ActionSet, &FailedActionSet);
if (FailedActionSet.NumActions) {
errs() << "Failed to initiate " << FailedActionSet.NumActions
<< " actions because:\n";
for (unsigned I = 0; I < FailedActionSet.NumActions; ++I) {
errs() << clang_getCString(clang_RefactoringActionType_getName(
FailedActionSet.Actions[I].Action))
<< ":";
CXDiagnosticSet Diags = FailedActionSet.Actions[I].Diagnostics;
unsigned NumDiags = clang_getNumDiagnosticsInSet(Diags);
for (unsigned DiagID = 0; DiagID < NumDiags; ++DiagID) {
CXDiagnostic Diag = clang_getDiagnosticInSet(Diags, DiagID);
CXString Spelling = clang_getDiagnosticSpelling(Diag);
errs() << ' ' << clang_getCString(Spelling);
clang_disposeString(Spelling);
}
errs() << "\n";
}
}
if (Err == CXError_RefactoringActionUnavailable)
errs() << "No refactoring actions are available at the given location\n";
if (Err != CXError_Success)
return 1;
// Print the list of refactoring actions.
outs() << "Found " << ActionSet.NumActions << " actions:\n";
for (unsigned I = 0; I < ActionSet.NumActions; ++I) {
outs() << clang_getCString(
clang_RefactoringActionType_getName(ActionSet.Actions[I]));
if (opts::listActions::DumpRawActionType)
outs() << "(" << ActionSet.Actions[I] << ")";
outs() << "\n";
}
clang_RefactoringActionSet_dispose(&ActionSet);
clang_RefactoringActionSetWithDiagnostics_dispose(&FailedActionSet);
return 0;
}
static std::string locationToString(CXSourceLocation Loc) {
unsigned Line, Column;
clang_getFileLocation(Loc, nullptr, &Line, &Column, nullptr);
std::string S;
llvm::raw_string_ostream OS(S);
OS << Line << ':' << Column;
return OS.str();
}
static std::string rangeToString(CXSourceRange Range) {
return locationToString(clang_getRangeStart(Range)) + " -> " +
locationToString(clang_getRangeEnd(Range));
}
static std::string
refactoringCandidatesToString(CXRefactoringCandidateSet Candidates) {
std::string Results = "with multiple candidates:";
for (unsigned I = 0; I < Candidates.NumCandidates; ++I) {
Results += "\n";
Results += clang_getCString(Candidates.Candidates[I].Description);
}
return Results;
}
static void printEscaped(StringRef Str, raw_ostream &OS) {
size_t Pos = Str.find('\n');
OS << Str.substr(0, Pos);
if (Pos == StringRef::npos)
return;
OS << "\\n";
printEscaped(Str.substr(Pos + 1), OS);
}
bool printRefactoringReplacements(
CXRefactoringResult Result, CXRefactoringContinuation Continuation,
CXRefactoringContinuation CurrentContinuation) {
CXRefactoringReplacements Replacements =
clang_RefactoringResult_getSourceReplacements(Result);
if (Replacements.NumFileReplacementSets == 0) {
if (CurrentContinuation)
return false;
errs() << "error: no replacements produced!\n";
return true;
}
// Print out the produced results.
for (unsigned FileIndex = 0; FileIndex < Replacements.NumFileReplacementSets;
++FileIndex) {
const CXRefactoringFileReplacementSet &FileSet =
Replacements.FileReplacementSets[FileIndex];
if (opts::Apply) {
apply(llvm::makeArrayRef(FileSet.Replacements, FileSet.NumReplacements),
clang_getCString(FileSet.Filename));
continue;
}
for (unsigned I = 0; I < FileSet.NumReplacements; ++I) {
const CXRefactoringReplacement &Replacement = FileSet.Replacements[I];
if (Continuation) {
// Always print the filenames in with continuations.
outs() << '"' << clang_getCString(FileSet.Filename) << "\" ";
}
outs() << '"';
printEscaped(clang_getCString(Replacement.ReplacementString), outs());
outs() << "\" ";
CXFileRange Range = Replacement.Range;
outs() << Range.Begin.Line << ":" << Range.Begin.Column << " -> "
<< Range.End.Line << ":" << Range.End.Column;
if (opts::initiateAndPerform::EmitAssociatedInfo) {
CXRefactoringReplacementAssociatedSymbolOccurrences Info =
clang_RefactoringReplacement_getAssociatedSymbolOccurrences(
Replacement);
for (const CXSymbolOccurrence &SymbolOccurrence :
llvm::makeArrayRef(Info.AssociatedSymbolOccurrences,
Info.NumAssociatedSymbolOccurrences)) {
outs() << " [Symbol " << renameOccurrenceKindString(
SymbolOccurrence.Kind, /*IsLocal*/ false,
SymbolOccurrence.IsMacroExpansion)
<< ' ' << SymbolOccurrence.SymbolIndex;
for (const auto &Piece :
llvm::makeArrayRef(SymbolOccurrence.NamePieces,
SymbolOccurrence.NumNamePieces)) {
outs() << ' ' << Piece.Begin.Line << ":" << Piece.Begin.Column
<< " -> " << Piece.End.Line << ":" << Piece.End.Column;
}
outs() << ']';
}
}
outs() << "\n";
}
}
return false;
}
/// Returns the last column number of a line in a file.
static std::string queryResultsForFile(StringRef Filename, StringRef Name,
StringRef FileSubstitution) {
auto Buf = llvm::MemoryBuffer::getFile(Filename);
if (!Buf)
return "<invalid>";
StringRef Buffer = (*Buf)->getBuffer();
std::string Label = Name.str() + ":";
size_t I = Buffer.find(Label);
if (I == StringRef::npos)
return "<invalid>";
I = I + Label.size();
auto Result = Buffer.substr(I, Buffer.find('\n', I) - I);
std::string Sub1 = llvm::Regex("%s").sub(FileSubstitution, Result);
return llvm::Regex("%S").sub(llvm::sys::path::parent_path(FileSubstitution),
Sub1);
}
static Optional<std::pair<unsigned, unsigned>>
findSelectionLocInSource(StringRef Buffer, StringRef Label) {
size_t I = Buffer.find(Label);
if (I == StringRef::npos)
return None;
I = I + Label.size();
auto LocParts =
Buffer.substr(I, Buffer.find_first_of("\n/", I) - I).trim().split(":");
unsigned CurrentLine = Buffer.take_front(I).count('\n') + 1;
if (LocParts.second.empty())
return None;
StringRef LineString = LocParts.first;
unsigned Line, Column;
enum ExprKind { Literal, Add, Sub };
ExprKind Expr = LineString.startswith("+")
? Add
: LineString.startswith("-") ? Sub : Literal;
if (LineString.drop_front(Expr != Literal ? 1 : 0).getAsInteger(10, Line))
return None;
if (Expr == Add)
Line += CurrentLine;
else if (Expr == Sub)
Line = CurrentLine - Line;
if (LocParts.second.getAsInteger(10, Column))
return None;
return std::make_pair(Line, Column);
}
static Optional<ParsedSourceLocation> selectionLocForFile(StringRef Filename,
StringRef Name) {
auto Buf = llvm::MemoryBuffer::getFile(Filename);
if (!Buf)
return None;
StringRef Buffer = (*Buf)->getBuffer();
std::string Label = Name.str() + ":";
auto Start = findSelectionLocInSource(Buffer, Label);
if (!Start)
return None;
// Create the resulting source location.
// FIXME: Parse can be avoided.
std::string Str;
llvm::raw_string_ostream OS(Str);
OS << Filename << ":" << Start->first << ":" << Start->second;
return ParsedSourceLocation::FromString(OS.str());
}
static Optional<ParsedSourceRange> selectionRangeForFile(StringRef Filename,
StringRef Name) {
auto Buf = llvm::MemoryBuffer::getFile(Filename);
if (!Buf)
return None;
StringRef Buffer = (*Buf)->getBuffer();
std::string BeginLabel = Name.str() + "-begin:";
std::string EndLabel = Name.str() + "-end:";
auto Start = findSelectionLocInSource(Buffer, BeginLabel);
auto End = findSelectionLocInSource(Buffer, EndLabel);
if (!Start || !End)
return None;
// Create the resulting source range.
// FIXME: Parse can be avoided.
std::string Str;
llvm::raw_string_ostream OS(Str);
OS << Filename << ":" << Start->first << ":" << Start->second << "-"
<< End->first << ":" << End->second;
return ParsedSourceRange::FromString(OS.str());
}
bool performOperation(CXRefactoringAction Action, ArrayRef<const char *> Args,
CXIndex CIdx) {
if (opts::initiateAndPerform::CandidateIndex.getNumOccurrences()) {
if (clang_RefactoringAction_selectRefactoringCandidate(
Action, opts::initiateAndPerform::CandidateIndex)) {
errs() << "error: failed to select the refactoring candidate!\n";
return true;
}
}
CXRefactoringOptionSet Options = nullptr;
CXString FailureReason;
CXRefactoringResult Result = clang_Refactoring_performOperation(
Action, Args.data(), Args.size(), nullptr, 0, Options, &FailureReason);
if (!Result) {
errs() << "error: failed to perform the refactoring operation";
if (const char *Reason = clang_getCString(FailureReason))
errs() << " (" << Reason << ')';
errs() << "!\n";
clang_disposeString(FailureReason);
return true;
}
CXRefactoringContinuation Continuation =
clang_RefactoringResult_getContinuation(Result);
bool AreReplacementsInvalid =
printRefactoringReplacements(Result, Continuation, Continuation);
clang_RefactoringResult_dispose(Result);
if (AreReplacementsInvalid) {
clang_RefactoringContinuation_dispose(Continuation);
return true;
}
if (!Continuation)
return false;
assert(clang_RefactoringContinuation_getNumIndexerQueries(Continuation) !=
0 &&
"Missing indexer queries?");
std::string QueryResults = queryResultsForFile(
opts::FileName, opts::initiateAndPerform::QueryResults,
/*FileSubstitution=*/opts::initiateAndPerform::ContinuationFile);
clang_RefactoringContinuation_loadSerializedIndexerQueryResults(
Continuation, /*Source=*/QueryResults.c_str());
CXDiagnosticSet Diags =
clang_RefactoringContinuation_verifyBeforeFinalizing(Continuation);
if (Diags) {
llvm::errs() << "error: continuation failed: ";
for (unsigned I = 0, E = clang_getNumDiagnosticsInSet(Diags); I != E; ++I) {
CXDiagnostic Diag = clang_getDiagnosticInSet(Diags, I);
CXString Spelling = clang_getDiagnosticSpelling(Diag);
errs() << clang_getCString(Spelling) << "\n";
clang_disposeString(Spelling);
clang_disposeDiagnostic(Diag);
}
clang_RefactoringContinuation_dispose(Continuation);
clang_disposeDiagnosticSet(Diags);
return true;
}
clang_RefactoringContinuation_finalizeEvaluationInInitationTU(Continuation);
// Load the continuation TU.
CXTranslationUnit ContinuationTU;
CXErrorCode Err = clang_parseTranslationUnit2(
CIdx, opts::initiateAndPerform::ContinuationFile.c_str(), Args.data(),
Args.size(), 0, 0, CXTranslationUnit_KeepGoing, &ContinuationTU);
if (Err != CXError_Success) {
errs() << "error: failed to load '"
<< opts::initiateAndPerform::ContinuationFile.c_str() << "'\n";
clang_RefactoringContinuation_dispose(Continuation);
return true;
}
Result = clang_RefactoringContinuation_continueOperationInTU(
Continuation, ContinuationTU, &FailureReason);
if (!Result) {
errs() << "error: failed to perform the refactoring continuation";
if (const char *Reason = clang_getCString(FailureReason))
errs() << " (" << Reason << ')';
errs() << "!\n";
clang_disposeString(FailureReason);
clang_disposeTranslationUnit(ContinuationTU);
clang_RefactoringContinuation_dispose(Continuation);
return true;
}
// FIXME: Continuations can be chained in the future.
AreReplacementsInvalid =
printRefactoringReplacements(Result, Continuation, nullptr);
clang_RefactoringResult_dispose(Result);
clang_disposeTranslationUnit(ContinuationTU);
clang_RefactoringContinuation_dispose(Continuation);
return AreReplacementsInvalid;
}
int initiateAndPerformAction(CXTranslationUnit TU, ArrayRef<const char *> Args,
CXIndex CIdx) {
std::vector<ParsedSourceLineRange> Ranges;
std::vector<ParsedSourceRange> SelectionRanges;
for (const auto &Range : opts::initiateAndPerform::InLocationRanges) {
auto ParsedLineRange = ParsedSourceLineRange::FromString(Range);
if (!ParsedLineRange) {
errs()
<< "error: The -in option must use the <file:line:column[-column]> "
"format\n";
return 1;
}
Ranges.push_back(ParsedLineRange.getValue());
}
for (const auto &Range : opts::initiateAndPerform::AtLocations) {
if (!StringRef(Range).contains(':')) {
auto ParsedLocation = selectionLocForFile(opts::FileName, Range);
if (!ParsedLocation) {
errs() << "error: The -at option must use the <file:line:column> "
"format\n";
return 1;
}
Ranges.push_back(*ParsedLocation);
continue;
}
// TODO: Remove old location in arguments in favour of new testing
// locations.
auto ParsedLocation = ParsedSourceLocation::FromString(Range);
if (ParsedLocation.FileName.empty()) {
errs() << "error: The -at option must use the <file:line:column> "
"format\n";
return 1;
}
Ranges.push_back(ParsedLocation);
}
for (const auto &Range : opts::initiateAndPerform::SelectedRanges) {
auto ParsedRange = StringRef(Range).contains(':')
? ParsedSourceRange::FromString(Range)
: selectionRangeForFile(opts::FileName, Range);
if (!ParsedRange) {
errs() << "error: The -selected option must use the "
"<file:line:column-line:column> format or refer to the name of "
"the selection specifier in the source\n";
return 1;
}
SelectionRanges.push_back(ParsedRange.getValue());
}
if (Ranges.empty() && SelectionRanges.empty()) {
errs() << "error: -in or -at options must be specified at least once!";
return 1;
}
if (!Ranges.empty() && !SelectionRanges.empty()) {
errs() << "error: -in or -at options can't be used with -selected!";
return 1;
}
auto ActionTypeOrNone = StringSwitch<Optional<CXRefactoringActionType>>(
opts::initiateAndPerform::ActionName)
#define REFACTORING_OPERATION_ACTION(Name, Spelling, Command) \
.Case(Command, CXRefactor_##Name)
#define REFACTORING_OPERATION_SUB_ACTION(Name, Parent, Spelling, Command) \
.Case(Command, CXRefactor_##Parent##_##Name)
#include "clang/Tooling/Refactor/RefactoringActions.def"
.Default(None);
if (!ActionTypeOrNone) {
errs() << "error: invalid action '" << opts::initiateAndPerform::ActionName
<< "'\n";
return 1;
}
CXRefactoringActionType ActionType = *ActionTypeOrNone;
Optional<bool> Initiated;
Optional<std::string> InitiationFailureReason;
Optional<std::string> LocationCandidateInformation;
auto InitiateAndPerform =
[&](const ParsedSourceLocation &Location, unsigned Column,
Optional<ParsedSourceRange> SelectionRange = None) -> bool {
CXSourceLocation Loc =
clang_getLocation(TU, clang_getFile(TU, Location.FileName.c_str()),
Location.Line, Column);
CXSourceRange Range;
if (SelectionRange) {
auto Begin = SelectionRange.getValue().Begin;
auto End = SelectionRange.getValue().End;
CXFile File = clang_getFile(TU, Begin.FileName.c_str());
Range =
clang_getRange(clang_getLocation(TU, File, Begin.Line, Begin.Column),
clang_getLocation(TU, File, End.Line, End.Column));
} else
Range = clang_getNullRange();
CXRefactoringAction Action;
CXString FailureReason;
CXErrorCode Err = clang_Refactoring_initiateActionAt(
TU, Loc, Range, ActionType, /*Options=*/nullptr, &Action,
&FailureReason);
std::string ReasonString;
if (const char *Reason = clang_getCString(FailureReason))
ReasonString = Reason;
clang_disposeString(FailureReason);
if (InitiationFailureReason.hasValue() &&
InitiationFailureReason.getValue() != ReasonString) {
errs() << "error: inconsistent results in a single action range!\n";
return true;
}
InitiationFailureReason = std::move(ReasonString);
if (Err == CXError_RefactoringActionUnavailable) {
if (Initiated.hasValue() && Initiated.getValue()) {
errs() << "error: inconsistent results in a single action range!\n";
return true;
}
Initiated = false;
} else if (Err != CXError_Success)
return true;
else if (Initiated.hasValue() && !Initiated.getValue()) {
errs() << "error: inconsistent results in a single action range!\n";
return true;
} else
Initiated = true;
CXRefactoringCandidateSet Candidates;
if (clang_RefactoringAction_getRefactoringCandidates(Action, &Candidates) ==
CXError_Success &&
Candidates.NumCandidates > 1) {
std::string CandidateString = refactoringCandidatesToString(Candidates);
if (LocationCandidateInformation) {
if (*LocationCandidateInformation != CandidateString) {
errs() << "error: inconsistent results in a single action range!\n";
return true;
}
} else
LocationCandidateInformation = CandidateString;
} else if (opts::InitiateActionSubcommand &&
!opts::initiateAndPerform::LocationAgnostic) {
CXSourceRange Range =
clang_RefactoringAction_getSourceRangeOfInterest(Action);
std::string LocationString =
std::string("at ") +
(!clang_Range_isNull(Range)
? SelectionRange ? rangeToString(Range)
: locationToString(clang_getRangeStart(Range))
: "<unknown>");
if (!LocationCandidateInformation.hasValue())
LocationCandidateInformation = LocationString;
else if (LocationCandidateInformation.getValue() != LocationString) {
errs() << "error: inconsistent results in a single action range!\n";
return true;
}
}
if (!*Initiated)
return false;
bool Failed = opts::PerformActionSubcommand
? performOperation(Action, Args, CIdx)
: false;
clang_RefactoringAction_dispose(Action);
return Failed;
};
// Iterate over all of the possible locations and perform the initiation
// at each range.
for (const ParsedSourceLineRange &LineRange : Ranges) {
for (unsigned Column = LineRange.Column; Column <= LineRange.MaxColumn;
++Column) {
if (InitiateAndPerform(LineRange, Column))
return 1;
}
}
for (const ParsedSourceRange &SelectionRange : SelectionRanges) {
if (InitiateAndPerform(SelectionRange.Begin, SelectionRange.Begin.Column,
SelectionRange))
return 1;
}
if (!Initiated.getValue()) {
errs() << "Failed to initiate the refactoring action";
if (InitiationFailureReason.hasValue() &&
!InitiationFailureReason.getValue().empty())
errs() << " (" << InitiationFailureReason.getValue() << ')';
errs() << "!\n";
return 1;
}
if (opts::InitiateActionSubcommand) {
outs() << "Initiated the '" << opts::initiateAndPerform::ActionName
<< "' action";
if (!opts::initiateAndPerform::LocationAgnostic)
outs() << ' ' << LocationCandidateInformation.getValue();
outs() << "\n";
}
return 0;
}
int main(int argc, const char **argv) {
cl::HideUnrelatedOptions(opts::ClangRefactorTestOptions);
cl::ParseCommandLineOptions(argc, argv, "Clang refactoring test tool\n");
cl::PrintOptionValues();
CXIndex CIdx = clang_createIndex(0, 0);
std::vector<const char *> Args;
for (const auto &Arg : opts::CompilerArguments) {
Args.push_back(Arg.c_str());
}
CXTranslationUnit TU;
CXErrorCode Err = clang_parseTranslationUnit2(
CIdx,
opts::IgnoreFilenameForInitiationTU ? nullptr : opts::FileName.c_str(),
Args.data(), Args.size(), 0, 0, CXTranslationUnit_KeepGoing, &TU);
if (Err != CXError_Success) {
errs() << "error: failed to load '" << opts::FileName << "'\n";
return 1;
}
if (opts::RenameInitiateSubcommand || opts::RenameInitiateUSRSubcommand)
return rename(TU, CIdx, Args);
else if (opts::RenameIndexedFileSubcommand)
return renameIndexedFile(CIdx, Args);
else if (opts::ListRefactoringActionsSubcommand)
return listRefactoringActions(TU);
else if (opts::InitiateActionSubcommand || opts::PerformActionSubcommand)
return initiateAndPerformAction(TU, Args, CIdx);
clang_disposeTranslationUnit(TU);
clang_disposeIndex(CIdx);
return 0;
}