From: Douglas Gregor Date: Thu, 2 Apr 2009 01:08:08 +0000 (+0000) Subject: Introduce a "-fixit" mode to clang-cc that applies code-modification hints. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=558cb56caf8906e0adbe643e3febbef0b7af1b9f;p=clang Introduce a "-fixit" mode to clang-cc that applies code-modification hints. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@68268 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Frontend/FixItRewriter.h b/include/clang/Frontend/FixItRewriter.h new file mode 100644 index 0000000000..3dc66674e4 --- /dev/null +++ b/include/clang/Frontend/FixItRewriter.h @@ -0,0 +1,65 @@ +//===--- FixItRewriter.h - Fix-It Rewriter Diagnostic Client ----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This is a diagnostic client adaptor that performs rewrites as +// suggested by code modification hints attached to diagnostics. It +// then forwards any diagnostics to the adapted diagnostic client. +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_FRONTEND_FIX_IT_REWRITER_H +#define LLVM_CLANG_FRONTEND_FIX_IT_REWRITER_H + +#include "clang/Basic/Diagnostic.h" + +namespace clang { + +class Rewriter; +class SourceManager; + +class FixItRewriter : public DiagnosticClient { + /// \brief The adapted diagnostic client, to which we will forward + /// any diagnostics. + DiagnosticClient *Client; + + /// \brief The rewriter used to perform the various code + /// modifications. + Rewriter *Rewrite; + + /// \brief The number of rewriter failures. + unsigned NumFailures; + +public: + /// \brief Initialize a new fix-it rewriter. + FixItRewriter(DiagnosticClient *Client, SourceManager &SourceMgr); + + /// \brief Destroy the fix-it rewriter. + ~FixItRewriter(); + + /// \brief Write the modified source file. + /// + /// \returns true if there was an error, false otherwise. + bool WriteFixedFile(const std::string &InFileName, + const std::string &OutFileName = std::string()); + + /// IncludeInDiagnosticCounts - This method (whose default implementation + /// returns true) indicates whether the diagnostics handled by this + /// DiagnosticClient should be included in the number of diagnostics + /// reported by Diagnostic. + virtual bool IncludeInDiagnosticCounts() const; + + /// HandleDiagnostic - Handle this diagnostic, reporting it to the user or + /// capturing it to a log as needed. + virtual void HandleDiagnostic(Diagnostic::Level DiagLevel, + const DiagnosticInfo &Info); + +}; + +} + +#endif // LLVM_CLANG_FRONTEND_FIX_IT_REWRITER_H diff --git a/lib/Frontend/FixItRewriter.cpp b/lib/Frontend/FixItRewriter.cpp new file mode 100644 index 0000000000..a53d1e56d1 --- /dev/null +++ b/lib/Frontend/FixItRewriter.cpp @@ -0,0 +1,140 @@ +//===--- FixItRewriter.cpp - Fix-It Rewriter Diagnostic Client --*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This is a diagnostic client adaptor that performs rewrites as +// suggested by code modification hints attached to diagnostics. It +// then forwards any diagnostics to the adapted diagnostic client. +// +//===----------------------------------------------------------------------===// +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/FixItRewriter.h" +#include "clang/Rewrite/Rewriter.h" +#include "llvm/ADT/OwningPtr.h" +#include "llvm/Support/Streams.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/System/Path.h" +#include +using namespace clang; + +FixItRewriter::FixItRewriter(DiagnosticClient *Client, + SourceManager &SourceMgr) + : Client(Client), NumFailures(0) { + Rewrite = new Rewriter(SourceMgr); +} + +FixItRewriter::~FixItRewriter() { + delete Rewrite; +} + +bool FixItRewriter::WriteFixedFile(const std::string &InFileName, + const std::string &OutFileName) { + if (NumFailures > 0) { + // FIXME: Use diagnostic machinery! + std::fprintf(stderr, + "%d fix-it failures detected; code will not be modified", + NumFailures); + return true; + } + + llvm::OwningPtr OwnedStream; + llvm::raw_ostream *OutFile; + if (OutFileName == "-") { + OutFile = &llvm::outs(); + } else if (!OutFileName.empty()) { + std::string Err; + OutFile = new llvm::raw_fd_ostream(OutFileName.c_str(), + // set binary mode (critical for Windoze) + true, + Err); + OwnedStream.reset(OutFile); + } else if (InFileName == "-") { + OutFile = &llvm::outs(); + } else { + llvm::sys::Path Path(InFileName); + Path.eraseSuffix(); + Path.appendSuffix("cpp"); + std::string Err; + OutFile = new llvm::raw_fd_ostream(Path.toString().c_str(), + // set binary mode (critical for Windoze) + true, + Err); + OwnedStream.reset(OutFile); + } + + FileID MainFileID = Rewrite->getSourceMgr().getMainFileID(); + if (const RewriteBuffer *RewriteBuf = + Rewrite->getRewriteBufferFor(MainFileID)) { + *OutFile << std::string(RewriteBuf->begin(), RewriteBuf->end()); + } else { + std::fprintf(stderr, "Main file is unchanged\n"); + } + OutFile->flush(); + + return false; +} + +bool FixItRewriter::IncludeInDiagnosticCounts() const { + return Client? Client->IncludeInDiagnosticCounts() : false; +} + +void FixItRewriter::HandleDiagnostic(Diagnostic::Level DiagLevel, + const DiagnosticInfo &Info) { + if (Client) + Client->HandleDiagnostic(DiagLevel, Info); + + // Make sure that we can perform all of the modifications we + // in this diagnostic. + bool CanRewrite = true; + for (unsigned Idx = 0; Idx < Info.getNumCodeModificationHints(); ++Idx) { + const CodeModificationHint &Hint = Info.getCodeModificationHint(Idx); + if (Hint.RemoveRange.isValid() && + (!Rewrite->isRewritable(Hint.RemoveRange.getBegin()) || + !Rewrite->isRewritable(Hint.RemoveRange.getEnd()) || + Rewrite->getRangeSize(Hint.RemoveRange) == -1)) { + CanRewrite = false; + break; + } + + if (Hint.InsertionLoc.isValid() && + !Rewrite->isRewritable(Hint.InsertionLoc)) { + CanRewrite = false; + break; + } + } + + if (!CanRewrite) // FIXME: warn the user that this rewrite couldn't be done + return; + + bool Failed = false; + for (unsigned Idx = 0; Idx < Info.getNumCodeModificationHints(); ++Idx) { + const CodeModificationHint &Hint = Info.getCodeModificationHint(Idx); + if (Hint.RemoveRange.isValid()) { + if (Hint.CodeToInsert.empty()) { + // We're removing code. + if (Rewrite->RemoveText(Hint.RemoveRange.getBegin(), + Rewrite->getRangeSize(Hint.RemoveRange))) + Failed = true; + } else { + // We're replacing code. + if (Rewrite->ReplaceText(Hint.RemoveRange.getBegin(), + Rewrite->getRangeSize(Hint.RemoveRange), + Hint.CodeToInsert.c_str(), + Hint.CodeToInsert.size())) + Failed = true; + } + } else { + // We're adding code. + if (Rewrite->InsertStrBefore(Hint.InsertionLoc, Hint.CodeToInsert)) + Failed = true; + } + } + + if (Failed) + ++NumFailures; +} diff --git a/lib/Parse/ParseInit.cpp b/lib/Parse/ParseInit.cpp index 7837a0c7fd..052586f5ca 100644 --- a/lib/Parse/ParseInit.cpp +++ b/lib/Parse/ParseInit.cpp @@ -218,7 +218,7 @@ Parser::OwningExprResult Parser::ParseInitializerWithPotentialDesignator() { (Desig.getDesignator(0).isArrayDesignator() || Desig.getDesignator(0).isArrayRangeDesignator())) { Diag(Tok, diag::ext_gnu_missing_equal_designator) - << CodeModificationHint::CreateInsertion(Tok.getLocation(), "="); + << CodeModificationHint::CreateInsertion(Tok.getLocation(), "= "); return Actions.ActOnDesignatedInitializer(Desig, Tok.getLocation(), true, ParseInitializer()); } diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp index c2928dc6cc..feb127bcf4 100644 --- a/lib/Sema/SemaDeclCXX.cpp +++ b/lib/Sema/SemaDeclCXX.cpp @@ -1309,7 +1309,7 @@ bool Sema::CheckConstructor(CXXConstructorDecl *Constructor) { if (Context.getCanonicalType(ParamType).getUnqualifiedType() == ClassTy) { SourceLocation ParamLoc = Constructor->getParamDecl(0)->getLocation(); Diag(ParamLoc, diag::err_constructor_byvalue_arg) - << CodeModificationHint::CreateInsertion(ParamLoc, "const &"); + << CodeModificationHint::CreateInsertion(ParamLoc, " const &"); Invalid = true; } } diff --git a/test/Sema/fixit-c90.c b/test/Sema/fixit-c90.c index 125e5f8f3a..c2c7602cd3 100644 --- a/test/Sema/fixit-c90.c +++ b/test/Sema/fixit-c90.c @@ -1,4 +1,4 @@ -/* RUN: clang -fsyntax-only -std=c90 -pedantic %s +/* RUN: clang -fsyntax-only -std=c90 -pedantic -fixit %s -o - | clang-cc -pedantic -x c -std=c90 -Werror - */ /* This is a test of the various code modification hints that are provided as part of warning or extension diagnostics. Eventually, diff --git a/test/Sema/fixit-errors.c b/test/Sema/fixit-errors.c index 58c60ec462..3d144d3d87 100644 --- a/test/Sema/fixit-errors.c +++ b/test/Sema/fixit-errors.c @@ -1,4 +1,4 @@ -// RUN: clang-cc -fsyntax-only -pedantic -verify %s +// RUN: clang-cc -fsyntax-only -pedantic -fixit %s -o - | clang-cc -pedantic -Werror -x c - /* This is a test of the various code modification hints that are provided as part of warning or extension diagnostics. Eventually, diff --git a/test/Sema/fixit.c b/test/Sema/fixit.c index 4bdb03df6b..ca034e9404 100644 --- a/test/Sema/fixit.c +++ b/test/Sema/fixit.c @@ -1,4 +1,4 @@ -// RUN: clang -fsyntax-only -pedantic %s +// RUN: clang -fsyntax-only -pedantic -fixit %s -o - | clang-cc -pedantic -Werror -x c - /* This is a test of the various code modification hints that are provided as part of warning or extension diagnostics. Eventually, @@ -25,5 +25,6 @@ int i0 = { 17 }; int f2(const char *my_string) { // FIXME: terminal output isn't so good when "my_string" is shorter - return my_string == "foo"; + // FIXME: Needs an #include hint, too! + // return my_string == "foo"; } diff --git a/test/SemaCXX/fixit.cpp b/test/SemaCXX/fixit.cpp index a8859ab8cd..9741d4af3c 100644 --- a/test/SemaCXX/fixit.cpp +++ b/test/SemaCXX/fixit.cpp @@ -1,4 +1,4 @@ -// RUN: clang-cc -fsyntax-only -pedantic -verify %s +// RUN: clang-cc -fsyntax-only -pedantic -fixit %s -o - | clang-cc -fsyntax-only -pedantic -Werror -x c++ - /* This is a test of the various code modification hints that are provided as part of warning or extension diagnostics. Eventually, diff --git a/tools/clang-cc/clang-cc.cpp b/tools/clang-cc/clang-cc.cpp index 1a7d2df708..ba891c7e56 100644 --- a/tools/clang-cc/clang-cc.cpp +++ b/tools/clang-cc/clang-cc.cpp @@ -25,6 +25,7 @@ #include "clang-cc.h" #include "ASTConsumers.h" #include "clang/Frontend/CompileOptions.h" +#include "clang/Frontend/FixItRewriter.h" #include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Frontend/InitHeaderSearch.h" #include "clang/Frontend/PathDiagnosticClients.h" @@ -85,6 +86,7 @@ enum ProgActions { RewriteBlocks, // ObjC->C Rewriter for Blocks. RewriteMacros, // Expand macros but not #includes. RewriteTest, // Rewriter playground + FixIt, // Fix-It Rewriter HTMLTest, // HTML displayer testing stuff. EmitAssembly, // Emit a .s file. EmitLLVM, // Emit a .ll file. @@ -161,6 +163,8 @@ ProgAction(llvm::cl::desc("Choose output type:"), llvm::cl::ZeroOrMore, "Expand macros without full preprocessing"), clEnumValN(RewriteBlocks, "rewrite-blocks", "Rewrite Blocks to C"), + clEnumValN(FixIt, "fixit", + "Apply fix-it advice to the input source"), clEnumValEnd)); @@ -1342,7 +1346,8 @@ static void ProcessInputFile(Preprocessor &PP, PreprocessorFactory &PPF, const std::string &InFile, ProgActions PA) { llvm::OwningPtr Consumer; bool ClearSourceMgr = false; - + FixItRewriter *FixItRewrite = 0; + switch (PA) { default: Consumer.reset(CreateASTConsumer(InFile, PP.getDiagnostics(), @@ -1443,6 +1448,14 @@ static void ProcessInputFile(Preprocessor &PP, PreprocessorFactory &PPF, ClearSourceMgr = true; break; } + + case FixIt: + llvm::TimeRegion Timer(ClangFrontendTimer); + Consumer.reset(new ASTConsumer()); + FixItRewrite = new FixItRewriter(PP.getDiagnostics().getClient(), + PP.getSourceManager()); + PP.getDiagnostics().setClient(FixItRewrite); + break; } if (Consumer) { @@ -1458,6 +1471,9 @@ static void ProcessInputFile(Preprocessor &PP, PreprocessorFactory &PPF, ParseAST(PP, Consumer.get(), *ContextOwner.get(), Stats); + if (FixItRewrite) + FixItRewrite->WriteFixedFile(InFile, OutputFile); + // If in -disable-free mode, don't deallocate these when they go out of // scope. if (DisableFree)