[include-fixer] Sort headers after inserting new headers.

Summary: [include-fixer] Sort headers after inserting new headers.

Reviewers: bkramer

Subscribers: klimek, djasper, hokein, cfe-commits

Differential Revision: http://reviews.llvm.org/D20370

git-svn-id: https://llvm.org/svn/llvm-project/clang-tools-extra/trunk@270031 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Eric Liu 2016-05-19 08:21:09 +00:00
Родитель 328a9927a9
Коммит 21d54d1673
6 изменённых файлов: 134 добавлений и 59 удалений

Просмотреть файл

@ -11,6 +11,7 @@ add_clang_library(clangIncludeFixer
LINK_LIBS
clangAST
clangBasic
clangFormat
clangFrontend
clangLex
clangParse

Просмотреть файл

@ -8,6 +8,7 @@
//===----------------------------------------------------------------------===//
#include "IncludeFixer.h"
#include "clang/Format/Format.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/Preprocessor.h"
@ -58,8 +59,9 @@ private:
class Action : public clang::ASTFrontendAction,
public clang::ExternalSemaSource {
public:
explicit Action(SymbolIndexManager &SymbolIndexMgr, bool MinimizeIncludePaths)
: SymbolIndexMgr(SymbolIndexMgr),
explicit Action(SymbolIndexManager &SymbolIndexMgr, StringRef StyleName,
bool MinimizeIncludePaths)
: SymbolIndexMgr(SymbolIndexMgr), FallbackStyle(StyleName),
MinimizeIncludePaths(MinimizeIncludePaths) {}
std::unique_ptr<clang::ASTConsumer>
@ -234,26 +236,61 @@ public:
return IsSystem ? '<' + Suggestion + '>' : '"' + Suggestion + '"';
}
/// Insert all headers before the first #include in \p Code and run
/// clang-format to sort all headers.
/// \return Replacements for inserting and sorting headers.
std::vector<clang::tooling::Replacement>
CreateReplacementsForHeaders(StringRef Code,
const std::set<std::string> &Headers) {
std::set<std::string> ExistingHeaders;
// Create replacements for new headers.
clang::tooling::Replacements Insertions;
if (FirstIncludeOffset == -1U) {
// FIXME: skip header guards.
FirstIncludeOffset = 0;
// If there is no existing #include, then insert an empty line after new
// header block.
if (Code.front() != '\n')
Insertions.insert(
clang::tooling::Replacement(Filename, FirstIncludeOffset, 0, "\n"));
}
// Keep inserting new headers before the first header.
for (StringRef Header : Headers) {
std::string Text = "#include " + Header.str() + "\n";
Insertions.insert(
clang::tooling::Replacement(Filename, FirstIncludeOffset, 0, Text));
}
DEBUG(llvm::dbgs() << "Header insertions:\n");
for (const auto &R : Insertions) {
DEBUG(llvm::dbgs() << R.toString() << "\n");
}
clang::format::FormatStyle Style =
clang::format::getStyle("file", Filename, FallbackStyle);
clang::tooling::Replacements Replaces =
formatReplacements(Code, Insertions, Style);
// FIXME: remove this when `clang::tooling::Replacements` is implemented as
// `std::vector<clang::tooling::Replacement>`.
std::vector<clang::tooling::Replacement> Results;
std::copy(Replaces.begin(), Replaces.end(), std::back_inserter(Results));
return Results;
}
/// Generate replacements for the suggested includes.
/// \return true if changes will be made, false otherwise.
bool Rewrite(clang::SourceManager &SourceManager,
clang::HeaderSearch &HeaderSearch,
std::vector<clang::tooling::Replacement> &replacements) {
std::set<std::string> &Headers,
std::vector<clang::tooling::Replacement> &Replacements) {
if (Untried.empty())
return false;
const auto &ToTry = UntriedList.front();
std::string ToAdd = "#include " +
minimizeInclude(ToTry, SourceManager, HeaderSearch) +
"\n";
DEBUG(llvm::dbgs() << "Adding " << ToAdd << "\n");
Headers.insert(minimizeInclude(ToTry, SourceManager, HeaderSearch));
if (FirstIncludeOffset == -1U)
FirstIncludeOffset = 0;
replacements.push_back(clang::tooling::Replacement(
SourceManager, FileBegin.getLocWithOffset(FirstIncludeOffset), 0,
ToAdd));
StringRef Code = SourceManager.getBufferData(SourceManager.getMainFileID());
Replacements = CreateReplacementsForHeaders(Code, Headers);
// We currently abort after the first inserted include. The more
// includes we have the less safe this becomes due to error recovery
@ -314,6 +351,10 @@ private:
/// be used as the insertion point for new include directives.
unsigned FirstIncludeOffset = -1U;
/// The fallback format style for formatting after insertion if there is no
/// clang-format config file found.
std::string FallbackStyle;
/// Includes we have left to try. A set to unique them and a list to keep
/// track of the order. We prefer includes that were discovered early to avoid
/// getting caught in results from error recovery.
@ -365,11 +406,12 @@ void PreprocessorHooks::InclusionDirective(
} // namespace
IncludeFixerActionFactory::IncludeFixerActionFactory(
SymbolIndexManager &SymbolIndexMgr,
std::vector<clang::tooling::Replacement> &Replacements,
SymbolIndexManager &SymbolIndexMgr, std::set<std::string> &Headers,
std::vector<clang::tooling::Replacement> &Replacements, StringRef StyleName,
bool MinimizeIncludePaths)
: SymbolIndexMgr(SymbolIndexMgr), Replacements(Replacements),
MinimizeIncludePaths(MinimizeIncludePaths) {}
: SymbolIndexMgr(SymbolIndexMgr), Headers(Headers),
Replacements(Replacements), MinimizeIncludePaths(MinimizeIncludePaths),
FallbackStyle(StyleName) {}
IncludeFixerActionFactory::~IncludeFixerActionFactory() = default;
@ -395,14 +437,14 @@ bool IncludeFixerActionFactory::runInvocation(
Compiler.getDiagnostics().setErrorLimit(0);
// Run the parser, gather missing includes.
auto ScopedToolAction =
llvm::make_unique<Action>(SymbolIndexMgr, MinimizeIncludePaths);
auto ScopedToolAction = llvm::make_unique<Action>(
SymbolIndexMgr, FallbackStyle, MinimizeIncludePaths);
Compiler.ExecuteAction(*ScopedToolAction);
// Generate replacements.
ScopedToolAction->Rewrite(Compiler.getSourceManager(),
Compiler.getPreprocessor().getHeaderSearchInfo(),
Replacements);
Headers, Replacements);
// Technically this should only return true if we're sure that we have a
// parseable file. We don't know that though. Only inform users of fatal

Просмотреть файл

@ -29,11 +29,13 @@ class IncludeFixerActionFactory : public clang::tooling::ToolAction {
public:
/// \param SymbolIndexMgr A source for matching symbols to header files.
/// \param Replacements Storage for the output of the fixer.
/// \param StyleName Fallback style for reformatting.
/// \param MinimizeIncludePaths whether inserted include paths are optimized.
IncludeFixerActionFactory(
SymbolIndexManager &SymbolIndexMgr,
SymbolIndexManager &SymbolIndexMgr, std::set<std::string> &Headers,
std::vector<clang::tooling::Replacement> &Replacements,
bool MinimizeIncludePaths = true);
StringRef StyleName, bool MinimizeIncludePaths = true);
~IncludeFixerActionFactory() override;
bool
@ -46,11 +48,18 @@ private:
/// The client to use to find cross-references.
SymbolIndexManager &SymbolIndexMgr;
/// Headers to be added.
std::set<std::string> &Headers;
/// Replacements are written here.
std::vector<clang::tooling::Replacement> &Replacements;
/// Whether inserted include paths should be optimized.
bool MinimizeIncludePaths;
/// The fallback format style for formatting after insertion if no
/// clang-format config file was found.
std::string FallbackStyle;
};
} // namespace include_fixer

Просмотреть файл

@ -56,6 +56,12 @@ cl::opt<bool>
"used for editor integration."),
cl::init(false), cl::cat(IncludeFixerCategory));
cl::opt<std::string>
Style("style",
cl::desc("Fallback style for reformatting after inserting new "
"headers if there is no clang-format config file found."),
cl::init("llvm"), cl::cat(IncludeFixerCategory));
int includeFixerMain(int argc, const char **argv) {
tooling::CommonOptionsParser options(argc, argv, IncludeFixerCategory);
tooling::ClangTool tool(options.getCompilations(),
@ -133,9 +139,10 @@ int includeFixerMain(int argc, const char **argv) {
}
// Now run our tool.
std::set<std::string> Headers; // Headers to be added.
std::vector<tooling::Replacement> Replacements;
include_fixer::IncludeFixerActionFactory Factory(
*SymbolIndexMgr, Replacements, MinimizeIncludePaths);
*SymbolIndexMgr, Headers, Replacements, Style, MinimizeIncludePaths);
if (tool.run(&Factory) != 0) {
llvm::errs()
@ -144,8 +151,8 @@ int includeFixerMain(int argc, const char **argv) {
}
if (!Quiet)
for (const tooling::Replacement &Replacement : Replacements)
llvm::errs() << "Added " << Replacement.getReplacementText();
for (const auto &Header : Headers)
llvm::errs() << "Added #include " << Header;
// Set up a new source manager for applying the resulting replacements.
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions);
@ -155,11 +162,10 @@ int includeFixerMain(int argc, const char **argv) {
Diagnostics.setClient(&DiagnosticPrinter, false);
if (STDINMode) {
for (const tooling::Replacement &Replacement : Replacements) {
FileID ID = SM.getMainFileID();
unsigned LineNum = SM.getLineNumber(ID, Replacement.getOffset());
llvm::outs() << LineNum << "," << Replacement.getReplacementText();
}
tooling::Replacements Replaces(Replacements.begin(), Replacements.end());
std::string ChangedCode =
tooling::applyAllReplacements(Code->getBuffer(), Replaces);
llvm::outs() << ChangedCode;
return 0;
}

Просмотреть файл

@ -16,6 +16,7 @@
# or save any files. To revert a fix, just undo.
import argparse
import difflib
import subprocess
import sys
import vim
@ -54,12 +55,10 @@ def main():
if stdout:
lines = stdout.splitlines()
for line in lines:
line_num, text = line.split(",")
# clang-include-fixer provides 1-based line number
line_num = int(line_num) - 1
print 'Inserting "{0}" at line {1}'.format(text, line_num)
vim.current.buffer[line_num:line_num] = [text]
sequence = difflib.SequenceMatcher(None, vim.current.buffer, lines)
for op in reversed(sequence.get_opcodes()):
if op[0] is not 'equal':
vim.current.buffer[op[1]:op[2]] = lines[op[3]:op[4]]
if __name__ == '__main__':
main()

Просмотреть файл

@ -70,8 +70,10 @@ static std::string runIncludeFixer(
SymbolIndexMgr->addSymbolIndex(
llvm::make_unique<include_fixer::InMemorySymbolIndex>(Symbols));
std::set<std::string> Headers;
std::vector<clang::tooling::Replacement> Replacements;
IncludeFixerActionFactory Factory(*SymbolIndexMgr, Replacements);
IncludeFixerActionFactory Factory(*SymbolIndexMgr, Headers, Replacements,
"llvm");
runOnCode(&Factory, Code, "input.cc", ExtraArgs);
clang::RewriterTestContext Context;
clang::FileID ID = Context.createInMemoryFile("input.cc", Code);
@ -80,32 +82,32 @@ static std::string runIncludeFixer(
}
TEST(IncludeFixer, Typo) {
EXPECT_EQ("#include <string>\nstd::string foo;\n",
EXPECT_EQ("#include <string>\n\nstd::string foo;\n",
runIncludeFixer("std::string foo;\n"));
EXPECT_EQ(
"// comment\n#include <string>\n#include \"foo.h\"\nstd::string foo;\n"
"// comment\n#include \"foo.h\"\n#include <string>\nstd::string foo;\n"
"#include \"dir/bar.h\"\n",
runIncludeFixer("// comment\n#include \"foo.h\"\nstd::string foo;\n"
"#include \"dir/bar.h\"\n"));
EXPECT_EQ("#include <string>\n#include \"foo.h\"\nstd::string foo;\n",
EXPECT_EQ("#include \"foo.h\"\n#include <string>\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",
"#include \"foo.h\"\n#include <string>\nstd::string::size_type foo;\n",
runIncludeFixer("#include \"foo.h\"\nstd::string::size_type foo;\n"));
// string without "std::" can also be fixed since fixed db results go through
// SymbolIndexManager, and SymbolIndexManager matches unqualified identifiers
// too.
EXPECT_EQ("#include <string>\nstring foo;\n",
EXPECT_EQ("#include <string>\n\nstring foo;\n",
runIncludeFixer("string foo;\n"));
}
TEST(IncludeFixer, IncompleteType) {
EXPECT_EQ(
"#include <string>\n#include \"foo.h\"\n"
"#include \"foo.h\"\n#include <string>\n"
"namespace std {\nclass string;\n}\nstring foo;\n",
runIncludeFixer("#include \"foo.h\"\n"
"namespace std {\nclass string;\n}\nstring foo;\n"));
@ -113,26 +115,26 @@ TEST(IncludeFixer, IncompleteType) {
TEST(IncludeFixer, MinimizeInclude) {
std::vector<std::string> IncludePath = {"-Idir/"};
EXPECT_EQ("#include \"otherdir/qux.h\"\na::b::foo bar;\n",
EXPECT_EQ("#include \"otherdir/qux.h\"\n\na::b::foo bar;\n",
runIncludeFixer("a::b::foo bar;\n", IncludePath));
IncludePath = {"-isystemdir"};
EXPECT_EQ("#include <otherdir/qux.h>\na::b::foo bar;\n",
EXPECT_EQ("#include <otherdir/qux.h>\n\na::b::foo bar;\n",
runIncludeFixer("a::b::foo bar;\n", IncludePath));
IncludePath = {"-iquotedir"};
EXPECT_EQ("#include \"otherdir/qux.h\"\na::b::foo bar;\n",
EXPECT_EQ("#include \"otherdir/qux.h\"\n\na::b::foo bar;\n",
runIncludeFixer("a::b::foo bar;\n", IncludePath));
IncludePath = {"-Idir", "-Idir/otherdir"};
EXPECT_EQ("#include \"qux.h\"\na::b::foo bar;\n",
EXPECT_EQ("#include \"qux.h\"\n\na::b::foo bar;\n",
runIncludeFixer("a::b::foo bar;\n", IncludePath));
}
TEST(IncludeFixer, NestedName) {
// Some tests don't pass for target *-win32.
std::vector<std::string> args = {"-target", "x86_64-unknown-unknown"};
EXPECT_EQ("#include \"dir/otherdir/qux.h\"\n"
EXPECT_EQ("#include \"dir/otherdir/qux.h\"\n\n"
"int x = a::b::foo(0);\n",
runIncludeFixer("int x = a::b::foo(0);\n", args));
@ -142,36 +144,52 @@ TEST(IncludeFixer, NestedName) {
EXPECT_EQ("#define FOO(x) a::##x\nint x = FOO(b::foo);\n",
runIncludeFixer("#define FOO(x) a::##x\nint x = FOO(b::foo);\n"));
EXPECT_EQ("#include \"dir/otherdir/qux.h\"\n"
EXPECT_EQ("#include \"dir/otherdir/qux.h\"\n\n"
"namespace a {}\nint a = a::b::foo(0);\n",
runIncludeFixer("namespace a {}\nint a = a::b::foo(0);\n", args));
}
TEST(IncludeFixer, MultipleMissingSymbols) {
EXPECT_EQ("#include <string>\nstd::string bar;\nstd::sting foo;\n",
EXPECT_EQ("#include <string>\n\nstd::string bar;\nstd::sting foo;\n",
runIncludeFixer("std::string bar;\nstd::sting foo;\n"));
}
TEST(IncludeFixer, ScopedNamespaceSymbols) {
EXPECT_EQ("#include \"bar.h\"\nnamespace a { b::bar b; }\n",
runIncludeFixer("namespace a { b::bar b; }\n"));
EXPECT_EQ("#include \"bar.h\"\nnamespace A { a::b::bar b; }\n",
runIncludeFixer("namespace A { a::b::bar b; }\n"));
EXPECT_EQ("#include \"bar.h\"\nnamespace a { void func() { b::bar b; } }\n",
runIncludeFixer("namespace a { void func() { b::bar b; } }\n"));
EXPECT_EQ("#include \"bar.h\"\n\nnamespace a {\nb::bar b;\n}",
runIncludeFixer("namespace a {\nb::bar b;\n}"));
EXPECT_EQ("#include \"bar.h\"\n\nnamespace A {\na::b::bar b;\n}",
runIncludeFixer("namespace A {\na::b::bar b;\n}"));
EXPECT_EQ("#include \"bar.h\"\n\nnamespace a {\nvoid func() { b::bar b; }\n}",
runIncludeFixer("namespace a {\nvoid func() { b::bar b; }\n}"));
EXPECT_EQ("namespace A { c::b::bar b; }\n",
runIncludeFixer("namespace A { c::b::bar b; }\n"));
// FIXME: The header should not be added here. Remove this after we support
// full match.
EXPECT_EQ("#include \"bar.h\"\nnamespace A { b::bar b; }\n",
runIncludeFixer("namespace A { b::bar b; }\n"));
EXPECT_EQ("#include \"bar.h\"\n\nnamespace A {\nb::bar b;\n}",
runIncludeFixer("namespace A {\nb::bar b;\n}"));
}
TEST(IncludeFixer, EnumConstantSymbols) {
EXPECT_EQ("#include \"color.h\"\nint test = a::b::Green;\n",
EXPECT_EQ("#include \"color.h\"\n\nint test = a::b::Green;\n",
runIncludeFixer("int test = a::b::Green;\n"));
}
// FIXME: add test cases for inserting and sorting multiple headers when
// include-fixer supports multiple headers insertion.
TEST(IncludeFixer, InsertAndSortSingleHeader) {
// Insert one header.
std::string Code = "#include \"a.h\"\n"
"#include \"foo.h\"\n"
"\n"
"namespace a { b::bar b; }";
std::string Expected = "#include \"a.h\"\n"
"#include \"bar.h\"\n"
"#include \"foo.h\"\n"
"\n"
"namespace a { b::bar b; }";
EXPECT_EQ(Expected, runIncludeFixer(Code));
}
} // namespace
} // namespace include_fixer
} // namespace clang