Index: include/clang/Frontend/CommandLineSourceLoc.h =================================================================== --- include/clang/Frontend/CommandLineSourceLoc.h +++ include/clang/Frontend/CommandLineSourceLoc.h @@ -51,6 +51,40 @@ } }; +/// A source range that has been parsed on the command line. +struct ParsedSourceRange { + std::string FileName; + std::pair Begin; + std::pair End; + + /// Returns a parsed source range from a string or None if the string is + /// invalid. + static Optional fromString(StringRef Str) { + std::pair RangeSplit; + // Avoid splitting '-' when there's no end line & column as '-' might be + // part of the filename. + if (Str.count(':') > 2) + RangeSplit = Str.rsplit('-'); + else + RangeSplit = {Str, ""}; + auto Begin = ParsedSourceLocation::FromString(RangeSplit.first); + if (Begin.FileName.empty()) + return None; + unsigned EndLine, EndColumn; + if (RangeSplit.second.empty()) { + EndLine = Begin.Line; + EndColumn = Begin.Column; + } else { + std::pair Split = RangeSplit.second.rsplit(':'); + if (Split.first.getAsInteger(10, EndLine) || + Split.second.getAsInteger(10, EndColumn)) + return None; + } + return ParsedSourceRange{std::move(Begin.FileName), + {Begin.Line, Begin.Column}, + {EndLine, EndColumn}}; + } +}; } namespace llvm { Index: test/Refactor/tool-apply-replacements.cpp =================================================================== --- /dev/null +++ test/Refactor/tool-apply-replacements.cpp @@ -0,0 +1,8 @@ +// RUN: rm -f %t.cp.cpp +// RUN: cp %s %t.cp.cpp +// RUN: clang-refactor local-rename -selection=%t.cp.cpp:6:7 -new-name=test %t.cp.cpp -- +// RUN: grep -v CHECK %t.cp.cpp | FileCheck %t.cp.cpp + +class RenameMe { +// CHECK: class test { +}; Index: test/Refactor/tool-selection-option.c =================================================================== --- /dev/null +++ test/Refactor/tool-selection-option.c @@ -0,0 +1,15 @@ +// RUN: rm -f %t.cp.c +// RUN: cp %s %t.cp.c +// RUN: clang-refactor local-rename -selection=%t.cp.c:6:5 -new-name=test -v %t.cp.c -- | FileCheck --check-prefix=CHECK1 %s +// RUN: clang-refactor local-rename -selection=%t.cp.c:6:5-6:9 -new-name=test -v %t.cp.c -- | FileCheck --check-prefix=CHECK2 %s + +int test; + +// CHECK1: invoking action 'local-rename': +// CHECK1-NEXT: -selection={{.*}}.cp.c:6:5 -> {{.*}}.cp.c:6:5 + +// CHECK2: invoking action 'local-rename': +// CHECK2-NEXT: -selection={{.*}}.cp.c:6:5 -> {{.*}}.cp.c:6:9 + +// RUN: not clang-refactor local-rename -selection=%s:6:5 -new-name=test -v %t.cp.c -- 2>&1 | FileCheck --check-prefix=CHECK-FILE-ERR %s +// CHECK-FILE-ERR: given file is not in the target TU Index: tools/clang-refactor/ClangRefactor.cpp =================================================================== --- tools/clang-refactor/ClangRefactor.cpp +++ tools/clang-refactor/ClangRefactor.cpp @@ -14,6 +14,7 @@ //===----------------------------------------------------------------------===// #include "TestSupport.h" +#include "clang/Frontend/CommandLineSourceLoc.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Refactoring.h" @@ -54,7 +55,7 @@ /// Prints any additional state associated with the selection argument to /// the given output stream. - virtual void print(raw_ostream &OS) = 0; + virtual void print(raw_ostream &OS) {} /// Returns a replacement refactoring result consumer (if any) that should /// consume the results of a refactoring operation. @@ -99,6 +100,41 @@ TestSelectionRangesInFile TestSelections; }; +/// Stores the parsed -selection=filename:line:column[-line:column] option. +class SourceRangeSelectionArgument final : public SourceSelectionArgument { +public: + SourceRangeSelectionArgument(ParsedSourceRange Range) + : Range(std::move(Range)) {} + + bool forAllRanges(const SourceManager &SM, + llvm::function_ref Callback) override { + const FileEntry *FE = SM.getFileManager().getFile(Range.FileName); + FileID FID = FE ? SM.translateFile(FE) : FileID(); + if (!FE || FID.isInvalid()) { + llvm::errs() << "error: -selection=" << Range.FileName + << ":... : given file is not in the target TU\n"; + return true; + } + + SourceLocation Start = SM.getMacroArgExpandedLocation( + SM.translateLineCol(FID, Range.Begin.first, Range.Begin.second)); + SourceLocation End = SM.getMacroArgExpandedLocation( + SM.translateLineCol(FID, Range.End.first, Range.End.second)); + if (Start.isInvalid() || End.isInvalid()) { + llvm::errs() << "error: -selection=" << Range.FileName << ':' + << Range.Begin.first << ':' << Range.Begin.second << '-' + << Range.End.first << ':' << Range.End.second + << " : invalid source location\n"; + return true; + } + Callback(SourceRange(Start, End)); + return false; + } + +private: + ParsedSourceRange Range; +}; + std::unique_ptr SourceSelectionArgument::fromString(StringRef Value) { if (Value.startswith("test:")) { @@ -110,10 +146,12 @@ return llvm::make_unique( std::move(*ParsedTestSelection)); } - // FIXME: Support true selection ranges. + Optional Range = ParsedSourceRange::fromString(Value); + if (Range) + return llvm::make_unique(std::move(*Range)); llvm::errs() << "error: '-selection' option must be specified using " ":: or " - "::-: format"; + "::-: format\n"; return nullptr; } @@ -272,7 +310,14 @@ llvm::errs() << llvm::toString(std::move(Err)) << "\n"; } - // FIXME: Consume atomic changes and apply them to files. + void handle(AtomicChanges Changes) { + SourceChanges.insert(SourceChanges.begin(), Changes.begin(), Changes.end()); + } + + const AtomicChanges &getSourceChanges() const { return SourceChanges; } + +private: + AtomicChanges SourceChanges; }; class ClangRefactorTool { @@ -352,6 +397,39 @@ } } + bool applySourceChanges(const AtomicChanges &Replacements) { + std::set Files; + for (const auto &Change : Replacements) + Files.insert(Change.getFilePath()); + // FIXME: Add automatic formatting support as well. + tooling::ApplyChangesSpec Spec; + // FIXME: We should probably cleanup the result by default as well. + Spec.Cleanup = false; + for (const auto &File : Files) { + llvm::ErrorOr> BufferErr = + llvm::MemoryBuffer::getFile(File); + if (!BufferErr) { + llvm::errs() << "error: failed to open" << File << " for rewriting\n"; + return true; + } + auto Result = tooling::applyAtomicChanges(File, (*BufferErr)->getBuffer(), + Replacements, Spec); + if (!Result) { + llvm::errs() << toString(Result.takeError()); + return true; + } + + std::error_code EC; + llvm::raw_fd_ostream OS(File, EC, llvm::sys::fs::F_Text); + if (EC) { + llvm::errs() << EC.message() << "\n"; + return true; + } + OS << *Result; + } + return false; + } + bool invokeAction(RefactoringActionSubcommand &Subcommand, const CompilationDatabase &DB, ArrayRef Sources) { @@ -423,7 +501,7 @@ // FIXME (Alex L): Implement non-selection based invocation path. })) return true; - return HasFailed; + return HasFailed || applySourceChanges(Consumer.getSourceChanges()); } };