Introduce a "-fixit" mode to clang-cc that applies code-modification hints.

llvm-svn: 68268
This commit is contained in:
Douglas Gregor 2009-04-02 01:08:08 +00:00
parent 40968598c7
commit 578dae57ca
9 changed files with 230 additions and 8 deletions

View File

@ -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

View File

@ -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 <cstdio>
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<llvm::raw_ostream> 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;
}

View File

@ -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());
}

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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";
}

View File

@ -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,

View File

@ -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<ASTConsumer> 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)