]> granicus.if.org Git - clang/commitdiff
[clang-refactor] Apply source replacements
authorAlex Lorenz <arphaman@gmail.com>
Fri, 13 Oct 2017 19:42:05 +0000 (19:42 +0000)
committerAlex Lorenz <arphaman@gmail.com>
Fri, 13 Oct 2017 19:42:05 +0000 (19:42 +0000)
This commit actually brings clang-refactor to a usable state as it can now
apply the refactoring changes to source files.
The -selection option is now also fully supported.

Differential Revision: https://reviews.llvm.org/D38402

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@315738 91177308-0d34-0410-b5e6-96231b3b80d8

include/clang/Frontend/CommandLineSourceLoc.h
test/Refactor/tool-apply-replacements.cpp [new file with mode: 0644]
test/Refactor/tool-selection-option.c [new file with mode: 0644]
tools/clang-refactor/ClangRefactor.cpp

index a78c96d23afa3af0444f246907413333d6e3a6f7..206bd8633090ba3e66b650cf923cb56a509e813d 100644 (file)
@@ -51,6 +51,51 @@ public:
   }
 };
 
+/// A source range that has been parsed on the command line.
+struct ParsedSourceRange {
+  std::string FileName;
+  /// The starting location of the range. The first element is the line and
+  /// the second element is the column.
+  std::pair<unsigned, unsigned> Begin;
+  /// The ending location of the range. The first element is the line and the
+  /// second element is the column.
+  std::pair<unsigned, unsigned> End;
+
+  /// Returns a parsed source range from a string or None if the string is
+  /// invalid.
+  ///
+  /// These source string has the following format:
+  ///
+  /// file:start_line:start_column[-end_line:end_column]
+  ///
+  /// If the end line and column are omitted, the starting line and columns
+  /// are used as the end values.
+  static Optional<ParsedSourceRange> fromString(StringRef Str) {
+    std::pair<StringRef, StringRef> 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<StringRef, StringRef> 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 {
diff --git a/test/Refactor/tool-apply-replacements.cpp b/test/Refactor/tool-apply-replacements.cpp
new file mode 100644 (file)
index 0000000..9f45950
--- /dev/null
@@ -0,0 +1,11 @@
+// RUN: rm -f %t.cp.cpp
+// RUN: cp %s %t.cp.cpp
+// RUN: clang-refactor local-rename -selection=%t.cp.cpp:9:7 -new-name=test %t.cp.cpp --
+// RUN: grep -v CHECK %t.cp.cpp | FileCheck %t.cp.cpp
+// RUN: cp %s %t.cp.cpp
+// RUN: clang-refactor local-rename -selection=%t.cp.cpp:9:7-9:15 -new-name=test %t.cp.cpp --
+// RUN: grep -v CHECK %t.cp.cpp | FileCheck %t.cp.cpp
+
+class RenameMe {
+// CHECK: class test {
+};
diff --git a/test/Refactor/tool-selection-option.c b/test/Refactor/tool-selection-option.c
new file mode 100644 (file)
index 0000000..f80457a
--- /dev/null
@@ -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 b1a1ebdf6845eb14866ba0b9bc2ad2e851989615..72b3dab89210c1f8bf75c2bc55eb9ef701b79595 100644 (file)
@@ -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 @@ public:
 
   /// 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 @@ private:
   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<void(SourceRange R)> 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>
 SourceSelectionArgument::fromString(StringRef Value) {
   if (Value.startswith("test:")) {
@@ -110,10 +146,12 @@ SourceSelectionArgument::fromString(StringRef Value) {
     return llvm::make_unique<TestSourceSelectionArgument>(
         std::move(*ParsedTestSelection));
   }
-  // FIXME: Support true selection ranges.
+  Optional<ParsedSourceRange> Range = ParsedSourceRange::fromString(Value);
+  if (Range)
+    return llvm::make_unique<SourceRangeSelectionArgument>(std::move(*Range));
   llvm::errs() << "error: '-selection' option must be specified using "
                   "<file>:<line>:<column> or "
-                  "<file>:<line>:<column>-<line>:<column> format";
+                  "<file>:<line>:<column>-<line>:<column> format\n";
   return nullptr;
 }
 
@@ -268,11 +306,18 @@ private:
 
 class ClangRefactorConsumer : public RefactoringResultConsumer {
 public:
-  void handleError(llvm::Error Err) {
+  void handleError(llvm::Error Err) override {
     llvm::errs() << llvm::toString(std::move(Err)) << "\n";
   }
 
-  // FIXME: Consume atomic changes and apply them to files.
+  void handle(AtomicChanges Changes) override {
+    SourceChanges.insert(SourceChanges.begin(), Changes.begin(), Changes.end());
+  }
+
+  const AtomicChanges &getSourceChanges() const { return SourceChanges; }
+
+private:
+  AtomicChanges SourceChanges;
 };
 
 class ClangRefactorTool {
@@ -352,6 +397,39 @@ public:
     }
   }
 
+  bool applySourceChanges(const AtomicChanges &Replacements) {
+    std::set<std::string> 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<std::unique_ptr<llvm::MemoryBuffer>> 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<std::string> Sources) {
@@ -423,7 +501,7 @@ public:
           // FIXME (Alex L): Implement non-selection based invocation path.
         }))
       return true;
-    return HasFailed;
+    return HasFailed || applySourceChanges(Consumer.getSourceChanges());
   }
 };