Index: include-fixer/IncludeFixer.cpp =================================================================== --- include-fixer/IncludeFixer.cpp +++ include-fixer/IncludeFixer.cpp @@ -37,7 +37,6 @@ std::unique_ptr CreateASTConsumer(clang::CompilerInstance &Compiler, StringRef InFile) override { - Filename = InFile; return llvm::make_unique(); } @@ -86,29 +85,6 @@ if (getCompilerInstance().getSema().isSFINAEContext()) return clang::TypoCorrection(); - // We currently ignore the unidentified symbol which is not from the - // main file. - // - // However, this is not always true due to templates in a non-self contained - // header, consider the case: - // - // // header.h - // template - // class Foo { - // T t; - // }; - // - // // test.cc - // // We need to add in test.cc instead of header.h. - // class Bar; - // Foo foo; - // - // FIXME: Add the missing header to the header file where the symbol comes - // from. - if (!getCompilerInstance().getSourceManager().isWrittenInMainFile( - Typo.getLoc())) - return clang::TypoCorrection(); - std::string TypoScopeString; if (S) { // FIXME: Currently we only use namespace contexts. Use other context @@ -185,8 +161,6 @@ return clang::TypoCorrection(); } - StringRef filename() const { return Filename; } - /// Get the minimal include for a given path. std::string minimizeInclude(StringRef Include, const clang::SourceManager &SourceManager, @@ -217,6 +191,9 @@ clang::HeaderSearch &HeaderSearch) { IncludeFixerContext FixerContext; FixerContext.SymbolIdentifier = QuerySymbol; + SmallString<256> CleanedFilePath = StringRef(Filename); + llvm::sys::path::remove_dots(CleanedFilePath, /*remove_dot_dot=*/true); + FixerContext.FilePath = CleanedFilePath.str(); for (const auto &Header : SymbolQueryResults) FixerContext.Headers.push_back( minimizeInclude(Header, SourceManager, HeaderSearch)); @@ -238,6 +215,7 @@ DEBUG(llvm::dbgs() << " ..."); QuerySymbol = Query.str(); + Filename = getCompilerInstance().getSourceManager().getFilename(Loc); SymbolQueryResults = SymbolIndexMgr.search(Query); DEBUG(llvm::dbgs() << SymbolQueryResults.size() << " replies\n"); return !SymbolQueryResults.empty(); @@ -246,7 +224,7 @@ /// The client to use to find cross-references. SymbolIndexManager &SymbolIndexMgr; - /// The absolute path to the file being processed. + /// The absolute path to a file where the QuerySymbol comes from. std::string Filename; /// The symbol being queried. Index: include-fixer/IncludeFixerContext.h =================================================================== --- include-fixer/IncludeFixerContext.h +++ include-fixer/IncludeFixerContext.h @@ -20,6 +20,8 @@ struct IncludeFixerContext { /// \brief The symbol name. std::string SymbolIdentifier; + /// \bridf The absolute path to a file where the SymbolIdentifier comes from. + std::string FilePath; /// \brief The headers which have SymbolIdentifier definitions. std::vector Headers; }; Index: include-fixer/tool/ClangIncludeFixer.cpp =================================================================== --- include-fixer/tool/ClangIncludeFixer.cpp +++ include-fixer/tool/ClangIncludeFixer.cpp @@ -33,6 +33,7 @@ static void mapping(IO &io, IncludeFixerContext &Context) { io.mapRequired("SymbolIdentifier", Context.SymbolIdentifier); io.mapRequired("Headers", Context.Headers); + io.mapRequired("FilePath", Context.FilePath); } }; } // namespace yaml @@ -80,6 +81,7 @@ "JSON format to stdout:\n" " {\n" " \"SymbolIdentifier\": \"foo\",\n" + " \"FilePath\": \"path/to/main.cc\", \n" " \"Headers\": [\"\\\"foo_a.h\\\"\"]\n" " }"), cl::init(false), cl::cat(IncludeFixerCategory)); @@ -90,6 +92,7 @@ "The result is written to stdout. It is currently used for\n" "editor integration. Support YAML/JSON format:\n" " -insert-header=\"{SymbolIdentifier: foo,\n" + " FilePath: path/to/main.cc,\n" " Headers: ['\\\"foo_a.h\\\"']}\""), cl::init(""), cl::cat(IncludeFixerCategory)); @@ -155,6 +158,7 @@ void writeToJson(llvm::raw_ostream &OS, const IncludeFixerContext& Context) { OS << "{\n" " \"SymbolIdentifier\": \"" << Context.SymbolIdentifier << "\",\n" + " \"FilePath\": \"" << Context.FilePath << "\",\n" " \"Headers\": [ "; for (const auto &Header : Context.Headers) { OS << " \"" << llvm::yaml::escape(Header) << "\""; @@ -190,8 +194,8 @@ tool.mapVirtualFile(options.getSourcePathList().front(), Code->getBuffer()); } - StringRef FilePath = options.getSourcePathList().front(); - format::FormatStyle InsertStyle = format::getStyle("file", FilePath, Style); + format::FormatStyle InsertStyle = + format::getStyle("file", options.getSourcePathList().front(), Style); if (!InsertHeader.empty()) { if (!STDINMode) { @@ -210,7 +214,8 @@ tooling::Replacements Replacements = clang::include_fixer::createInsertHeaderReplacements( - Code->getBuffer(), FilePath, Context.Headers[0], InsertStyle); + Code->getBuffer(), Context.FilePath, Context.Headers[0], + InsertStyle); tooling::Replacements Replaces(Replacements.begin(), Replacements.end()); std::string ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces); @@ -243,17 +248,16 @@ if (Context.Headers.empty()) return 0; - auto Buffer = llvm::MemoryBuffer::getFile(FilePath); + auto Buffer = llvm::MemoryBuffer::getFile(Context.FilePath); if (!Buffer) { - errs() << "Couldn't open file: " << FilePath; + errs() << "Couldn't open file: " << Context.FilePath; return 1; } - // FIXME: Rank the results and pick the best one instead of the first one. tooling::Replacements Replacements = clang::include_fixer::createInsertHeaderReplacements( - /*Code=*/Buffer.get()->getBuffer(), FilePath, Context.Headers.front(), - InsertStyle); + /*Code=*/Buffer.get()->getBuffer(), Context.FilePath, + Context.Headers.front(), InsertStyle); if (!Quiet) llvm::errs() << "Added #include" << Context.Headers.front(); Index: include-fixer/tool/clang-include-fixer.py =================================================================== --- include-fixer/tool/clang-include-fixer.py +++ include-fixer/tool/clang-include-fixer.py @@ -49,11 +49,19 @@ return p.communicate(input=text) -def InsertHeaderToVimBuffer(header, text): +def InsertHeaderToVimBuffer(header, code): command = [binary, "-stdin", "-insert-header="+json.dumps(header), vim.current.buffer.name] - stdout, stderr = execute(command, text) + stdout, stderr = execute(command, code) + if stdout: + # If the file being inserted is not the current one in vim buffer, just + # overwrite it in filesystem. + if header["FilePath"] != vim.current.buffer.name: + with open(header["FilePath"], "w") as f: + f.write(stdout) + return + lines = stdout.splitlines() sequence = difflib.SequenceMatcher(None, vim.current.buffer, lines) for op in reversed(sequence.get_opcodes()): @@ -72,12 +80,12 @@ # Get the current text. buf = vim.current.buffer - text = '\n'.join(buf) + code = '\n'.join(buf) # Run command to get all headers. command = [binary, "-stdin", "-output-headers", "-db="+args.db, "-input="+args.input, vim.current.buffer.name] - stdout, stderr = execute(command, text) + stdout, stderr = execute(command, code) if stderr: print >> sys.stderr, "Error while running clang-include-fixer: " + stderr return @@ -85,6 +93,11 @@ include_fixer_context = json.loads(stdout) symbol = include_fixer_context["SymbolIdentifier"] headers = include_fixer_context["Headers"] + file_path = include_fixer_context["FilePath"] + + if file_path != vim.current.buffer.name: + with open(file_path, "r") as f: + code = f.read(); if not symbol: print "The file is fine, no need to add a header.\n" @@ -98,8 +111,11 @@ # If there is only one suggested header, insert it directly. if len(headers) == 1 or maximum_suggested_headers == 1: InsertHeaderToVimBuffer({"SymbolIdentifier": symbol, - "Headers":[headers[0]]}, text) - print "Added #include {0} for {1}.\n".format(headers[0], symbol) + "Headers": [headers[0]], + "FilePath": file_path}, code) + print "Added #include {0} for {1} in {2}.\n".format(headers[0], + symbol, + file_path) return choices_message = "" @@ -112,8 +128,11 @@ choices_message) # Insert a selected header. InsertHeaderToVimBuffer({"SymbolIdentifier": symbol, - "Headers":[headers[select-1]]}, text) - print "Added #include {0} for {1}.\n".format(headers[select-1], symbol) + "Headers": [headers[select-1]], + "FilePath": file_path}, code) + print "Added #include {0} for {1} in {2}.\n".format(headers[select-1], + symbol, + file_path) return; Index: test/include-fixer/commandline_options.cpp =================================================================== --- test/include-fixer/commandline_options.cpp +++ test/include-fixer/commandline_options.cpp @@ -1,7 +1,7 @@ // 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='{SymbolIdentifier: foo, Headers: ["\"foo.h\""]}' %t.cpp | FileCheck %s -check-prefix=CHECK +// RUN: cat %t.cpp | clang-include-fixer -stdin -insert-header='{SymbolIdentifier: foo, FilePath: %t.cpp, Headers: ["\"foo.h\""]}' %t.cpp | FileCheck %s -check-prefix=CHECK // // CHECK-HEADERS: "Headers": [ "\"foo.h\"", "\"bar.h\"" ] // Index: test/include-fixer/insert_header_in_header.cpp =================================================================== --- /dev/null +++ test/include-fixer/insert_header_in_header.cpp @@ -0,0 +1,10 @@ +// REQUIRES: shell +// RUN: echo 'foo f;' > %T/main.h +// RUN: sed -e 's#//.*$##' %s > %t.cpp +// RUN: clang-include-fixer -db=yaml -input=%p/Inputs/fake_yaml_db.yaml %t.cpp -- +// RUN: FileCheck %s -input-file=%T/main.h + +// CHECK: #include "foo.h" +// CHECK: foo f; + +#include "main.h" Index: unittests/include-fixer/IncludeFixerTest.cpp =================================================================== --- unittests/include-fixer/IncludeFixerTest.cpp +++ unittests/include-fixer/IncludeFixerTest.cpp @@ -44,8 +44,6 @@ llvm::MemoryBuffer::getMemBuffer("\n")); InMemoryFileSystem->addFile("dir/otherdir/qux.h", 0, llvm::MemoryBuffer::getMemBuffer("\n")); - InMemoryFileSystem->addFile("header.h", 0, - llvm::MemoryBuffer::getMemBuffer("bar b;")); return Invocation.run(); } @@ -188,11 +186,6 @@ runIncludeFixer("int test = a::b::Green;\n")); } -TEST(IncludeFixer, IgnoreSymbolFromHeader) { - std::string Code = "#include \"header.h\""; - EXPECT_EQ(Code, runIncludeFixer(Code)); -} - // FIXME: add test cases for inserting and sorting multiple headers when // include-fixer supports multiple headers insertion. TEST(IncludeFixer, InsertAndSortSingleHeader) {