summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra/clangd/Headers.cpp
blob: c6f18fa39339796c48b4283e804695132df0b787 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//===--- Headers.cpp - Include headers ---------------------------*- C++-*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "Headers.h"
#include "Compiler.h"
#include "Logger.h"
#include "SourceCode.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Lex/HeaderSearch.h"
#include "llvm/Support/Path.h"

namespace clang {
namespace clangd {
namespace {

class RecordHeaders : public PPCallbacks {
public:
  RecordHeaders(const SourceManager &SM,
                std::function<void(Inclusion)> Callback)
      : SM(SM), Callback(std::move(Callback)) {}

  // Record existing #includes - both written and resolved paths. Only #includes
  // in the main file are collected.
  void InclusionDirective(SourceLocation HashLoc, const Token & /*IncludeTok*/,
                          llvm::StringRef FileName, bool IsAngled,
                          CharSourceRange FilenameRange, const FileEntry *File,
                          llvm::StringRef /*SearchPath*/,
                          llvm::StringRef /*RelativePath*/,
                          const Module * /*Imported*/,
                          SrcMgr::CharacteristicKind /*FileType*/) override {
    // Only inclusion directives in the main file make sense. The user cannot
    // select directives not in the main file.
    if (HashLoc.isInvalid() || !SM.isInMainFile(HashLoc))
      return;
    std::string Written =
        (IsAngled ? "<" + FileName + ">" : "\"" + FileName + "\"").str();
    std::string Resolved = (!File || File->tryGetRealPathName().empty())
                               ? ""
                               : File->tryGetRealPathName();
    Callback({halfOpenToRange(SM, FilenameRange), Written, Resolved});
  }

private:
  const SourceManager &SM;
  std::function<void(Inclusion)> Callback;
};

} // namespace

bool isLiteralInclude(llvm::StringRef Include) {
  return Include.startswith("<") || Include.startswith("\"");
}

bool HeaderFile::valid() const {
  return (Verbatim && isLiteralInclude(File)) ||
         (!Verbatim && llvm::sys::path::is_absolute(File));
}

std::unique_ptr<PPCallbacks>
collectInclusionsInMainFileCallback(const SourceManager &SM,
                                    std::function<void(Inclusion)> Callback) {
  return llvm::make_unique<RecordHeaders>(SM, std::move(Callback));
}

/// FIXME(ioeric): we might not want to insert an absolute include path if the
/// path is not shortened.
llvm::Expected<std::string> calculateIncludePath(
    PathRef File, StringRef BuildDir, HeaderSearch &HeaderSearchInfo,
    const std::vector<Inclusion> &Inclusions, const HeaderFile &DeclaringHeader,
    const HeaderFile &InsertedHeader) {
  assert(DeclaringHeader.valid() && InsertedHeader.valid());
  if (File == DeclaringHeader.File || File == InsertedHeader.File)
    return "";
  llvm::StringSet<> IncludedHeaders;
  for (const auto &Inc : Inclusions) {
    IncludedHeaders.insert(Inc.Written);
    if (!Inc.Resolved.empty())
      IncludedHeaders.insert(Inc.Resolved);
  }
  auto Included = [&](llvm::StringRef Header) {
    return IncludedHeaders.find(Header) != IncludedHeaders.end();
  };
  if (Included(DeclaringHeader.File) || Included(InsertedHeader.File))
    return "";

  bool IsSystem = false;

  if (InsertedHeader.Verbatim)
    return InsertedHeader.File;

  std::string Suggested = HeaderSearchInfo.suggestPathToFileForDiagnostics(
      InsertedHeader.File, BuildDir, &IsSystem);
  if (IsSystem)
    Suggested = "<" + Suggested + ">";
  else
    Suggested = "\"" + Suggested + "\"";

  log("Suggested #include for " + InsertedHeader.File + " is: " + Suggested);
  return Suggested;
}

Expected<Optional<TextEdit>>
IncludeInserter::insert(const HeaderFile &DeclaringHeader,
                        const HeaderFile &InsertedHeader) const {
  auto Validate = [](const HeaderFile &Header) {
    return Header.valid()
               ? llvm::Error::success()
               : llvm::make_error<llvm::StringError>(
                     "Invalid HeaderFile: " + Header.File +
                         " (verbatim=" + std::to_string(Header.Verbatim) + ").",
                     llvm::inconvertibleErrorCode());
  };
  if (auto Err = Validate(DeclaringHeader))
    return std::move(Err);
  if (auto Err = Validate(InsertedHeader))
    return std::move(Err);
  auto Include =
      calculateIncludePath(FileName, BuildDir, HeaderSearchInfo, Inclusions,
                           DeclaringHeader, InsertedHeader);
  if (!Include)
    return Include.takeError();
  if (Include->empty())
    return llvm::None;
  StringRef IncludeRef = *Include;
  auto Insertion =
      Inserter.insert(IncludeRef.trim("\"<>"), IncludeRef.startswith("<"));
  if (!Insertion)
    return llvm::None;
  return replacementToEdit(Code, *Insertion);
}

} // namespace clangd
} // namespace clang
OpenPOWER on IntegriCloud