[include-fixer] Create a mode in vim integration to show multiple potential headers.

Summary:
Some changes in the patch:

* Add two commandline flags in clang-include-fixer.
* Introduce a IncludeFixerContext for the queried symbol.
* Pull out CreateReplacementsForHeader.

Reviewers: bkramer

Subscribers: klimek, cfe-commits, ioeric

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

llvm-svn: 271258
This commit is contained in:
Haojian Wu 2016-05-31 09:31:51 +00:00
parent d53e3655e2
commit 11e9bd2450
7 changed files with 267 additions and 104 deletions

View File

@ -214,7 +214,7 @@ public:
/// Get the minimal include for a given path.
std::string minimizeInclude(StringRef Include,
clang::SourceManager &SourceManager,
const clang::SourceManager &SourceManager,
clang::HeaderSearch &HeaderSearch) {
if (!MinimizeIncludePaths)
return Include;
@ -236,66 +236,21 @@ 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) {
// 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)
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::set<std::string> &Headers,
std::vector<clang::tooling::Replacement> &Replacements) {
/// Get the include fixer context for the queried symbol.
IncludeFixerContext
getIncludeFixerContext(const clang::SourceManager &SourceManager,
clang::HeaderSearch &HeaderSearch) {
IncludeFixerContext FixerContext;
if (SymbolQueryResults.empty())
return false;
return FixerContext;
// FIXME: Rank the results and pick the best one instead of the first one.
const auto &ToTry = SymbolQueryResults.front();
Headers.insert(minimizeInclude(ToTry, SourceManager, HeaderSearch));
FixerContext.SymbolIdentifer = QuerySymbol;
FixerContext.FirstIncludeOffset = FirstIncludeOffset;
for (const auto &Header : SymbolQueryResults)
FixerContext.Headers.push_back(
minimizeInclude(Header, SourceManager, HeaderSearch));
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
// changing the results.
return true;
return FixerContext;
}
/// Sets the location at the very top of the file.
@ -314,6 +269,7 @@ private:
DEBUG(Loc.print(llvm::dbgs(), getCompilerInstance().getSourceManager()));
DEBUG(llvm::dbgs() << " ...");
QuerySymbol = Query.str();
SymbolQueryResults = SymbolIndexMgr.search(Query);
DEBUG(llvm::dbgs() << SymbolQueryResults.size() << " replies\n");
return !SymbolQueryResults.empty();
@ -336,6 +292,9 @@ private:
/// clang-format config file found.
std::string FallbackStyle;
/// The symbol being queried.
std::string QuerySymbol;
/// The query results of an identifier. We only include the first discovered
/// identifier to avoid getting caught in results from error recovery.
std::vector<std::string> SymbolQueryResults;
@ -385,12 +344,10 @@ void PreprocessorHooks::InclusionDirective(
} // namespace
IncludeFixerActionFactory::IncludeFixerActionFactory(
SymbolIndexManager &SymbolIndexMgr, std::set<std::string> &Headers,
std::vector<clang::tooling::Replacement> &Replacements, StringRef StyleName,
bool MinimizeIncludePaths)
: SymbolIndexMgr(SymbolIndexMgr), Headers(Headers),
Replacements(Replacements), MinimizeIncludePaths(MinimizeIncludePaths),
FallbackStyle(StyleName) {}
SymbolIndexManager &SymbolIndexMgr, IncludeFixerContext &Context,
StringRef StyleName, bool MinimizeIncludePaths)
: SymbolIndexMgr(SymbolIndexMgr), Context(Context),
MinimizeIncludePaths(MinimizeIncludePaths), FallbackStyle(StyleName) {}
IncludeFixerActionFactory::~IncludeFixerActionFactory() = default;
@ -420,10 +377,9 @@ bool IncludeFixerActionFactory::runInvocation(
SymbolIndexMgr, FallbackStyle, MinimizeIncludePaths);
Compiler.ExecuteAction(*ScopedToolAction);
// Generate replacements.
ScopedToolAction->Rewrite(Compiler.getSourceManager(),
Compiler.getPreprocessor().getHeaderSearchInfo(),
Headers, Replacements);
Context = ScopedToolAction->getIncludeFixerContext(
Compiler.getSourceManager(),
Compiler.getPreprocessor().getHeaderSearchInfo());
// 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
@ -431,5 +387,41 @@ bool IncludeFixerActionFactory::runInvocation(
return !Compiler.getDiagnostics().hasFatalErrorOccurred();
}
std::vector<clang::tooling::Replacement>
createInsertHeaderReplacements(StringRef Code, StringRef FilePath,
StringRef Header, unsigned FirstIncludeOffset,
const clang::format::FormatStyle &Style) {
if (Header.empty())
return {};
// 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(FilePath, FirstIncludeOffset, 0, "\n"));
}
// Keep inserting new headers before the first header.
std::string Text = "#include " + Header.str() + "\n";
Insertions.insert(
clang::tooling::Replacement(FilePath, FirstIncludeOffset, 0, Text));
DEBUG({
llvm::dbgs() << "Header insertions:\n";
for (const auto &R : Insertions)
llvm::dbgs() << R.toString() << '\n';
});
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;
}
} // namespace include_fixer
} // namespace clang

View File

@ -10,7 +10,9 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXER_H
#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXER_H
#include "IncludeFixerContext.h"
#include "SymbolIndexManager.h"
#include "clang/Format/Format.h"
#include "clang/Tooling/Core/Replacement.h"
#include "clang/Tooling/Tooling.h"
#include <memory>
@ -28,13 +30,12 @@ namespace include_fixer {
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 Context A context for the symbol being queried.
/// \param StyleName Fallback style for reformatting.
/// \param MinimizeIncludePaths whether inserted include paths are optimized.
IncludeFixerActionFactory(
SymbolIndexManager &SymbolIndexMgr, std::set<std::string> &Headers,
std::vector<clang::tooling::Replacement> &Replacements,
StringRef StyleName, bool MinimizeIncludePaths = true);
IncludeFixerActionFactory(SymbolIndexManager &SymbolIndexMgr,
IncludeFixerContext &Context, StringRef StyleName,
bool MinimizeIncludePaths = true);
~IncludeFixerActionFactory() override;
@ -48,11 +49,8 @@ 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;
/// The context that contains all information about the symbol being queried.
IncludeFixerContext &Context;
/// Whether inserted include paths should be optimized.
bool MinimizeIncludePaths;
@ -62,6 +60,25 @@ private:
std::string FallbackStyle;
};
/// Create replacements for the header being inserted. The replacements will
/// insert a header before the first #include in \p Code, and sort all headers
/// with the given clang-format style.
///
/// \param Code The source code.
/// \param FilePath The source file path.
/// \param Header The header being inserted.
/// \param FirstIncludeOffset The insertion point for new include directives.
/// The default value -1U means inserting the header at the first line, and if
/// there is no #include block, it will create a header block by inserting a
/// newline.
/// \param Style clang-format style being used.
///
/// \return Replacements for inserting and sorting headers.
std::vector<clang::tooling::Replacement> createInsertHeaderReplacements(
StringRef Code, StringRef FilePath, StringRef Header,
unsigned FirstIncludeOffset = -1U,
const clang::format::FormatStyle &Style = clang::format::getLLVMStyle());
} // namespace include_fixer
} // namespace clang

View File

@ -0,0 +1,32 @@
//===-- IncludeFixerContext.h - Include fixer context -----------*- 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_INCLUDEFIXERCONTEXT_H
#define LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXERCONTEXT_H
#include <string>
#include <vector>
namespace clang {
namespace include_fixer {
/// \brief A context for the symbol being queried.
struct IncludeFixerContext {
/// \brief The symbol name.
std::string SymbolIdentifer;
/// \brief The headers which have SymbolIdentifier definitions.
std::vector<std::string> Headers;
/// \brief The insertion point for new include header.
unsigned FirstIncludeOffset;
};
} // namespace include_fixer
} // namespace clang
#endif // LLVM_CLANG_TOOLS_EXTRA_INCLUDE_FIXER_INCLUDEFIXERCONTEXT_H

View File

@ -9,8 +9,10 @@
#include "InMemorySymbolIndex.h"
#include "IncludeFixer.h"
#include "IncludeFixerContext.h"
#include "SymbolIndexManager.h"
#include "YamlSymbolIndex.h"
#include "clang/Format/Format.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Tooling/CommonOptionsParser.h"
@ -56,6 +58,23 @@ cl::opt<bool>
"used for editor integration."),
cl::init(false), cl::cat(IncludeFixerCategory));
cl::opt<bool> OutputHeaders(
"output-headers",
cl::desc("Output the symbol being quired and all its relevant headers.\n"
"The first line is the symbol name; The other lines\n"
"are the headers: \n"
" b::foo\n"
" path/to/foo_a.h\n"
" path/to/foo_b.h\n"),
cl::init(false), cl::cat(IncludeFixerCategory));
cl::opt<std::string> InsertHeader(
"insert-header",
cl::desc("Insert a specific header. This should run with STDIN mode.\n"
"The result is written to stdout. It is currently used for\n"
"editor integration."),
cl::init(""), cl::cat(IncludeFixerCategory));
cl::opt<std::string>
Style("style",
cl::desc("Fallback style for reformatting after inserting new "
@ -87,6 +106,27 @@ int includeFixerMain(int argc, const char **argv) {
tool.mapVirtualFile(options.getSourcePathList().front(), Code->getBuffer());
}
StringRef FilePath = options.getSourcePathList().front();
format::FormatStyle InsertStyle = format::getStyle("file", FilePath, Style);
if (!InsertHeader.empty()) {
if (!STDINMode) {
errs() << "Should be running in STDIN mode\n";
return 1;
}
// FIXME: Insert the header in right FirstIncludeOffset.
std::vector<tooling::Replacement> Replacements =
clang::include_fixer::createInsertHeaderReplacements(
Code->getBuffer(), FilePath, InsertHeader,
/*FirstIncludeOffset=*/0, InsertStyle);
tooling::Replacements Replaces(Replacements.begin(), Replacements.end());
std::string ChangedCode =
tooling::applyAllReplacements(Code->getBuffer(), Replaces);
llvm::outs() << ChangedCode;
return 0;
}
// Set up data source.
auto SymbolIndexMgr = llvm::make_unique<include_fixer::SymbolIndexManager>();
switch (DatabaseFormat) {
@ -139,10 +179,9 @@ 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, Headers, Replacements, Style, MinimizeIncludePaths);
include_fixer::IncludeFixerContext Context;
include_fixer::IncludeFixerActionFactory Factory(*SymbolIndexMgr, Context,
Style, MinimizeIncludePaths);
if (tool.run(&Factory) != 0) {
llvm::errs()
@ -150,9 +189,31 @@ int includeFixerMain(int argc, const char **argv) {
return 1;
}
if (OutputHeaders) {
// FIXME: Output IncludeFixerContext as YAML.
llvm::outs() << Context.SymbolIdentifer << "\n";
for (const auto &Header : Context.Headers)
llvm::outs() << Header << "\n";
return 0;
}
if (Context.Headers.empty())
return 0;
auto Buffer = llvm::MemoryBuffer::getFile(FilePath);
if (!Buffer) {
errs() << "Couldn't open file: " << FilePath;
return 1;
}
// FIXME: Rank the results and pick the best one instead of the first one.
std::vector<tooling::Replacement> Replacements =
clang::include_fixer::createInsertHeaderReplacements(
/*Code=*/Buffer.get()->getBuffer(), FilePath, Context.Headers.front(),
Context.FirstIncludeOffset, InsertStyle);
if (!Quiet)
for (const auto &Header : Headers)
llvm::errs() << "Added #include " << Header;
llvm::errs() << "Added #include" << Context.Headers.front();
// Set up a new source manager for applying the resulting replacements.
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions);

View File

@ -18,7 +18,6 @@
import argparse
import difflib
import subprocess
import sys
import vim
# set g:clang_include_fixer_path to the path to clang-include-fixer if it is not
@ -28,6 +27,39 @@ binary = 'clang-include-fixer'
if vim.eval('exists("g:clang_include_fixer_path")') == "1":
binary = vim.eval('g:clang_include_fixer_path')
maximum_suggested_headers=3
if vim.eval('exists("g:clang_include_fixer_maximum_suggested_headers")') == "1":
maximum_suggested_headers = max(
1,
vim.eval('g:clang_include_fixer_maximum_suggested_headers'))
def ShowDialog(message, choices, default_choice_index=0):
to_eval = "confirm('{0}', '{1}', '{2}')".format(message,
choices,
default_choice_index)
return int(vim.eval(to_eval));
def execute(command, text):
p = subprocess.Popen(command,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
return p.communicate(input=text)
def InsertHeaderToVimBuffer(header, text):
command = [binary, "-stdin", "-insert-header="+header,
vim.current.buffer.name]
stdout, stderr = execute(command, text)
if stdout:
lines = stdout.splitlines()
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]]
def main():
parser = argparse.ArgumentParser(
description='Vim integration for clang-include-fixer')
@ -41,24 +73,36 @@ def main():
buf = vim.current.buffer
text = '\n'.join(buf)
# Call clang-include-fixer.
command = [binary, "-stdin", "-db="+args.db, "-input="+args.input,
# Run command to get all headers.
command = [binary, "-stdin", "-output-headers", "-db="+args.db, "-input="+args.input, "-debug",
vim.current.buffer.name]
p = subprocess.Popen(command,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
stdout, stderr = p.communicate(input=text)
stdout, stderr = execute(command, text)
lines = stdout.splitlines()
if len(lines) < 2:
print "No header is included.\n"
return
# If successful, replace buffer contents.
if stderr:
print stderr
# The first line is the symbol name.
symbol = lines[0]
# If there is only one suggested header, insert it directly.
if len(lines) == 2 or maximum_suggested_headers == 1:
InsertHeaderToVimBuffer(lines[1], text)
print "Added #include {0} for {1}.\n".format(lines[1], symbol)
return
choices_message = ""
index = 1;
for header in lines[1:1+maximum_suggested_headers]:
choices_message += "&" + str(index) + header + "\n"
index += 1
select = ShowDialog("choose a header file for {0}.".format(symbol),
choices_message)
# Insert a selected header.
InsertHeaderToVimBuffer(lines[select], text)
print "Added #include {0} for {1}.\n".format(lines[select], symbol)
return;
if stdout:
lines = stdout.splitlines()
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()

View File

@ -0,0 +1,12 @@
// REQUIRES: shell
// RUN: sed -e 's#//.*$##' %s > %t.cpp
// RUN: clang-include-fixer -db=fixed -input='foo= "foo.h","bar.h"' -output-headers %t.cpp -- | FileCheck %s -check-prefix=CHECK-HEADERS
// RUN: cat %t.cpp | clang-include-fixer -stdin -insert-header='"foo.h"' %t.cpp | FileCheck %s -check-prefix=CHECK
//
// CHECK-HEADERS: "foo.h"
// CHECK-HEADERS: "bar.h"
//
// CHECK: #include "foo.h"
// CHECK: foo f;
foo f;

View File

@ -70,11 +70,16 @@ 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, Headers, Replacements,
"llvm");
IncludeFixerContext FixerContext;
IncludeFixerActionFactory Factory(*SymbolIndexMgr, FixerContext, "llvm");
runOnCode(&Factory, Code, "input.cc", ExtraArgs);
std::vector<clang::tooling::Replacement> Replacements;
if (!FixerContext.Headers.empty()) {
Replacements = clang::include_fixer::createInsertHeaderReplacements(
Code, "input.cc", FixerContext.Headers.front(),
FixerContext.FirstIncludeOffset);
}
clang::RewriterTestContext Context;
clang::FileID ID = Context.createInMemoryFile("input.cc", Code);
clang::tooling::applyAllReplacements(Replacements, Context.Rewrite);