46 changes: 46 additions & 0 deletions clang-tools-extra/include-fixer/IncludeFixer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//===-- IncludeFixer.h - Include inserter -----------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXER_H
#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXER_H

#include "XrefsDB.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "clang/Tooling/Core/Replacement.h"
#include "clang/Tooling/Tooling.h"

namespace clang {
namespace include_fixer {

class IncludeFixerActionFactory : public clang::tooling::ToolAction {
public:
/// \param Xrefs A source for matching symbols to header files.
/// \param Replacements Storage for the output of the fixer.
IncludeFixerActionFactory(
XrefsDB &Xrefs, std::vector<clang::tooling::Replacement> &Replacements);
~IncludeFixerActionFactory();

bool
runInvocation(clang::CompilerInvocation *Invocation,
clang::FileManager *Files,
std::shared_ptr<clang::PCHContainerOperations> PCHContainerOps,
clang::DiagnosticConsumer *Diagnostics) override;

private:
/// The client to use to find cross-references.
XrefsDB &Xrefs;

/// Replacements are written here.
std::vector<clang::tooling::Replacement> &Replacements;
};

} // namespace include_fixer
} // namespace clang

#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXER_H
38 changes: 38 additions & 0 deletions clang-tools-extra/include-fixer/XrefsDB.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//===-- XrefsDB.h - Interface for symbol-header matching --------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_XREFSDB_H
#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_XREFSDB_H

#include "llvm/ADT/StringRef.h"
#include <vector>

namespace clang {
namespace include_fixer {

/// This class provides an interface for finding the header files corresponding
/// to an indentifier in the source code.
class XrefsDB {
public:
virtual ~XrefsDB() = default;

/// Search for header files to be included for an identifier.
/// \param Identifier The identifier being searched for. May or may not be
/// fully qualified.
/// \returns A list of inclusion candidates, in a format ready for being
/// pasted after an #include token.
// FIXME: Expose the type name so we can also insert using declarations (or
// fix the usage)
virtual std::vector<std::string> search(llvm::StringRef Identifier) = 0;
};

} // namespace include_fixer
} // namespace clang

#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_XREFSDB_H
11 changes: 11 additions & 0 deletions clang-tools-extra/include-fixer/tool/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)

add_clang_executable(clang-include-fixer ClangIncludeFixer.cpp)
target_link_libraries(clang-include-fixer
clangBasic
clangFrontend
clangIncludeFixer
clangRewrite
clangTooling
clangToolingCore
)
50 changes: 50 additions & 0 deletions clang-tools-extra/include-fixer/tool/ClangIncludeFixer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//===-- ClangIncludeFixer.cpp - Standalone include fixer ------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "InMemoryXrefsDB.h"
#include "IncludeFixer.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"
using namespace clang;

static llvm::cl::OptionCategory tool_options("Tool options");

int main(int argc, char **argv) {
clang::tooling::CommonOptionsParser options(argc, (const char **)argv,
tool_options);
clang::tooling::ClangTool tool(options.getCompilations(),
options.getSourcePathList());
// Set up the data source.
std::map<std::string, std::vector<std::string>> XrefsMap = {
{"std::string", {"<string>"}}};
auto XrefsDB =
llvm::make_unique<include_fixer::InMemoryXrefsDB>(std::move(XrefsMap));

// Now run our tool.
std::vector<clang::tooling::Replacement> Replacements;
include_fixer::IncludeFixerActionFactory Factory(*XrefsDB, Replacements);

tool.run(&Factory); // Always succeeds.

// Set up a new source manager for applying the resulting replacements.
llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> DiagOpts(
new clang::DiagnosticOptions);
clang::DiagnosticsEngine Diagnostics(new clang::DiagnosticIDs, &*DiagOpts);
clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::outs(), &*DiagOpts);
clang::SourceManager source_manager(Diagnostics, tool.getFiles());
Diagnostics.setClient(&DiagnosticPrinter, false);

// Write replacements to disk.
clang::Rewriter Rewrites(source_manager, clang::LangOptions());
clang::tooling::applyAllReplacements(Replacements, Rewrites);
return Rewrites.overwriteChangedFiles();
}
1 change: 1 addition & 0 deletions clang-tools-extra/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ add_subdirectory(clang-apply-replacements)
add_subdirectory(clang-rename)
add_subdirectory(clang-query)
add_subdirectory(clang-tidy)
add_subdirectory(include-fixer)
22 changes: 22 additions & 0 deletions clang-tools-extra/unittests/include-fixer/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
set(LLVM_LINK_COMPONENTS
support
)

get_filename_component(INCLUDE_FIXER_SOURCE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/../../include-fixer REALPATH)
include_directories(
${INCLUDE_FIXER_SOURCE_DIR}
)

add_extra_unittest(IncludeFixerTests
IncludeFixerTest.cpp
)

target_link_libraries(IncludeFixerTests
clangBasic
clangFrontend
clangIncludeFixer
clangRewrite
clangTooling
clangToolingCore
)
87 changes: 87 additions & 0 deletions clang-tools-extra/unittests/include-fixer/IncludeFixerTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//===-- IncludeFixerTest.cpp - Include fixer unit tests -------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "../../../../unittests/Tooling/RewriterTestContext.h"
#include "InMemoryXrefsDB.h"
#include "IncludeFixer.h"
#include "clang/Tooling/Tooling.h"
#include "gtest/gtest.h"
using namespace clang;

namespace clang {
namespace include_fixer {
namespace {

static bool runOnCode(tooling::ToolAction *ToolAction, StringRef Code,
StringRef FileName) {
llvm::IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem(
new vfs::InMemoryFileSystem);
llvm::IntrusiveRefCntPtr<FileManager> Files(
new FileManager(FileSystemOptions(), InMemoryFileSystem));
tooling::ToolInvocation Invocation(
{std::string("include_fixer"), std::string("-fsyntax-only"),
FileName.str()},
ToolAction, Files.get(), std::make_shared<PCHContainerOperations>());

InMemoryFileSystem->addFile(FileName, 0,
llvm::MemoryBuffer::getMemBuffer(Code));

InMemoryFileSystem->addFile("foo.h", 0,
llvm::MemoryBuffer::getMemBuffer("\n"));
InMemoryFileSystem->addFile("bar.h", 0,
llvm::MemoryBuffer::getMemBuffer("\n"));
return Invocation.run();
}

static std::string runIncludeFixer(StringRef Code) {
std::map<std::string, std::vector<std::string>> XrefsMap = {
{"std::string", {"<string>"}}, {"std::string::size_type", {"<string>"}}};
auto XrefsDB =
llvm::make_unique<include_fixer::InMemoryXrefsDB>(std::move(XrefsMap));
std::vector<clang::tooling::Replacement> Replacements;
IncludeFixerActionFactory Factory(*XrefsDB, Replacements);
runOnCode(&Factory, Code, "input.cc");
clang::RewriterTestContext Context;
clang::FileID ID = Context.createInMemoryFile("input.cc", Code);
clang::tooling::applyAllReplacements(Replacements, Context.Rewrite);
return Context.getRewrittenText(ID);
}

TEST(IncludeFixer, Typo) {
EXPECT_EQ("#include <string>\nstd::string foo;\n",
runIncludeFixer("std::string foo;\n"));

EXPECT_EQ(
"// comment\n#include <string>\n#include \"foo.h\"\nstd::string foo;\n"
"#include \"bar.h\"\n",
runIncludeFixer("// comment\n#include \"foo.h\"\nstd::string foo;\n"
"#include \"bar.h\"\n"));

EXPECT_EQ("#include <string>\n#include \"foo.h\"\nstd::string foo;\n",
runIncludeFixer("#include \"foo.h\"\nstd::string foo;\n"));

EXPECT_EQ(
"#include <string>\n#include \"foo.h\"\nstd::string::size_type foo;\n",
runIncludeFixer("#include \"foo.h\"\nstd::string::size_type foo;\n"));

// The fixed xrefs db doesn't know how to handle string without std::.
EXPECT_EQ("string foo;\n", runIncludeFixer("string foo;\n"));
}

TEST(IncludeFixer, IncompleteType) {
EXPECT_EQ(
"#include <string>\n#include \"foo.h\"\n"
"namespace std {\nclass string;\n}\nstring foo;\n",
runIncludeFixer("#include \"foo.h\"\n"
"namespace std {\nclass string;\n}\nstring foo;\n"));
}

} // namespace
} // namespace include_fixer
} // namespace clang