brintos

brintos / llvm-project-archived public Read only

0
0
Text · 47.2 KiB · 21c078f Raw
1297 lines · cpp
1//===--- SourceCode.h - Manipulating source code as strings -----*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8#include "SourceCode.h"9 10#include "FuzzyMatch.h"11#include "Preamble.h"12#include "Protocol.h"13#include "support/Context.h"14#include "support/Logger.h"15#include "clang/Basic/FileEntry.h"16#include "clang/Basic/LangOptions.h"17#include "clang/Basic/SourceLocation.h"18#include "clang/Basic/SourceManager.h"19#include "clang/Basic/TokenKinds.h"20#include "clang/Driver/Types.h"21#include "clang/Format/Format.h"22#include "clang/Lex/Lexer.h"23#include "clang/Lex/Preprocessor.h"24#include "clang/Lex/Token.h"25#include "clang/Tooling/Core/Replacement.h"26#include "clang/Tooling/Syntax/Tokens.h"27#include "llvm/ADT/ArrayRef.h"28#include "llvm/ADT/BitVector.h"29#include "llvm/ADT/STLExtras.h"30#include "llvm/ADT/StringExtras.h"31#include "llvm/ADT/StringMap.h"32#include "llvm/ADT/StringRef.h"33#include "llvm/Support/Compiler.h"34#include "llvm/Support/Errc.h"35#include "llvm/Support/Error.h"36#include "llvm/Support/ErrorHandling.h"37#include "llvm/Support/LineIterator.h"38#include "llvm/Support/MemoryBuffer.h"39#include "llvm/Support/Path.h"40#include "llvm/Support/VirtualFileSystem.h"41#include "llvm/Support/xxhash.h"42#include <algorithm>43#include <cstddef>44#include <optional>45#include <string>46#include <vector>47 48namespace clang {49namespace clangd {50 51// Here be dragons. LSP positions use columns measured in *UTF-16 code units*!52// Clangd uses UTF-8 and byte-offsets internally, so conversion is nontrivial.53 54// Iterates over unicode codepoints in the (UTF-8) string. For each,55// invokes CB(UTF-8 length, UTF-16 length), and breaks if it returns true.56// Returns true if CB returned true, false if we hit the end of string.57//58// If the string is not valid UTF-8, we log this error and "decode" the59// text in some arbitrary way. This is pretty sad, but this tends to happen deep60// within indexing of headers where clang misdetected the encoding, and61// propagating the error all the way back up is (probably?) not be worth it.62template <typename Callback>63static bool iterateCodepoints(llvm::StringRef U8, const Callback &CB) {64  bool LoggedInvalid = false;65  // A codepoint takes two UTF-16 code unit if it's astral (outside BMP).66  // Astral codepoints are encoded as 4 bytes in UTF-8, starting with 11110xxx.67  for (size_t I = 0; I < U8.size();) {68    unsigned char C = static_cast<unsigned char>(U8[I]);69    if (LLVM_LIKELY(!(C & 0x80))) { // ASCII character.70      if (CB(1, 1))71        return true;72      ++I;73      continue;74    }75    // This convenient property of UTF-8 holds for all non-ASCII characters.76    size_t UTF8Length = llvm::countl_one(C);77    // 0xxx is ASCII, handled above. 10xxx is a trailing byte, invalid here.78    // 11111xxx is not valid UTF-8 at all, maybe some ISO-8859-*.79    if (LLVM_UNLIKELY(UTF8Length < 2 || UTF8Length > 4)) {80      if (!LoggedInvalid) {81        elog("File has invalid UTF-8 near offset {0}: {1}", I, llvm::toHex(U8));82        LoggedInvalid = true;83      }84      // We can't give a correct result, but avoid returning something wild.85      // Pretend this is a valid ASCII byte, for lack of better options.86      // (Too late to get ISO-8859-* right, we've skipped some bytes already).87      if (CB(1, 1))88        return true;89      ++I;90      continue;91    }92    I += UTF8Length; // Skip over all trailing bytes.93    // A codepoint takes two UTF-16 code unit if it's astral (outside BMP).94    // Astral codepoints are encoded as 4 bytes in UTF-8 (11110xxx ...)95    if (CB(UTF8Length, UTF8Length == 4 ? 2 : 1))96      return true;97  }98  return false;99}100 101// Returns the byte offset into the string that is an offset of \p Units in102// the specified encoding.103// Conceptually, this converts to the encoding, truncates to CodeUnits,104// converts back to UTF-8, and returns the length in bytes.105static size_t measureUnits(llvm::StringRef U8, int Units, OffsetEncoding Enc,106                           bool &Valid) {107  Valid = Units >= 0;108  if (Units <= 0)109    return 0;110  size_t Result = 0;111  switch (Enc) {112  case OffsetEncoding::UTF8:113    Result = Units;114    break;115  case OffsetEncoding::UTF16:116    Valid = iterateCodepoints(U8, [&](int U8Len, int U16Len) {117      Result += U8Len;118      Units -= U16Len;119      return Units <= 0;120    });121    if (Units < 0) // Offset in the middle of a surrogate pair.122      Valid = false;123    break;124  case OffsetEncoding::UTF32:125    Valid = iterateCodepoints(U8, [&](int U8Len, int U16Len) {126      Result += U8Len;127      Units--;128      return Units <= 0;129    });130    break;131  case OffsetEncoding::UnsupportedEncoding:132    llvm_unreachable("unsupported encoding");133  }134  // Don't return an out-of-range index if we overran.135  if (Result > U8.size()) {136    Valid = false;137    return U8.size();138  }139  return Result;140}141 142Key<OffsetEncoding> kCurrentOffsetEncoding;143static OffsetEncoding lspEncoding() {144  auto *Enc = Context::current().get(kCurrentOffsetEncoding);145  return Enc ? *Enc : OffsetEncoding::UTF16;146}147 148// Like most strings in clangd, the input is UTF-8 encoded.149size_t lspLength(llvm::StringRef Code) {150  size_t Count = 0;151  switch (lspEncoding()) {152  case OffsetEncoding::UTF8:153    Count = Code.size();154    break;155  case OffsetEncoding::UTF16:156    iterateCodepoints(Code, [&](int U8Len, int U16Len) {157      Count += U16Len;158      return false;159    });160    break;161  case OffsetEncoding::UTF32:162    iterateCodepoints(Code, [&](int U8Len, int U16Len) {163      ++Count;164      return false;165    });166    break;167  case OffsetEncoding::UnsupportedEncoding:168    llvm_unreachable("unsupported encoding");169  }170  return Count;171}172 173llvm::Expected<size_t> positionToOffset(llvm::StringRef Code, Position P,174                                        bool AllowColumnsBeyondLineLength) {175  if (P.line < 0)176    return error(llvm::errc::invalid_argument,177                 "Line value can't be negative ({0})", P.line);178  if (P.character < 0)179    return error(llvm::errc::invalid_argument,180                 "Character value can't be negative ({0})", P.character);181  size_t StartOfLine = 0;182  for (int I = 0; I != P.line; ++I) {183    size_t NextNL = Code.find('\n', StartOfLine);184    if (NextNL == llvm::StringRef::npos)185      return error(llvm::errc::invalid_argument,186                   "Line value is out of range ({0})", P.line);187    StartOfLine = NextNL + 1;188  }189  StringRef Line =190      Code.substr(StartOfLine).take_until([](char C) { return C == '\n'; });191 192  // P.character may be in UTF-16, transcode if necessary.193  bool Valid;194  size_t ByteInLine = measureUnits(Line, P.character, lspEncoding(), Valid);195  if (!Valid && !AllowColumnsBeyondLineLength)196    return error(llvm::errc::invalid_argument,197                 "{0} offset {1} is invalid for line {2}", lspEncoding(),198                 P.character, P.line);199  return StartOfLine + ByteInLine;200}201 202Position offsetToPosition(llvm::StringRef Code, size_t Offset) {203  Offset = std::min(Code.size(), Offset);204  llvm::StringRef Before = Code.substr(0, Offset);205  int Lines = Before.count('\n');206  size_t PrevNL = Before.rfind('\n');207  size_t StartOfLine = (PrevNL == llvm::StringRef::npos) ? 0 : (PrevNL + 1);208  Position Pos;209  Pos.line = Lines;210  Pos.character = lspLength(Before.substr(StartOfLine));211  return Pos;212}213 214Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc) {215  // We use the SourceManager's line tables, but its column number is in bytes.216  FileID FID;217  unsigned Offset;218  std::tie(FID, Offset) = SM.getDecomposedSpellingLoc(Loc);219  Position P;220  P.line = static_cast<int>(SM.getLineNumber(FID, Offset)) - 1;221  bool Invalid = false;222  llvm::StringRef Code = SM.getBufferData(FID, &Invalid);223  if (!Invalid) {224    auto ColumnInBytes = SM.getColumnNumber(FID, Offset) - 1;225    auto LineSoFar = Code.substr(Offset - ColumnInBytes, ColumnInBytes);226    P.character = lspLength(LineSoFar);227  }228  return P;229}230 231bool isSpelledInSource(SourceLocation Loc, const SourceManager &SM) {232  if (Loc.isFileID())233    return true;234  auto Spelling = SM.getDecomposedSpellingLoc(Loc);235  bool InvalidSLocEntry = false;236  const auto SLocEntry = SM.getSLocEntry(Spelling.first, &InvalidSLocEntry);237  if (InvalidSLocEntry)238    return false;239  StringRef SpellingFile = SLocEntry.getFile().getName();240  if (SpellingFile == "<scratch space>")241    return false;242  if (SpellingFile == "<built-in>")243    // __STDC__ etc are considered spelled, but BAR in arg -DFOO=BAR is not.244    return !SM.isWrittenInCommandLineFile(245        SM.getComposedLoc(Spelling.first, Spelling.second));246  return true;247}248 249bool isValidFileRange(const SourceManager &Mgr, SourceRange R) {250  if (!R.getBegin().isValid() || !R.getEnd().isValid())251    return false;252 253  FileID BeginFID;254  size_t BeginOffset = 0;255  std::tie(BeginFID, BeginOffset) = Mgr.getDecomposedLoc(R.getBegin());256 257  FileID EndFID;258  size_t EndOffset = 0;259  std::tie(EndFID, EndOffset) = Mgr.getDecomposedLoc(R.getEnd());260 261  return BeginFID.isValid() && BeginFID == EndFID && BeginOffset <= EndOffset;262}263 264SourceLocation includeHashLoc(FileID IncludedFile, const SourceManager &SM) {265  assert(SM.getLocForEndOfFile(IncludedFile).isFileID());266  FileID IncludingFile;267  unsigned Offset;268  std::tie(IncludingFile, Offset) =269      SM.getDecomposedExpansionLoc(SM.getIncludeLoc(IncludedFile));270  bool Invalid = false;271  llvm::StringRef Buf = SM.getBufferData(IncludingFile, &Invalid);272  if (Invalid)273    return SourceLocation();274  // Now buf is "...\n#include <foo>\n..."275  // and Offset points here:   ^276  // Rewind to the preceding # on the line.277  assert(Offset < Buf.size());278  for (;; --Offset) {279    if (Buf[Offset] == '#')280      return SM.getComposedLoc(IncludingFile, Offset);281    if (Buf[Offset] == '\n' || Offset == 0) // no hash, what's going on?282      return SourceLocation();283  }284}285 286static unsigned getTokenLengthAtLoc(SourceLocation Loc, const SourceManager &SM,287                                    const LangOptions &LangOpts) {288  Token TheTok;289  if (Lexer::getRawToken(Loc, TheTok, SM, LangOpts))290    return 0;291  // FIXME: Here we check whether the token at the location is a greatergreater292  // (>>) token and consider it as a single greater (>). This is to get it293  // working for templates but it isn't correct for the right shift operator. We294  // can avoid this by using half open char ranges in getFileRange() but getting295  // token ending is not well supported in macroIDs.296  if (TheTok.is(tok::greatergreater))297    return 1;298  return TheTok.getLength();299}300 301// Returns location of the last character of the token at a given loc302static SourceLocation getLocForTokenEnd(SourceLocation BeginLoc,303                                        const SourceManager &SM,304                                        const LangOptions &LangOpts) {305  unsigned Len = getTokenLengthAtLoc(BeginLoc, SM, LangOpts);306  return BeginLoc.getLocWithOffset(Len ? Len - 1 : 0);307}308 309// Returns location of the starting of the token at a given EndLoc310static SourceLocation getLocForTokenBegin(SourceLocation EndLoc,311                                          const SourceManager &SM,312                                          const LangOptions &LangOpts) {313  return EndLoc.getLocWithOffset(314      -(signed)getTokenLengthAtLoc(EndLoc, SM, LangOpts));315}316 317// Converts a char source range to a token range.318static SourceRange toTokenRange(CharSourceRange Range, const SourceManager &SM,319                                const LangOptions &LangOpts) {320  if (!Range.isTokenRange())321    Range.setEnd(getLocForTokenBegin(Range.getEnd(), SM, LangOpts));322  return Range.getAsRange();323}324// Returns the union of two token ranges.325// To find the maximum of the Ends of the ranges, we compare the location of the326// last character of the token.327static SourceRange unionTokenRange(SourceRange R1, SourceRange R2,328                                   const SourceManager &SM,329                                   const LangOptions &LangOpts) {330  SourceLocation Begin =331      SM.isBeforeInTranslationUnit(R1.getBegin(), R2.getBegin())332          ? R1.getBegin()333          : R2.getBegin();334  SourceLocation End =335      SM.isBeforeInTranslationUnit(getLocForTokenEnd(R1.getEnd(), SM, LangOpts),336                                   getLocForTokenEnd(R2.getEnd(), SM, LangOpts))337          ? R2.getEnd()338          : R1.getEnd();339  return SourceRange(Begin, End);340}341 342// Given a range whose endpoints may be in different expansions or files,343// tries to find a range within a common file by following up the expansion and344// include location in each.345static SourceRange rangeInCommonFile(SourceRange R, const SourceManager &SM,346                                     const LangOptions &LangOpts) {347  // Fast path for most common cases.348  if (SM.isWrittenInSameFile(R.getBegin(), R.getEnd()))349    return R;350  // Record the stack of expansion locations for the beginning, keyed by FileID.351  llvm::DenseMap<FileID, SourceLocation> BeginExpansions;352  for (SourceLocation Begin = R.getBegin(); Begin.isValid();353       Begin = Begin.isFileID()354                   ? includeHashLoc(SM.getFileID(Begin), SM)355                   : SM.getImmediateExpansionRange(Begin).getBegin()) {356    BeginExpansions[SM.getFileID(Begin)] = Begin;357  }358  // Move up the stack of expansion locations for the end until we find the359  // location in BeginExpansions with that has the same file id.360  for (SourceLocation End = R.getEnd(); End.isValid();361       End = End.isFileID() ? includeHashLoc(SM.getFileID(End), SM)362                            : toTokenRange(SM.getImmediateExpansionRange(End),363                                           SM, LangOpts)364                                  .getEnd()) {365    auto It = BeginExpansions.find(SM.getFileID(End));366    if (It != BeginExpansions.end()) {367      if (SM.getFileOffset(It->second) > SM.getFileOffset(End))368        return SourceLocation();369      return {It->second, End};370    }371  }372  return SourceRange();373}374 375// Find an expansion range (not necessarily immediate) the ends of which are in376// the same file id.377static SourceRange378getExpansionTokenRangeInSameFile(SourceLocation Loc, const SourceManager &SM,379                                 const LangOptions &LangOpts) {380  return rangeInCommonFile(381      toTokenRange(SM.getImmediateExpansionRange(Loc), SM, LangOpts), SM,382      LangOpts);383}384 385// Returns the file range for a given Location as a Token Range386// This is quite similar to getFileLoc in SourceManager as both use387// getImmediateExpansionRange and getImmediateSpellingLoc (for macro IDs).388// However:389// - We want to maintain the full range information as we move from one file to390//   the next. getFileLoc only uses the BeginLoc of getImmediateExpansionRange.391// - We want to split '>>' tokens as the lexer parses the '>>' in nested392//   template instantiations as a '>>' instead of two '>'s.393// There is also getExpansionRange but it simply calls394// getImmediateExpansionRange on the begin and ends separately which is wrong.395static SourceRange getTokenFileRange(SourceLocation Loc,396                                     const SourceManager &SM,397                                     const LangOptions &LangOpts) {398  SourceRange FileRange = Loc;399  while (!FileRange.getBegin().isFileID()) {400    if (SM.isMacroArgExpansion(FileRange.getBegin())) {401      FileRange = unionTokenRange(402          SM.getImmediateSpellingLoc(FileRange.getBegin()),403          SM.getImmediateSpellingLoc(FileRange.getEnd()), SM, LangOpts);404      assert(SM.isWrittenInSameFile(FileRange.getBegin(), FileRange.getEnd()));405    } else {406      SourceRange ExpansionRangeForBegin =407          getExpansionTokenRangeInSameFile(FileRange.getBegin(), SM, LangOpts);408      SourceRange ExpansionRangeForEnd =409          getExpansionTokenRangeInSameFile(FileRange.getEnd(), SM, LangOpts);410      if (ExpansionRangeForBegin.isInvalid() ||411          ExpansionRangeForEnd.isInvalid())412        return SourceRange();413      assert(SM.isWrittenInSameFile(ExpansionRangeForBegin.getBegin(),414                                    ExpansionRangeForEnd.getBegin()) &&415             "Both Expansion ranges should be in same file.");416      FileRange = unionTokenRange(ExpansionRangeForBegin, ExpansionRangeForEnd,417                                  SM, LangOpts);418    }419  }420  return FileRange;421}422 423bool isInsideMainFile(SourceLocation Loc, const SourceManager &SM) {424  if (!Loc.isValid())425    return false;426  FileID FID = SM.getFileID(SM.getExpansionLoc(Loc));427  return FID == SM.getMainFileID() || FID == SM.getPreambleFileID();428}429 430std::optional<SourceRange> toHalfOpenFileRange(const SourceManager &SM,431                                               const LangOptions &LangOpts,432                                               SourceRange R) {433  SourceRange R1 = getTokenFileRange(R.getBegin(), SM, LangOpts);434  if (!isValidFileRange(SM, R1))435    return std::nullopt;436 437  SourceRange R2 = getTokenFileRange(R.getEnd(), SM, LangOpts);438  if (!isValidFileRange(SM, R2))439    return std::nullopt;440 441  SourceRange Result =442      rangeInCommonFile(unionTokenRange(R1, R2, SM, LangOpts), SM, LangOpts);443  unsigned TokLen = getTokenLengthAtLoc(Result.getEnd(), SM, LangOpts);444  // Convert from closed token range to half-open (char) range445  Result.setEnd(Result.getEnd().getLocWithOffset(TokLen));446  if (!isValidFileRange(SM, Result))447    return std::nullopt;448 449  return Result;450}451 452llvm::StringRef toSourceCode(const SourceManager &SM, SourceRange R) {453  assert(isValidFileRange(SM, R));454  auto Buf = SM.getBufferOrNone(SM.getFileID(R.getBegin()));455  assert(Buf);456 457  size_t BeginOffset = SM.getFileOffset(R.getBegin());458  size_t EndOffset = SM.getFileOffset(R.getEnd());459  return Buf->getBuffer().substr(BeginOffset, EndOffset - BeginOffset);460}461 462llvm::Expected<SourceLocation> sourceLocationInMainFile(const SourceManager &SM,463                                                        Position P) {464  llvm::StringRef Code = SM.getBufferOrFake(SM.getMainFileID()).getBuffer();465  auto Offset =466      positionToOffset(Code, P, /*AllowColumnsBeyondLineLength=*/false);467  if (!Offset)468    return Offset.takeError();469  return SM.getLocForStartOfFile(SM.getMainFileID()).getLocWithOffset(*Offset);470}471 472Range halfOpenToRange(const SourceManager &SM, CharSourceRange R) {473  // Clang is 1-based, LSP uses 0-based indexes.474  Position Begin = sourceLocToPosition(SM, R.getBegin());475  Position End = sourceLocToPosition(SM, R.getEnd());476 477  return {Begin, End};478}479 480void unionRanges(Range &A, Range B) {481  if (B.start < A.start)482    A.start = B.start;483  if (A.end < B.end)484    A.end = B.end;485}486 487std::pair<size_t, size_t> offsetToClangLineColumn(llvm::StringRef Code,488                                                  size_t Offset) {489  Offset = std::min(Code.size(), Offset);490  llvm::StringRef Before = Code.substr(0, Offset);491  int Lines = Before.count('\n');492  size_t PrevNL = Before.rfind('\n');493  size_t StartOfLine = (PrevNL == llvm::StringRef::npos) ? 0 : (PrevNL + 1);494  return {Lines + 1, Offset - StartOfLine + 1};495}496 497std::pair<StringRef, StringRef> splitQualifiedName(StringRef QName) {498  size_t Pos = QName.rfind("::");499  if (Pos == llvm::StringRef::npos)500    return {llvm::StringRef(), QName};501  return {QName.substr(0, Pos + 2), QName.substr(Pos + 2)};502}503 504TextEdit replacementToEdit(llvm::StringRef Code,505                           const tooling::Replacement &R) {506  Range ReplacementRange = {507      offsetToPosition(Code, R.getOffset()),508      offsetToPosition(Code, R.getOffset() + R.getLength())};509  return {ReplacementRange, std::string(R.getReplacementText())};510}511 512std::vector<TextEdit> replacementsToEdits(llvm::StringRef Code,513                                          const tooling::Replacements &Repls) {514  std::vector<TextEdit> Edits;515  for (const auto &R : Repls)516    Edits.push_back(replacementToEdit(Code, R));517  return Edits;518}519 520std::optional<std::string> getCanonicalPath(const FileEntryRef F,521                                            FileManager &FileMgr) {522  llvm::SmallString<128> FilePath = F.getName();523  if (!llvm::sys::path::is_absolute(FilePath)) {524    if (auto EC =525            FileMgr.getVirtualFileSystem().makeAbsolute(526                FilePath)) {527      elog("Could not turn relative path '{0}' to absolute: {1}", FilePath,528           EC.message());529      return std::nullopt;530    }531  }532 533  // Handle the symbolic link path case where the current working directory534  // (getCurrentWorkingDirectory) is a symlink. We always want to the real535  // file path (instead of the symlink path) for the  C++ symbols.536  //537  // Consider the following example:538  //539  //   src dir: /project/src/foo.h540  //   current working directory (symlink): /tmp/build -> /project/src/541  //542  //  The file path of Symbol is "/project/src/foo.h" instead of543  //  "/tmp/build/foo.h"544  if (auto Dir = FileMgr.getOptionalDirectoryRef(545          llvm::sys::path::parent_path(FilePath))) {546    llvm::SmallString<128> RealPath;547    llvm::StringRef DirName = FileMgr.getCanonicalName(*Dir);548    llvm::sys::path::append(RealPath, DirName,549                            llvm::sys::path::filename(FilePath));550    return RealPath.str().str();551  }552 553  return FilePath.str().str();554}555 556TextEdit toTextEdit(const FixItHint &FixIt, const SourceManager &M,557                    const LangOptions &L) {558  TextEdit Result;559  Result.range =560      halfOpenToRange(M, Lexer::makeFileCharRange(FixIt.RemoveRange, M, L));561  Result.newText = FixIt.CodeToInsert;562  return Result;563}564 565FileDigest digest(llvm::StringRef Content) {566  uint64_t Hash{llvm::xxh3_64bits(Content)};567  FileDigest Result;568  for (unsigned I = 0; I < Result.size(); ++I) {569    Result[I] = uint8_t(Hash);570    Hash >>= 8;571  }572  return Result;573}574 575std::optional<FileDigest> digestFile(const SourceManager &SM, FileID FID) {576  bool Invalid = false;577  llvm::StringRef Content = SM.getBufferData(FID, &Invalid);578  if (Invalid)579    return std::nullopt;580  return digest(Content);581}582 583format::FormatStyle getFormatStyleForFile(llvm::StringRef File,584                                          llvm::StringRef Content,585                                          const ThreadsafeFS &TFS,586                                          bool FormatFile) {587  // Unless we're formatting a substantial amount of code (the entire file588  // or an arbitrarily large range), skip libFormat's heuristic check for589  // .h files that tries to determine whether the file contains objective-c590  // code. (This is accomplished by passing empty code contents to getStyle().591  // The heuristic is the only thing that looks at the contents.)592  // This is a workaround for PR60151, a known issue in libFormat where this593  // heuristic can OOM on large files. If we *are* formatting the entire file,594  // there's no point in doing this because the actual format::reformat() call595  // will run into the same OOM; we'd just be risking inconsistencies between596  // clangd and clang-format on smaller .h files where they disagree on what597  // language is detected.598  if (!FormatFile)599    Content = {};600  auto Style = format::getStyle(format::DefaultFormatStyle, File,601                                format::DefaultFallbackStyle, Content,602                                TFS.view(/*CWD=*/std::nullopt).get());603  if (!Style) {604    log("getStyle() failed for file {0}: {1}. Fallback is LLVM style.", File,605        Style.takeError());606    return format::getLLVMStyle();607  }608  return *Style;609}610 611llvm::Expected<tooling::Replacements>612cleanupAndFormat(StringRef Code, const tooling::Replacements &Replaces,613                 const format::FormatStyle &Style) {614  auto CleanReplaces = cleanupAroundReplacements(Code, Replaces, Style);615  if (!CleanReplaces)616    return CleanReplaces;617  return formatReplacements(Code, std::move(*CleanReplaces), Style);618}619 620static void621lex(llvm::StringRef Code, const LangOptions &LangOpts,622    llvm::function_ref<void(const syntax::Token &, const SourceManager &SM)>623        Action) {624  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!625  std::string NullTerminatedCode = Code.str();626  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);627  auto &SM = FileSM.get();628  for (const auto &Tok : syntax::tokenize(SM.getMainFileID(), SM, LangOpts))629    Action(Tok, SM);630}631 632llvm::StringMap<unsigned> collectIdentifiers(llvm::StringRef Content,633                                             const format::FormatStyle &Style) {634  llvm::StringMap<unsigned> Identifiers;635  auto LangOpt = format::getFormattingLangOpts(Style);636  lex(Content, LangOpt, [&](const syntax::Token &Tok, const SourceManager &SM) {637    if (Tok.kind() == tok::identifier)638      ++Identifiers[Tok.text(SM)];639    // FIXME: Should this function really return keywords too ?640    else if (const auto *Keyword = tok::getKeywordSpelling(Tok.kind()))641      ++Identifiers[Keyword];642  });643  return Identifiers;644}645 646std::vector<Range> collectIdentifierRanges(llvm::StringRef Identifier,647                                           llvm::StringRef Content,648                                           const LangOptions &LangOpts) {649  std::vector<Range> Ranges;650  lex(Content, LangOpts,651      [&](const syntax::Token &Tok, const SourceManager &SM) {652        if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier)653          return;654        Ranges.push_back(halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));655      });656  return Ranges;657}658 659bool isKeyword(llvm::StringRef NewName, const LangOptions &LangOpts) {660  // Keywords are initialized in constructor.661  clang::IdentifierTable KeywordsTable(LangOpts);662  return KeywordsTable.find(NewName) != KeywordsTable.end();663}664 665namespace {666struct NamespaceEvent {667  enum {668    BeginNamespace, // namespace <ns> {.     Payload is resolved <ns>.669    EndNamespace,   // } // namespace <ns>.  Payload is resolved *outer*670                    // namespace.671    UsingDirective  // using namespace <ns>. Payload is unresolved <ns>.672  } Trigger;673  std::string Payload;674  Position Pos;675};676// Scans C++ source code for constructs that change the visible namespaces.677void parseNamespaceEvents(llvm::StringRef Code, const LangOptions &LangOpts,678                          llvm::function_ref<void(NamespaceEvent)> Callback) {679 680  // Stack of enclosing namespaces, e.g. {"clang", "clangd"}681  std::vector<std::string> Enclosing; // Contains e.g. "clang", "clangd"682  // Stack counts open braces. true if the brace opened a namespace.683  llvm::BitVector BraceStack;684 685  enum {686    Default,687    Namespace,          // just saw 'namespace'688    NamespaceName,      // just saw 'namespace' NSName689    Using,              // just saw 'using'690    UsingNamespace,     // just saw 'using namespace'691    UsingNamespaceName, // just saw 'using namespace' NSName692  } State = Default;693  std::string NSName;694 695  NamespaceEvent Event;696  lex(Code, LangOpts, [&](const syntax::Token &Tok, const SourceManager &SM) {697    Event.Pos = sourceLocToPosition(SM, Tok.location());698    switch (Tok.kind()) {699    case tok::kw_using:700      State = State == Default ? Using : Default;701      break;702    case tok::kw_namespace:703      switch (State) {704      case Using:705        State = UsingNamespace;706        break;707      case Default:708        State = Namespace;709        break;710      default:711        State = Default;712        break;713      }714      break;715    case tok::identifier:716      switch (State) {717      case UsingNamespace:718        NSName.clear();719        [[fallthrough]];720      case UsingNamespaceName:721        NSName.append(Tok.text(SM).str());722        State = UsingNamespaceName;723        break;724      case Namespace:725        NSName.clear();726        [[fallthrough]];727      case NamespaceName:728        NSName.append(Tok.text(SM).str());729        State = NamespaceName;730        break;731      case Using:732      case Default:733        State = Default;734        break;735      }736      break;737    case tok::coloncolon:738      // This can come at the beginning or in the middle of a namespace739      // name.740      switch (State) {741      case UsingNamespace:742        NSName.clear();743        [[fallthrough]];744      case UsingNamespaceName:745        NSName.append("::");746        State = UsingNamespaceName;747        break;748      case NamespaceName:749        NSName.append("::");750        State = NamespaceName;751        break;752      case Namespace: // Not legal here.753      case Using:754      case Default:755        State = Default;756        break;757      }758      break;759    case tok::l_brace:760      // Record which { started a namespace, so we know when } ends one.761      if (State == NamespaceName) {762        // Parsed: namespace <name> {763        BraceStack.push_back(true);764        Enclosing.push_back(NSName);765        Event.Trigger = NamespaceEvent::BeginNamespace;766        Event.Payload = llvm::join(Enclosing, "::");767        Callback(Event);768      } else {769        // This case includes anonymous namespaces (State = Namespace).770        // For our purposes, they're not namespaces and we ignore them.771        BraceStack.push_back(false);772      }773      State = Default;774      break;775    case tok::r_brace:776      // If braces are unmatched, we're going to be confused, but don't777      // crash.778      if (!BraceStack.empty()) {779        if (BraceStack.back()) {780          // Parsed: } // namespace781          Enclosing.pop_back();782          Event.Trigger = NamespaceEvent::EndNamespace;783          Event.Payload = llvm::join(Enclosing, "::");784          Callback(Event);785        }786        BraceStack.pop_back();787      }788      break;789    case tok::semi:790      if (State == UsingNamespaceName) {791        // Parsed: using namespace <name> ;792        Event.Trigger = NamespaceEvent::UsingDirective;793        Event.Payload = std::move(NSName);794        Callback(Event);795      }796      State = Default;797      break;798    default:799      State = Default;800      break;801    }802  });803}804 805// Returns the prefix namespaces of NS: {"" ... NS}.806llvm::SmallVector<llvm::StringRef> ancestorNamespaces(llvm::StringRef NS) {807  llvm::SmallVector<llvm::StringRef> Results;808  Results.push_back(NS.take_front(0));809  NS.split(Results, "::", /*MaxSplit=*/-1, /*KeepEmpty=*/false);810  for (llvm::StringRef &R : Results)811    R = NS.take_front(R.end() - NS.begin());812  return Results;813}814 815// Checks whether \p FileName is a valid spelling of main file.816bool isMainFile(llvm::StringRef FileName, const SourceManager &SM) {817  auto FE = SM.getFileManager().getOptionalFileRef(FileName);818  return FE && FE == SM.getFileEntryRefForID(SM.getMainFileID());819}820 821} // namespace822 823std::vector<std::string> visibleNamespaces(llvm::StringRef Code,824                                           const LangOptions &LangOpts) {825  std::string Current;826  // Map from namespace to (resolved) namespaces introduced via using directive.827  llvm::StringMap<llvm::StringSet<>> UsingDirectives;828 829  parseNamespaceEvents(Code, LangOpts, [&](NamespaceEvent Event) {830    llvm::StringRef NS = Event.Payload;831    switch (Event.Trigger) {832    case NamespaceEvent::BeginNamespace:833    case NamespaceEvent::EndNamespace:834      Current = std::move(Event.Payload);835      break;836    case NamespaceEvent::UsingDirective:837      if (NS.consume_front("::"))838        UsingDirectives[Current].insert(NS);839      else {840        for (llvm::StringRef Enclosing : ancestorNamespaces(Current)) {841          if (Enclosing.empty())842            UsingDirectives[Current].insert(NS);843          else844            UsingDirectives[Current].insert((Enclosing + "::" + NS).str());845        }846      }847      break;848    }849  });850 851  std::vector<std::string> Found;852  for (llvm::StringRef Enclosing : ancestorNamespaces(Current)) {853    Found.push_back(std::string(Enclosing));854    auto It = UsingDirectives.find(Enclosing);855    if (It != UsingDirectives.end())856      for (const auto &Used : It->second)857        Found.push_back(std::string(Used.getKey()));858  }859 860  llvm::sort(Found, [&](const std::string &LHS, const std::string &RHS) {861    if (Current == RHS)862      return false;863    if (Current == LHS)864      return true;865    return LHS < RHS;866  });867  Found.erase(llvm::unique(Found), Found.end());868  return Found;869}870 871llvm::StringSet<> collectWords(llvm::StringRef Content) {872  // We assume short words are not significant.873  // We may want to consider other stopwords, e.g. language keywords.874  // (A very naive implementation showed no benefit, but lexing might do better)875  static constexpr int MinWordLength = 4;876 877  std::vector<CharRole> Roles(Content.size());878  calculateRoles(Content, Roles);879 880  llvm::StringSet<> Result;881  llvm::SmallString<256> Word;882  auto Flush = [&] {883    if (Word.size() >= MinWordLength) {884      for (char &C : Word)885        C = llvm::toLower(C);886      Result.insert(Word);887    }888    Word.clear();889  };890  for (unsigned I = 0; I < Content.size(); ++I) {891    switch (Roles[I]) {892    case Head:893      Flush();894      [[fallthrough]];895    case Tail:896      Word.push_back(Content[I]);897      break;898    case Unknown:899    case Separator:900      Flush();901      break;902    }903  }904  Flush();905 906  return Result;907}908 909static bool isLikelyIdentifier(llvm::StringRef Word, llvm::StringRef Before,910                               llvm::StringRef After) {911  // `foo` is an identifier.912  if (Before.ends_with("`") && After.starts_with("`"))913    return true;914  // In foo::bar, both foo and bar are identifiers.915  if (Before.ends_with("::") || After.starts_with("::"))916    return true;917  // Doxygen tags like \c foo indicate identifiers.918  // Don't search too far back.919  // This duplicates clang's doxygen parser, revisit if it gets complicated.920  Before = Before.take_back(100); // Don't search too far back.921  auto Pos = Before.find_last_of("\\@");922  if (Pos != llvm::StringRef::npos) {923    llvm::StringRef Tag = Before.substr(Pos + 1).rtrim(' ');924    if (Tag == "p" || Tag == "c" || Tag == "class" || Tag == "tparam" ||925        Tag == "param" || Tag == "param[in]" || Tag == "param[out]" ||926        Tag == "param[in,out]" || Tag == "retval" || Tag == "throw" ||927        Tag == "throws" || Tag == "link")928      return true;929  }930 931  // Word contains underscore.932  // This handles things like snake_case and MACRO_CASE.933  if (Word.contains('_')) {934    return true;935  }936  // Word contains capital letter other than at beginning.937  // This handles things like lowerCamel and UpperCamel.938  // The check for also containing a lowercase letter is to rule out939  // initialisms like "HTTP".940  bool HasLower = Word.find_if(clang::isLowercase) != StringRef::npos;941  bool HasUpper = Word.substr(1).find_if(clang::isUppercase) != StringRef::npos;942  if (HasLower && HasUpper) {943    return true;944  }945  // FIXME: consider mid-sentence Capitalization?946  return false;947}948 949std::optional<SpelledWord> SpelledWord::touching(SourceLocation SpelledLoc,950                                                 const syntax::TokenBuffer &TB,951                                                 const LangOptions &LangOpts) {952  const auto &SM = TB.sourceManager();953  auto Touching = syntax::spelledTokensTouching(SpelledLoc, TB);954  for (const auto &T : Touching) {955    // If the token is an identifier or a keyword, don't use any heuristics.956    if (tok::isAnyIdentifier(T.kind()) || tok::getKeywordSpelling(T.kind())) {957      SpelledWord Result;958      Result.Location = T.location();959      Result.Text = T.text(SM);960      Result.LikelyIdentifier = tok::isAnyIdentifier(T.kind());961      Result.PartOfSpelledToken = &T;962      Result.SpelledToken = &T;963      auto Expanded =964          TB.expandedTokens(SM.getMacroArgExpandedLocation(T.location()));965      if (Expanded.size() == 1 && Expanded.front().text(SM) == Result.Text)966        Result.ExpandedToken = &Expanded.front();967      return Result;968    }969  }970  FileID File;971  unsigned Offset;972  std::tie(File, Offset) = SM.getDecomposedLoc(SpelledLoc);973  bool Invalid = false;974  llvm::StringRef Code = SM.getBufferData(File, &Invalid);975  if (Invalid)976    return std::nullopt;977  unsigned B = Offset, E = Offset;978  while (B > 0 && isAsciiIdentifierContinue(Code[B - 1]))979    --B;980  while (E < Code.size() && isAsciiIdentifierContinue(Code[E]))981    ++E;982  if (B == E)983    return std::nullopt;984 985  SpelledWord Result;986  Result.Location = SM.getComposedLoc(File, B);987  Result.Text = Code.slice(B, E);988  Result.LikelyIdentifier =989      isLikelyIdentifier(Result.Text, Code.substr(0, B), Code.substr(E)) &&990      // should not be a keyword991      tok::isAnyIdentifier(992          IdentifierTable(LangOpts).get(Result.Text).getTokenID());993  for (const auto &T : Touching)994    if (T.location() <= Result.Location)995      Result.PartOfSpelledToken = &T;996  return Result;997}998 999std::optional<DefinedMacro> locateMacroAt(const syntax::Token &SpelledTok,1000                                          Preprocessor &PP) {1001  if (SpelledTok.kind() != tok::identifier)1002    return std::nullopt;1003  SourceLocation Loc = SpelledTok.location();1004  assert(Loc.isFileID());1005  const auto &SM = PP.getSourceManager();1006  IdentifierInfo *IdentifierInfo = PP.getIdentifierInfo(SpelledTok.text(SM));1007  if (!IdentifierInfo || !IdentifierInfo->hadMacroDefinition())1008    return std::nullopt;1009 1010  // We need to take special case to handle #define and #undef.1011  // Preprocessor::getMacroDefinitionAtLoc() only considers a macro1012  // definition to be in scope *after* the location of the macro name in a1013  // #define that introduces it, and *before* the location of the macro name1014  // in an #undef that undefines it. To handle these cases, we check for1015  // the macro being in scope either just after or just before the location1016  // of the token. In getting the location before, we also take care to check1017  // for start-of-file.1018  FileID FID = SM.getFileID(Loc);1019  assert(Loc != SM.getLocForEndOfFile(FID));1020  SourceLocation JustAfterToken = Loc.getLocWithOffset(1);1021  auto *MacroInfo =1022      PP.getMacroDefinitionAtLoc(IdentifierInfo, JustAfterToken).getMacroInfo();1023  if (!MacroInfo && SM.getLocForStartOfFile(FID) != Loc) {1024    SourceLocation JustBeforeToken = Loc.getLocWithOffset(-1);1025    MacroInfo = PP.getMacroDefinitionAtLoc(IdentifierInfo, JustBeforeToken)1026                    .getMacroInfo();1027  }1028  if (!MacroInfo) {1029    return std::nullopt;1030  }1031  return DefinedMacro{1032      IdentifierInfo->getName(), MacroInfo,1033      translatePreamblePatchLocation(MacroInfo->getDefinitionLoc(), SM)};1034}1035 1036llvm::Expected<std::string> Edit::apply() const {1037  return tooling::applyAllReplacements(InitialCode, Replacements);1038}1039 1040std::vector<TextEdit> Edit::asTextEdits() const {1041  return replacementsToEdits(InitialCode, Replacements);1042}1043 1044bool Edit::canApplyTo(llvm::StringRef Code) const {1045  // Create line iterators, since line numbers are important while applying our1046  // edit we cannot skip blank lines.1047  auto LHS = llvm::MemoryBuffer::getMemBuffer(Code);1048  llvm::line_iterator LHSIt(*LHS, /*SkipBlanks=*/false);1049 1050  auto RHS = llvm::MemoryBuffer::getMemBuffer(InitialCode);1051  llvm::line_iterator RHSIt(*RHS, /*SkipBlanks=*/false);1052 1053  // Compare the InitialCode we prepared the edit for with the Code we received1054  // line by line to make sure there are no differences.1055  // FIXME: This check is too conservative now, it should be enough to only1056  // check lines around the replacements contained inside the Edit.1057  while (!LHSIt.is_at_eof() && !RHSIt.is_at_eof()) {1058    if (*LHSIt != *RHSIt)1059      return false;1060    ++LHSIt;1061    ++RHSIt;1062  }1063 1064  // After we reach EOF for any of the files we make sure the other one doesn't1065  // contain any additional content except empty lines, they should not1066  // interfere with the edit we produced.1067  while (!LHSIt.is_at_eof()) {1068    if (!LHSIt->empty())1069      return false;1070    ++LHSIt;1071  }1072  while (!RHSIt.is_at_eof()) {1073    if (!RHSIt->empty())1074      return false;1075    ++RHSIt;1076  }1077  return true;1078}1079 1080llvm::Error reformatEdit(Edit &E, const format::FormatStyle &Style) {1081  if (auto NewEdits = cleanupAndFormat(E.InitialCode, E.Replacements, Style))1082    E.Replacements = std::move(*NewEdits);1083  else1084    return NewEdits.takeError();1085  return llvm::Error::success();1086}1087 1088// Workaround for editors that have buggy handling of newlines at end of file.1089//1090// The editor is supposed to expose document contents over LSP as an exact1091// string, with whitespace and newlines well-defined. But internally many1092// editors treat text as an array of lines, and there can be ambiguity over1093// whether the last line ends with a newline or not.1094//1095// This confusion can lead to incorrect edits being sent. Failing to apply them1096// is catastrophic: we're desynced, LSP has no mechanism to get back in sync.1097// We apply a heuristic to avoid this state.1098//1099// If our current view of an N-line file does *not* end in a newline, but the1100// editor refers to the start of the next line (an impossible location), then1101// we silently add a newline to make this valid.1102// We will still validate that the rangeLength is correct, *including* the1103// inferred newline.1104//1105// See https://github.com/neovim/neovim/issues/170851106static void inferFinalNewline(llvm::Expected<size_t> &Err,1107                              std::string &Contents, const Position &Pos) {1108  if (Err)1109    return;1110  if (!Contents.empty() && Contents.back() == '\n')1111    return;1112  if (Pos.character != 0)1113    return;1114  if (Pos.line != llvm::count(Contents, '\n') + 1)1115    return;1116  log("Editor sent invalid change coordinates, inferring newline at EOF");1117  Contents.push_back('\n');1118  consumeError(Err.takeError());1119  Err = Contents.size();1120}1121 1122llvm::Error applyChange(std::string &Contents,1123                        const TextDocumentContentChangeEvent &Change) {1124  if (!Change.range) {1125    Contents = Change.text;1126    return llvm::Error::success();1127  }1128 1129  const Position &Start = Change.range->start;1130  llvm::Expected<size_t> StartIndex = positionToOffset(Contents, Start, false);1131  inferFinalNewline(StartIndex, Contents, Start);1132  if (!StartIndex)1133    return StartIndex.takeError();1134 1135  const Position &End = Change.range->end;1136  llvm::Expected<size_t> EndIndex = positionToOffset(Contents, End, false);1137  inferFinalNewline(EndIndex, Contents, End);1138  if (!EndIndex)1139    return EndIndex.takeError();1140 1141  if (*EndIndex < *StartIndex)1142    return error(llvm::errc::invalid_argument,1143                 "Range's end position ({0}) is before start position ({1})",1144                 End, Start);1145 1146  // Since the range length between two LSP positions is dependent on the1147  // contents of the buffer we compute the range length between the start and1148  // end position ourselves and compare it to the range length of the LSP1149  // message to verify the buffers of the client and server are in sync.1150 1151  // EndIndex and StartIndex are in bytes, but Change.rangeLength is in UTF-161152  // code units.1153  ssize_t ComputedRangeLength =1154      lspLength(Contents.substr(*StartIndex, *EndIndex - *StartIndex));1155 1156  if (Change.rangeLength && ComputedRangeLength != *Change.rangeLength)1157    return error(llvm::errc::invalid_argument,1158                 "Change's rangeLength ({0}) doesn't match the "1159                 "computed range length ({1}).",1160                 *Change.rangeLength, ComputedRangeLength);1161 1162  Contents.replace(*StartIndex, *EndIndex - *StartIndex, Change.text);1163 1164  return llvm::Error::success();1165}1166 1167EligibleRegion getEligiblePoints(llvm::StringRef Code,1168                                 llvm::StringRef FullyQualifiedName,1169                                 const LangOptions &LangOpts) {1170  EligibleRegion ER;1171  // Start with global namespace.1172  std::vector<std::string> Enclosing = {""};1173  // FIXME: In addition to namespaces try to generate events for function1174  // definitions as well. One might use a closing parantheses(")" followed by an1175  // opening brace "{" to trigger the start.1176  parseNamespaceEvents(Code, LangOpts, [&](NamespaceEvent Event) {1177    // Using Directives only introduces declarations to current scope, they do1178    // not change the current namespace, so skip them.1179    if (Event.Trigger == NamespaceEvent::UsingDirective)1180      return;1181    // Do not qualify the global namespace.1182    if (!Event.Payload.empty())1183      Event.Payload.append("::");1184 1185    std::string CurrentNamespace;1186    if (Event.Trigger == NamespaceEvent::BeginNamespace) {1187      Enclosing.emplace_back(std::move(Event.Payload));1188      CurrentNamespace = Enclosing.back();1189      // parseNameSpaceEvents reports the beginning position of a token; we want1190      // to insert after '{', so increment by one.1191      ++Event.Pos.character;1192    } else {1193      // Event.Payload points to outer namespace when exiting a scope, so use1194      // the namespace we've last entered instead.1195      CurrentNamespace = std::move(Enclosing.back());1196      Enclosing.pop_back();1197      assert(Enclosing.back() == Event.Payload);1198    }1199 1200    // Ignore namespaces that are not a prefix of the target.1201    if (!FullyQualifiedName.starts_with(CurrentNamespace))1202      return;1203 1204    // Prefer the namespace that shares the longest prefix with target.1205    if (CurrentNamespace.size() > ER.EnclosingNamespace.size()) {1206      ER.EligiblePoints.clear();1207      ER.EnclosingNamespace = CurrentNamespace;1208    }1209    if (CurrentNamespace.size() == ER.EnclosingNamespace.size())1210      ER.EligiblePoints.emplace_back(std::move(Event.Pos));1211  });1212  // If there were no shared namespaces just return EOF.1213  if (ER.EligiblePoints.empty()) {1214    assert(ER.EnclosingNamespace.empty());1215    ER.EligiblePoints.emplace_back(offsetToPosition(Code, Code.size()));1216  }1217  return ER;1218}1219 1220std::string getNamespaceAtPosition(StringRef Code, const Position &Pos,1221                                   const LangOptions &LangOpts) {1222  std::vector<std::string> Enclosing = {""};1223  parseNamespaceEvents(Code, LangOpts, [&](NamespaceEvent Event) {1224    if (Pos < Event.Pos)1225      return;1226    if (Event.Trigger == NamespaceEvent::UsingDirective)1227      return;1228    if (!Event.Payload.empty())1229      Event.Payload.append("::");1230    if (Event.Trigger == NamespaceEvent::BeginNamespace) {1231      Enclosing.emplace_back(std::move(Event.Payload));1232    } else {1233      Enclosing.pop_back();1234      assert(Enclosing.back() == Event.Payload);1235    }1236  });1237  return Enclosing.back();1238}1239 1240bool isHeaderFile(llvm::StringRef FileName,1241                  std::optional<LangOptions> LangOpts) {1242  // Respect the langOpts, for non-file-extension cases, e.g. standard library1243  // files.1244  if (LangOpts && LangOpts->IsHeaderFile)1245    return true;1246  namespace types = clang::driver::types;1247  auto Lang = types::lookupTypeForExtension(1248      llvm::sys::path::extension(FileName).substr(1));1249  return Lang != types::TY_INVALID && types::onlyPrecompileType(Lang);1250}1251 1252bool isProtoFile(SourceLocation Loc, const SourceManager &SM) {1253  auto FileName = SM.getFilename(Loc);1254  if (!FileName.ends_with(".proto.h") && !FileName.ends_with(".pb.h"))1255    return false;1256  auto FID = SM.getFileID(Loc);1257  // All proto generated headers should start with this line.1258  static const char *ProtoHeaderComment =1259      "// Generated by the protocol buffer compiler.  DO NOT EDIT!";1260  // Double check that this is an actual protobuf header.1261  return SM.getBufferData(FID).starts_with(ProtoHeaderComment);1262}1263 1264SourceLocation translatePreamblePatchLocation(SourceLocation Loc,1265                                              const SourceManager &SM) {1266  auto DefFile = SM.getFileID(Loc);1267  if (auto FE = SM.getFileEntryRefForID(DefFile)) {1268    auto IncludeLoc = SM.getIncludeLoc(DefFile);1269    // Preamble patch is included inside the builtin file.1270    if (IncludeLoc.isValid() && SM.isWrittenInBuiltinFile(IncludeLoc) &&1271        FE->getName().ends_with(PreamblePatch::HeaderName)) {1272      auto Presumed = SM.getPresumedLoc(Loc);1273      // Check that line directive is pointing at main file.1274      if (Presumed.isValid() && Presumed.getFileID().isInvalid() &&1275          isMainFile(Presumed.getFilename(), SM)) {1276        Loc = SM.translateLineCol(SM.getMainFileID(), Presumed.getLine(),1277                                  Presumed.getColumn());1278      }1279    }1280  }1281  return Loc;1282}1283 1284clangd::Range rangeTillEOL(llvm::StringRef Code, unsigned HashOffset) {1285  clangd::Range Result;1286  Result.end = Result.start = offsetToPosition(Code, HashOffset);1287 1288  // Span the warning until the EOL or EOF.1289  Result.end.character +=1290      lspLength(Code.drop_front(HashOffset).take_until([](char C) {1291        return C == '\n' || C == '\r';1292      }));1293  return Result;1294}1295} // namespace clangd1296} // namespace clang1297