blob: 39deddc4518317a74d69feb431d25d13e67f6f32 [file] [log] [blame]
//===--- swift-refactor.cpp - Test driver for local refactoring --*- C++ -*-==//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "llvm/Support/CommandLine.h"
#include "swift/Basic/LLVMInitialize.h"
#include "swift/Frontend/Frontend.h"
#include "swift/Frontend/PrintingDiagnosticConsumer.h"
#include "swift/IDE/Refactoring.h"
#include "swift/IDE/Utils.h"
#include <regex>
using namespace swift;
using namespace swift::ide;
namespace options {
static llvm::cl::opt<RefactoringKind>
Action(llvm::cl::desc("kind:"), llvm::cl::init(RefactoringKind::None),
llvm::cl::values(
clEnumValN(RefactoringKind::LocalRename,
"rename", "Perform rename refactoring"),
clEnumValN(RefactoringKind::ExtractExpr,
"extract-expr", "Perform extract expression refactoring"),
clEnumValN(RefactoringKind::ExtractRepeatedExpr,
"extract-repeat", "Perform extract repeated expression refactoring"),
clEnumValN(RefactoringKind::FillProtocolStub,
"fill-stub", "Perform fill protocol stub refactoring"),
clEnumValN(RefactoringKind::ExpandDefault,
"expand-default", "Perform expand default statement refactoring"),
clEnumValN(RefactoringKind::LocalizeString,
"localize-string", "Perform string localization refactoring"),
clEnumValN(RefactoringKind::CollapseNestedIfExpr,
"collapse-nested-if", "Perform collapse nested if statements"),
clEnumValN(RefactoringKind::ConvertToDoCatch,
"convert-to-do-catch", "Perform force try to do try catch refactoring"),
clEnumValN(RefactoringKind::SimplifyNumberLiteral,
"simplify-long-number", "Perform simplify long number literal refactoring"),
clEnumValN(RefactoringKind::ConvertStringsConcatenationToInterpolation,
"strings-concatenation-to-interpolation", "Perform strings concatenation to interpolation refactoring"),
clEnumValN(RefactoringKind::ExtractFunction,
"extract-function", "Perform extract function refactoring"),
clEnumValN(RefactoringKind::GlobalRename,
"syntactic-rename", "Perform syntactic rename"),
clEnumValN(RefactoringKind::FindGlobalRenameRanges,
"find-rename-ranges", "Find detailed ranges for syntactic rename"),
clEnumValN(RefactoringKind::FindLocalRenameRanges,
"find-local-rename-ranges", "Find detailed ranges for local rename")));
static llvm::cl::opt<std::string>
ModuleName("module-name", llvm::cl::desc("The module name of the given test."),
llvm::cl::init("swift_refactor"));
static llvm::cl::opt<std::string>
SourceFilename("source-filename", llvm::cl::desc("Name of the source file"));
static llvm::cl::list<std::string>
InputFilenames(llvm::cl::Positional, llvm::cl::desc("[input files...]"),
llvm::cl::ZeroOrMore);
static llvm::cl::opt<std::string>
LineColumnPair("pos", llvm::cl::desc("Line:Column pair or /*label*/"));
static llvm::cl::opt<std::string>
EndLineColumnPair("end-pos", llvm::cl::desc("Line:Column pair or /*label*/"));
static llvm::cl::opt<std::string>
NewName("new-name", llvm::cl::desc("A given name to rename to"),
llvm::cl::init("new_name"));
static llvm::cl::opt<std::string>
OldName("old-name", llvm::cl::desc("The expected existing name"),
llvm::cl::init("old_name"));
static llvm::cl::opt<bool>
IsFunctionLike("is-function-like", llvm::cl::desc("The symbol being renamed is function-like"),
llvm::cl::ValueDisallowed);
static llvm::cl::opt<bool>
IsNonProtocolType("is-non-protocol-type",
llvm::cl::desc("The symbol being renamed is a type and not a protocol"));
static llvm::cl::opt<bool>
DumpInJason("dump-json",
llvm::cl::desc("Whether to dump refactoring edits in Json"),
llvm::cl::init(false));
static llvm::cl::opt<bool>
AvailableActions("actions",
llvm::cl::desc("Dump the available refactoring kind for a given range"),
llvm::cl::init(false));
}
bool doesFileExist(StringRef Path) {
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> FileBufOrErr =
llvm::MemoryBuffer::getFile(Path);
if(FileBufOrErr)
return true;
return false;
}
struct RefactorLoc {
unsigned Line;
unsigned Column;
NameUsage Usage;
};
NameUsage convertToNameUsage(StringRef RoleString) {
if (RoleString == "unknown")
return NameUsage::Unknown;
if (RoleString == "def")
return NameUsage::Definition;
if (RoleString == "ref")
return NameUsage::Reference;
if (RoleString == "call")
return NameUsage::Call;
llvm_unreachable("unhandled role string");
}
std::vector<RefactorLoc> getLocsByLabelOrPosition(StringRef LabelOrLineCol,
std::string &Buffer) {
std::vector<RefactorLoc> LocResults;
if (LabelOrLineCol.empty())
return LocResults;
if (LabelOrLineCol.contains(':')) {
auto LineCol = parseLineCol(LabelOrLineCol);
if (LineCol.hasValue()) {
LocResults.push_back({LineCol.getValue().first,LineCol.getValue().second,
NameUsage::Unknown});
} else {
llvm::errs() << "cannot parse position pair.";
}
return LocResults;
}
std::smatch Matches;
const std::regex LabelRegex("/\\*([^ *]+)\\*/|\\n");
std::string::const_iterator SearchStart(Buffer.cbegin());
unsigned Line = 1;
unsigned Column = 1;
while (std::regex_search(SearchStart, Buffer.cend(), Matches, LabelRegex)) {
auto EndOffset = Matches.position() + Matches.length();
if (Matches[1].matched) {
Column += EndOffset;
std::string MatchedStorage(Matches[1].str());
StringRef Matched(MatchedStorage);
size_t ColonPos = Matched.find(':');
if (Matched.slice(0, ColonPos) == LabelOrLineCol) {
NameUsage Usage = NameUsage::Reference;
if (ColonPos != StringRef::npos)
Usage = convertToNameUsage(Matched.substr(ColonPos + 1));
LocResults.push_back({Line, Column, Usage});
}
} else {
++Line;
Column = 1;
}
SearchStart += EndOffset;
}
return LocResults;
}
std::vector<RenameLoc> getRenameLocs(unsigned BufferID, SourceManager &SM,
ArrayRef<RefactorLoc> Locs,
StringRef OldName, StringRef NewName,
bool IsFunctionLike,
bool IsNonProtocolType) {
std::vector<RenameLoc> Renames;
std::transform(Locs.begin(), Locs.end(), std::back_inserter(Renames), [&](const RefactorLoc &Loc) -> RenameLoc {
return {Loc.Line, Loc.Column, Loc.Usage, OldName, NewName, IsFunctionLike,
IsNonProtocolType};
});
return Renames;
}
RangeConfig getRange(unsigned BufferID, SourceManager &SM,
RefactorLoc Start,
RefactorLoc End) {
RangeConfig Range;
SourceLoc EndLoc = SM.getLocForLineCol(BufferID, End.Line,
End.Column);
Range.BufferId = BufferID;
Range.Line = Start.Line;
Range.Column = Start.Column;
Range.Length = SM.getByteDistance(Range.getStart(SM), EndLoc);
assert(Range.getEnd(SM) == EndLoc);
return Range;
}
// This function isn't referenced outside its translation unit, but it
// can't use the "static" keyword because its address is used for
// getMainExecutable (since some platforms don't support taking the
// address of main, and some platforms can't implement getMainExecutable
// without being given the address of a function in the main executable).
void anchorForGetMainExecutable() {}
int main(int argc, char *argv[]) {
INITIALIZE_LLVM(argc, argv);
llvm::cl::ParseCommandLineOptions(argc, argv, "Swift refactor\n");
if (options::SourceFilename.empty()) {
llvm::errs() << "cannot find source filename\n";
return 1;
}
if (!doesFileExist(options::SourceFilename)) {
llvm::errs() << "cannot open source file.\n";
return 1;
}
CompilerInvocation Invocation;
Invocation.setMainExecutablePath(
llvm::sys::fs::getMainExecutable(argv[0],
reinterpret_cast<void *>(&anchorForGetMainExecutable)));
Invocation.addInputFilename(options::SourceFilename);
Invocation.getLangOptions().AttachCommentsToDecls = true;
Invocation.getLangOptions().KeepTokensInSourceFile = true;
for (auto FileName : options::InputFilenames)
Invocation.addInputFilename(FileName);
Invocation.setModuleName(options::ModuleName);
CompilerInstance CI;
// Display diagnostics to stderr.
PrintingDiagnosticConsumer PrintDiags;
CI.addDiagnosticConsumer(&PrintDiags);
if (CI.setup(Invocation))
return 1;
switch (options::Action) {
case RefactoringKind::GlobalRename:
case RefactoringKind::FindGlobalRenameRanges:
CI.performParseOnly(/*EvaluateConditionals*/true);
break;
default:
CI.performSema();
}
SourceFile *SF = nullptr;
for (auto Unit : CI.getMainModule()->getFiles()) {
if (auto Current = dyn_cast<SourceFile>(Unit)) {
if (Current->getFilename() == options::SourceFilename)
SF = Current;
}
if (SF)
break;
}
assert(SF && "no source file?");
SourceManager &SM = SF->getASTContext().SourceMgr;
unsigned BufferID = SF->getBufferID().getValue();
std::string Buffer = SM.getRangeForBuffer(BufferID).str();
auto Start = getLocsByLabelOrPosition(options::LineColumnPair, Buffer);
if (Start.empty()) {
llvm::errs() << "cannot parse position pair.";
return 1;
}
RefactorLoc EndLoc = Start.front();
if (options::EndLineColumnPair.getNumOccurrences() == 1) {
auto End = getLocsByLabelOrPosition(options::EndLineColumnPair, Buffer);
if (End.size() > 1) {
llvm::errs() << "only a single start and end position may be specified.";
return 1;
}
EndLoc = End.front();
}
RefactorLoc &StartLoc = Start.front();
if (options::Action == RefactoringKind::FindLocalRenameRanges) {
RangeConfig Range = getRange(BufferID, SM, StartLoc, EndLoc);
FindRenameRangesAnnotatingConsumer Consumer(SM, BufferID, llvm::outs());
return findLocalRenameRanges(SF, Range, Consumer, PrintDiags);
}
if (options::Action == RefactoringKind::GlobalRename ||
options::Action == RefactoringKind::FindGlobalRenameRanges) {
if (!options::OldName.getNumOccurrences()) {
llvm::errs() << "old-name must be specified.";
return 1;
}
if (options::Action == RefactoringKind::GlobalRename && !options::NewName.getNumOccurrences()) {
llvm::errs() << "new-name must be specified.";
return 1;
}
std::string NewName = options::NewName;
if (!options::NewName.getNumOccurrences()) {
// Unlike other operations, we don't want to provide a default new_name,
// since we don't want to validate the new name.
NewName = "";
}
std::vector<RenameLoc>
RenameLocs = getRenameLocs(BufferID, SM, Start, options::OldName, NewName,
options::IsFunctionLike.getNumOccurrences(),
options::IsNonProtocolType.getNumOccurrences());
switch (options::Action) {
case RefactoringKind::GlobalRename: {
SourceEditOutputConsumer EditConsumer(SM, BufferID, llvm::outs());
return syntacticRename(SF, RenameLocs, EditConsumer, PrintDiags);
}
case RefactoringKind::FindGlobalRenameRanges: {
FindRenameRangesAnnotatingConsumer Consumer(SM, BufferID, llvm::outs());
return findSyntacticRenameRanges(SF, RenameLocs, Consumer, PrintDiags);
}
default:
llvm_unreachable("unexpected refactoring kind");
}
}
RangeConfig Range = getRange(BufferID, SM, StartLoc, EndLoc);
if (options::Action == RefactoringKind::None) {
std::vector<RefactoringKind> Scratch;
ArrayRef<RefactoringKind> AllKinds;
bool RangeStartMayNeedRename = false;
AllKinds = collectAvailableRefactorings(SF, Range,RangeStartMayNeedRename,
Scratch, {&PrintDiags});
llvm::outs() << "Action begins\n";
for (auto Kind : AllKinds) {
llvm::outs() << getDescriptiveRefactoringKindName(Kind) << "\n";
}
llvm::outs() << "Action ends\n";
return 0;
}
RefactoringOptions RefactoringConfig(options::Action);
RefactoringConfig.Range = Range;
RefactoringConfig.PreferredName = options::NewName;
std::string Error;
std::unique_ptr<SourceEditConsumer> pConsumer;
if (options::DumpInJason)
pConsumer.reset(new SourceEditJsonConsumer(llvm::outs()));
else
pConsumer.reset(new SourceEditOutputConsumer(SF->getASTContext().SourceMgr,
BufferID,
llvm::outs()));
return refactorSwiftModule(CI.getMainModule(), RefactoringConfig, *pConsumer,
PrintDiags);
}