llvm-project
1276 строк · 46.5 Кб
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-exception
6//
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" the
59// text in some arbitrary way. This is pretty sad, but this tends to happen deep
60// within indexing of headers where clang misdetected the encoding, and
61// 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) {64bool 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.67for (size_t I = 0; I < U8.size();) {68unsigned char C = static_cast<unsigned char>(U8[I]);69if (LLVM_LIKELY(!(C & 0x80))) { // ASCII character.70if (CB(1, 1))71return true;72++I;73continue;74}75// This convenient property of UTF-8 holds for all non-ASCII characters.76size_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-*.79if (LLVM_UNLIKELY(UTF8Length < 2 || UTF8Length > 4)) {80if (!LoggedInvalid) {81elog("File has invalid UTF-8 near offset {0}: {1}", I, llvm::toHex(U8));82LoggedInvalid = 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).87if (CB(1, 1))88return true;89++I;90continue;91}92I += 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 ...)95if (CB(UTF8Length, UTF8Length == 4 ? 2 : 1))96return true;97}98return false;99}
100
101// Returns the byte offset into the string that is an offset of \p Units in
102// 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,106bool &Valid) {107Valid = Units >= 0;108if (Units <= 0)109return 0;110size_t Result = 0;111switch (Enc) {112case OffsetEncoding::UTF8:113Result = Units;114break;115case OffsetEncoding::UTF16:116Valid = iterateCodepoints(U8, [&](int U8Len, int U16Len) {117Result += U8Len;118Units -= U16Len;119return Units <= 0;120});121if (Units < 0) // Offset in the middle of a surrogate pair.122Valid = false;123break;124case OffsetEncoding::UTF32:125Valid = iterateCodepoints(U8, [&](int U8Len, int U16Len) {126Result += U8Len;127Units--;128return Units <= 0;129});130break;131case OffsetEncoding::UnsupportedEncoding:132llvm_unreachable("unsupported encoding");133}134// Don't return an out-of-range index if we overran.135if (Result > U8.size()) {136Valid = false;137return U8.size();138}139return Result;140}
141
142Key<OffsetEncoding> kCurrentOffsetEncoding;143static OffsetEncoding lspEncoding() {144auto *Enc = Context::current().get(kCurrentOffsetEncoding);145return Enc ? *Enc : OffsetEncoding::UTF16;146}
147
148// Like most strings in clangd, the input is UTF-8 encoded.
149size_t lspLength(llvm::StringRef Code) {150size_t Count = 0;151switch (lspEncoding()) {152case OffsetEncoding::UTF8:153Count = Code.size();154break;155case OffsetEncoding::UTF16:156iterateCodepoints(Code, [&](int U8Len, int U16Len) {157Count += U16Len;158return false;159});160break;161case OffsetEncoding::UTF32:162iterateCodepoints(Code, [&](int U8Len, int U16Len) {163++Count;164return false;165});166break;167case OffsetEncoding::UnsupportedEncoding:168llvm_unreachable("unsupported encoding");169}170return Count;171}
172
173llvm::Expected<size_t> positionToOffset(llvm::StringRef Code, Position P,174bool AllowColumnsBeyondLineLength) {175if (P.line < 0)176return error(llvm::errc::invalid_argument,177"Line value can't be negative ({0})", P.line);178if (P.character < 0)179return error(llvm::errc::invalid_argument,180"Character value can't be negative ({0})", P.character);181size_t StartOfLine = 0;182for (int I = 0; I != P.line; ++I) {183size_t NextNL = Code.find('\n', StartOfLine);184if (NextNL == llvm::StringRef::npos)185return error(llvm::errc::invalid_argument,186"Line value is out of range ({0})", P.line);187StartOfLine = NextNL + 1;188}189StringRef Line =190Code.substr(StartOfLine).take_until([](char C) { return C == '\n'; });191
192// P.character may be in UTF-16, transcode if necessary.193bool Valid;194size_t ByteInLine = measureUnits(Line, P.character, lspEncoding(), Valid);195if (!Valid && !AllowColumnsBeyondLineLength)196return error(llvm::errc::invalid_argument,197"{0} offset {1} is invalid for line {2}", lspEncoding(),198P.character, P.line);199return StartOfLine + ByteInLine;200}
201
202Position offsetToPosition(llvm::StringRef Code, size_t Offset) {203Offset = std::min(Code.size(), Offset);204llvm::StringRef Before = Code.substr(0, Offset);205int Lines = Before.count('\n');206size_t PrevNL = Before.rfind('\n');207size_t StartOfLine = (PrevNL == llvm::StringRef::npos) ? 0 : (PrevNL + 1);208Position Pos;209Pos.line = Lines;210Pos.character = lspLength(Before.substr(StartOfLine));211return 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.216FileID FID;217unsigned Offset;218std::tie(FID, Offset) = SM.getDecomposedSpellingLoc(Loc);219Position P;220P.line = static_cast<int>(SM.getLineNumber(FID, Offset)) - 1;221bool Invalid = false;222llvm::StringRef Code = SM.getBufferData(FID, &Invalid);223if (!Invalid) {224auto ColumnInBytes = SM.getColumnNumber(FID, Offset) - 1;225auto LineSoFar = Code.substr(Offset - ColumnInBytes, ColumnInBytes);226P.character = lspLength(LineSoFar);227}228return P;229}
230
231bool isSpelledInSource(SourceLocation Loc, const SourceManager &SM) {232if (Loc.isFileID())233return true;234auto Spelling = SM.getDecomposedSpellingLoc(Loc);235bool InvalidSLocEntry = false;236const auto SLocEntry = SM.getSLocEntry(Spelling.first, &InvalidSLocEntry);237if (InvalidSLocEntry)238return false;239StringRef SpellingFile = SLocEntry.getFile().getName();240if (SpellingFile == "<scratch space>")241return false;242if (SpellingFile == "<built-in>")243// __STDC__ etc are considered spelled, but BAR in arg -DFOO=BAR is not.244return !SM.isWrittenInCommandLineFile(245SM.getComposedLoc(Spelling.first, Spelling.second));246return true;247}
248
249bool isValidFileRange(const SourceManager &Mgr, SourceRange R) {250if (!R.getBegin().isValid() || !R.getEnd().isValid())251return false;252
253FileID BeginFID;254size_t BeginOffset = 0;255std::tie(BeginFID, BeginOffset) = Mgr.getDecomposedLoc(R.getBegin());256
257FileID EndFID;258size_t EndOffset = 0;259std::tie(EndFID, EndOffset) = Mgr.getDecomposedLoc(R.getEnd());260
261return BeginFID.isValid() && BeginFID == EndFID && BeginOffset <= EndOffset;262}
263
264SourceLocation includeHashLoc(FileID IncludedFile, const SourceManager &SM) {265assert(SM.getLocForEndOfFile(IncludedFile).isFileID());266FileID IncludingFile;267unsigned Offset;268std::tie(IncludingFile, Offset) =269SM.getDecomposedExpansionLoc(SM.getIncludeLoc(IncludedFile));270bool Invalid = false;271llvm::StringRef Buf = SM.getBufferData(IncludingFile, &Invalid);272if (Invalid)273return SourceLocation();274// Now buf is "...\n#include <foo>\n..."275// and Offset points here: ^276// Rewind to the preceding # on the line.277assert(Offset < Buf.size());278for (;; --Offset) {279if (Buf[Offset] == '#')280return SM.getComposedLoc(IncludingFile, Offset);281if (Buf[Offset] == '\n' || Offset == 0) // no hash, what's going on?282return SourceLocation();283}284}
285
286static unsigned getTokenLengthAtLoc(SourceLocation Loc, const SourceManager &SM,287const LangOptions &LangOpts) {288Token TheTok;289if (Lexer::getRawToken(Loc, TheTok, SM, LangOpts))290return 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.296if (TheTok.is(tok::greatergreater))297return 1;298return TheTok.getLength();299}
300
301// Returns location of the last character of the token at a given loc
302static SourceLocation getLocForTokenEnd(SourceLocation BeginLoc,303const SourceManager &SM,304const LangOptions &LangOpts) {305unsigned Len = getTokenLengthAtLoc(BeginLoc, SM, LangOpts);306return BeginLoc.getLocWithOffset(Len ? Len - 1 : 0);307}
308
309// Returns location of the starting of the token at a given EndLoc
310static SourceLocation getLocForTokenBegin(SourceLocation EndLoc,311const SourceManager &SM,312const LangOptions &LangOpts) {313return 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,319const LangOptions &LangOpts) {320if (!Range.isTokenRange())321Range.setEnd(getLocForTokenBegin(Range.getEnd(), SM, LangOpts));322return 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 the
326// last character of the token.
327static SourceRange unionTokenRange(SourceRange R1, SourceRange R2,328const SourceManager &SM,329const LangOptions &LangOpts) {330SourceLocation Begin =331SM.isBeforeInTranslationUnit(R1.getBegin(), R2.getBegin())332? R1.getBegin()333: R2.getBegin();334SourceLocation End =335SM.isBeforeInTranslationUnit(getLocForTokenEnd(R1.getEnd(), SM, LangOpts),336getLocForTokenEnd(R2.getEnd(), SM, LangOpts))337? R2.getEnd()338: R1.getEnd();339return 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 and
344// include location in each.
345static SourceRange rangeInCommonFile(SourceRange R, const SourceManager &SM,346const LangOptions &LangOpts) {347// Fast path for most common cases.348if (SM.isWrittenInSameFile(R.getBegin(), R.getEnd()))349return R;350// Record the stack of expansion locations for the beginning, keyed by FileID.351llvm::DenseMap<FileID, SourceLocation> BeginExpansions;352for (SourceLocation Begin = R.getBegin(); Begin.isValid();353Begin = Begin.isFileID()354? includeHashLoc(SM.getFileID(Begin), SM)355: SM.getImmediateExpansionRange(Begin).getBegin()) {356BeginExpansions[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.360for (SourceLocation End = R.getEnd(); End.isValid();361End = End.isFileID() ? includeHashLoc(SM.getFileID(End), SM)362: toTokenRange(SM.getImmediateExpansionRange(End),363SM, LangOpts)364.getEnd()) {365auto It = BeginExpansions.find(SM.getFileID(End));366if (It != BeginExpansions.end()) {367if (SM.getFileOffset(It->second) > SM.getFileOffset(End))368return SourceLocation();369return {It->second, End};370}371}372return SourceRange();373}
374
375// Find an expansion range (not necessarily immediate) the ends of which are in
376// the same file id.
377static SourceRange378getExpansionTokenRangeInSameFile(SourceLocation Loc, const SourceManager &SM,379const LangOptions &LangOpts) {380return rangeInCommonFile(381toTokenRange(SM.getImmediateExpansionRange(Loc), SM, LangOpts), SM,382LangOpts);383}
384
385// Returns the file range for a given Location as a Token Range
386// This is quite similar to getFileLoc in SourceManager as both use
387// getImmediateExpansionRange and getImmediateSpellingLoc (for macro IDs).
388// However:
389// - We want to maintain the full range information as we move from one file to
390// the next. getFileLoc only uses the BeginLoc of getImmediateExpansionRange.
391// - We want to split '>>' tokens as the lexer parses the '>>' in nested
392// template instantiations as a '>>' instead of two '>'s.
393// There is also getExpansionRange but it simply calls
394// getImmediateExpansionRange on the begin and ends separately which is wrong.
395static SourceRange getTokenFileRange(SourceLocation Loc,396const SourceManager &SM,397const LangOptions &LangOpts) {398SourceRange FileRange = Loc;399while (!FileRange.getBegin().isFileID()) {400if (SM.isMacroArgExpansion(FileRange.getBegin())) {401FileRange = unionTokenRange(402SM.getImmediateSpellingLoc(FileRange.getBegin()),403SM.getImmediateSpellingLoc(FileRange.getEnd()), SM, LangOpts);404assert(SM.isWrittenInSameFile(FileRange.getBegin(), FileRange.getEnd()));405} else {406SourceRange ExpansionRangeForBegin =407getExpansionTokenRangeInSameFile(FileRange.getBegin(), SM, LangOpts);408SourceRange ExpansionRangeForEnd =409getExpansionTokenRangeInSameFile(FileRange.getEnd(), SM, LangOpts);410if (ExpansionRangeForBegin.isInvalid() ||411ExpansionRangeForEnd.isInvalid())412return SourceRange();413assert(SM.isWrittenInSameFile(ExpansionRangeForBegin.getBegin(),414ExpansionRangeForEnd.getBegin()) &&415"Both Expansion ranges should be in same file.");416FileRange = unionTokenRange(ExpansionRangeForBegin, ExpansionRangeForEnd,417SM, LangOpts);418}419}420return FileRange;421}
422
423bool isInsideMainFile(SourceLocation Loc, const SourceManager &SM) {424if (!Loc.isValid())425return false;426FileID FID = SM.getFileID(SM.getExpansionLoc(Loc));427return FID == SM.getMainFileID() || FID == SM.getPreambleFileID();428}
429
430std::optional<SourceRange> toHalfOpenFileRange(const SourceManager &SM,431const LangOptions &LangOpts,432SourceRange R) {433SourceRange R1 = getTokenFileRange(R.getBegin(), SM, LangOpts);434if (!isValidFileRange(SM, R1))435return std::nullopt;436
437SourceRange R2 = getTokenFileRange(R.getEnd(), SM, LangOpts);438if (!isValidFileRange(SM, R2))439return std::nullopt;440
441SourceRange Result =442rangeInCommonFile(unionTokenRange(R1, R2, SM, LangOpts), SM, LangOpts);443unsigned TokLen = getTokenLengthAtLoc(Result.getEnd(), SM, LangOpts);444// Convert from closed token range to half-open (char) range445Result.setEnd(Result.getEnd().getLocWithOffset(TokLen));446if (!isValidFileRange(SM, Result))447return std::nullopt;448
449return Result;450}
451
452llvm::StringRef toSourceCode(const SourceManager &SM, SourceRange R) {453assert(isValidFileRange(SM, R));454auto Buf = SM.getBufferOrNone(SM.getFileID(R.getBegin()));455assert(Buf);456
457size_t BeginOffset = SM.getFileOffset(R.getBegin());458size_t EndOffset = SM.getFileOffset(R.getEnd());459return Buf->getBuffer().substr(BeginOffset, EndOffset - BeginOffset);460}
461
462llvm::Expected<SourceLocation> sourceLocationInMainFile(const SourceManager &SM,463Position P) {464llvm::StringRef Code = SM.getBufferOrFake(SM.getMainFileID()).getBuffer();465auto Offset =466positionToOffset(Code, P, /*AllowColumnsBeyondLineLength=*/false);467if (!Offset)468return Offset.takeError();469return 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.474Position Begin = sourceLocToPosition(SM, R.getBegin());475Position End = sourceLocToPosition(SM, R.getEnd());476
477return {Begin, End};478}
479
480void unionRanges(Range &A, Range B) {481if (B.start < A.start)482A.start = B.start;483if (A.end < B.end)484A.end = B.end;485}
486
487std::pair<size_t, size_t> offsetToClangLineColumn(llvm::StringRef Code,488size_t Offset) {489Offset = std::min(Code.size(), Offset);490llvm::StringRef Before = Code.substr(0, Offset);491int Lines = Before.count('\n');492size_t PrevNL = Before.rfind('\n');493size_t StartOfLine = (PrevNL == llvm::StringRef::npos) ? 0 : (PrevNL + 1);494return {Lines + 1, Offset - StartOfLine + 1};495}
496
497std::pair<StringRef, StringRef> splitQualifiedName(StringRef QName) {498size_t Pos = QName.rfind("::");499if (Pos == llvm::StringRef::npos)500return {llvm::StringRef(), QName};501return {QName.substr(0, Pos + 2), QName.substr(Pos + 2)};502}
503
504TextEdit replacementToEdit(llvm::StringRef Code,505const tooling::Replacement &R) {506Range ReplacementRange = {507offsetToPosition(Code, R.getOffset()),508offsetToPosition(Code, R.getOffset() + R.getLength())};509return {ReplacementRange, std::string(R.getReplacementText())};510}
511
512std::vector<TextEdit> replacementsToEdits(llvm::StringRef Code,513const tooling::Replacements &Repls) {514std::vector<TextEdit> Edits;515for (const auto &R : Repls)516Edits.push_back(replacementToEdit(Code, R));517return Edits;518}
519
520std::optional<std::string> getCanonicalPath(const FileEntryRef F,521FileManager &FileMgr) {522llvm::SmallString<128> FilePath = F.getName();523if (!llvm::sys::path::is_absolute(FilePath)) {524if (auto EC =525FileMgr.getVirtualFileSystem().makeAbsolute(526FilePath)) {527elog("Could not turn relative path '{0}' to absolute: {1}", FilePath,528EC.message());529return 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"544if (auto Dir = FileMgr.getOptionalDirectoryRef(545llvm::sys::path::parent_path(FilePath))) {546llvm::SmallString<128> RealPath;547llvm::StringRef DirName = FileMgr.getCanonicalName(*Dir);548llvm::sys::path::append(RealPath, DirName,549llvm::sys::path::filename(FilePath));550return RealPath.str().str();551}552
553return FilePath.str().str();554}
555
556TextEdit toTextEdit(const FixItHint &FixIt, const SourceManager &M,557const LangOptions &L) {558TextEdit Result;559Result.range =560halfOpenToRange(M, Lexer::makeFileCharRange(FixIt.RemoveRange, M, L));561Result.newText = FixIt.CodeToInsert;562return Result;563}
564
565FileDigest digest(llvm::StringRef Content) {566uint64_t Hash{llvm::xxh3_64bits(Content)};567FileDigest Result;568for (unsigned I = 0; I < Result.size(); ++I) {569Result[I] = uint8_t(Hash);570Hash >>= 8;571}572return Result;573}
574
575std::optional<FileDigest> digestFile(const SourceManager &SM, FileID FID) {576bool Invalid = false;577llvm::StringRef Content = SM.getBufferData(FID, &Invalid);578if (Invalid)579return std::nullopt;580return digest(Content);581}
582
583format::FormatStyle getFormatStyleForFile(llvm::StringRef File,584llvm::StringRef Content,585const ThreadsafeFS &TFS,586bool 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.598if (!FormatFile)599Content = {};600auto Style = format::getStyle(format::DefaultFormatStyle, File,601format::DefaultFallbackStyle, Content,602TFS.view(/*CWD=*/std::nullopt).get());603if (!Style) {604log("getStyle() failed for file {0}: {1}. Fallback is LLVM style.", File,605Style.takeError());606return format::getLLVMStyle();607}608return *Style;609}
610
611llvm::Expected<tooling::Replacements>612cleanupAndFormat(StringRef Code, const tooling::Replacements &Replaces,613const format::FormatStyle &Style) {614auto CleanReplaces = cleanupAroundReplacements(Code, Replaces, Style);615if (!CleanReplaces)616return CleanReplaces;617return formatReplacements(Code, std::move(*CleanReplaces), Style);618}
619
620static void621lex(llvm::StringRef Code, const LangOptions &LangOpts,622llvm::function_ref<void(const syntax::Token &, const SourceManager &SM)>623Action) {624// FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!625std::string NullTerminatedCode = Code.str();626SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);627auto &SM = FileSM.get();628for (const auto &Tok : syntax::tokenize(SM.getMainFileID(), SM, LangOpts))629Action(Tok, SM);630}
631
632llvm::StringMap<unsigned> collectIdentifiers(llvm::StringRef Content,633const format::FormatStyle &Style) {634llvm::StringMap<unsigned> Identifiers;635auto LangOpt = format::getFormattingLangOpts(Style);636lex(Content, LangOpt, [&](const syntax::Token &Tok, const SourceManager &SM) {637if (Tok.kind() == tok::identifier)638++Identifiers[Tok.text(SM)];639// FIXME: Should this function really return keywords too ?640else if (const auto *Keyword = tok::getKeywordSpelling(Tok.kind()))641++Identifiers[Keyword];642});643return Identifiers;644}
645
646std::vector<Range> collectIdentifierRanges(llvm::StringRef Identifier,647llvm::StringRef Content,648const LangOptions &LangOpts) {649std::vector<Range> Ranges;650lex(Content, LangOpts,651[&](const syntax::Token &Tok, const SourceManager &SM) {652if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier)653return;654Ranges.push_back(halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));655});656return Ranges;657}
658
659bool isKeyword(llvm::StringRef NewName, const LangOptions &LangOpts) {660// Keywords are initialized in constructor.661clang::IdentifierTable KeywordsTable(LangOpts);662return KeywordsTable.find(NewName) != KeywordsTable.end();663}
664
665namespace {666struct NamespaceEvent {667enum {668BeginNamespace, // namespace <ns> {. Payload is resolved <ns>.669EndNamespace, // } // namespace <ns>. Payload is resolved *outer*670// namespace.671UsingDirective // using namespace <ns>. Payload is unresolved <ns>.672} Trigger;673std::string Payload;674Position Pos;675};676// Scans C++ source code for constructs that change the visible namespaces.
677void parseNamespaceEvents(llvm::StringRef Code, const LangOptions &LangOpts,678llvm::function_ref<void(NamespaceEvent)> Callback) {679
680// Stack of enclosing namespaces, e.g. {"clang", "clangd"}681std::vector<std::string> Enclosing; // Contains e.g. "clang", "clangd"682// Stack counts open braces. true if the brace opened a namespace.683llvm::BitVector BraceStack;684
685enum {686Default,687Namespace, // just saw 'namespace'688NamespaceName, // just saw 'namespace' NSName689Using, // just saw 'using'690UsingNamespace, // just saw 'using namespace'691UsingNamespaceName, // just saw 'using namespace' NSName692} State = Default;693std::string NSName;694
695NamespaceEvent Event;696lex(Code, LangOpts, [&](const syntax::Token &Tok, const SourceManager &SM) {697Event.Pos = sourceLocToPosition(SM, Tok.location());698switch (Tok.kind()) {699case tok::kw_using:700State = State == Default ? Using : Default;701break;702case tok::kw_namespace:703switch (State) {704case Using:705State = UsingNamespace;706break;707case Default:708State = Namespace;709break;710default:711State = Default;712break;713}714break;715case tok::identifier:716switch (State) {717case UsingNamespace:718NSName.clear();719[[fallthrough]];720case UsingNamespaceName:721NSName.append(Tok.text(SM).str());722State = UsingNamespaceName;723break;724case Namespace:725NSName.clear();726[[fallthrough]];727case NamespaceName:728NSName.append(Tok.text(SM).str());729State = NamespaceName;730break;731case Using:732case Default:733State = Default;734break;735}736break;737case tok::coloncolon:738// This can come at the beginning or in the middle of a namespace739// name.740switch (State) {741case UsingNamespace:742NSName.clear();743[[fallthrough]];744case UsingNamespaceName:745NSName.append("::");746State = UsingNamespaceName;747break;748case NamespaceName:749NSName.append("::");750State = NamespaceName;751break;752case Namespace: // Not legal here.753case Using:754case Default:755State = Default;756break;757}758break;759case tok::l_brace:760// Record which { started a namespace, so we know when } ends one.761if (State == NamespaceName) {762// Parsed: namespace <name> {763BraceStack.push_back(true);764Enclosing.push_back(NSName);765Event.Trigger = NamespaceEvent::BeginNamespace;766Event.Payload = llvm::join(Enclosing, "::");767Callback(Event);768} else {769// This case includes anonymous namespaces (State = Namespace).770// For our purposes, they're not namespaces and we ignore them.771BraceStack.push_back(false);772}773State = Default;774break;775case tok::r_brace:776// If braces are unmatched, we're going to be confused, but don't777// crash.778if (!BraceStack.empty()) {779if (BraceStack.back()) {780// Parsed: } // namespace781Enclosing.pop_back();782Event.Trigger = NamespaceEvent::EndNamespace;783Event.Payload = llvm::join(Enclosing, "::");784Callback(Event);785}786BraceStack.pop_back();787}788break;789case tok::semi:790if (State == UsingNamespaceName) {791// Parsed: using namespace <name> ;792Event.Trigger = NamespaceEvent::UsingDirective;793Event.Payload = std::move(NSName);794Callback(Event);795}796State = Default;797break;798default:799State = Default;800break;801}802});803}
804
805// Returns the prefix namespaces of NS: {"" ... NS}.
806llvm::SmallVector<llvm::StringRef> ancestorNamespaces(llvm::StringRef NS) {807llvm::SmallVector<llvm::StringRef> Results;808Results.push_back(NS.take_front(0));809NS.split(Results, "::", /*MaxSplit=*/-1, /*KeepEmpty=*/false);810for (llvm::StringRef &R : Results)811R = NS.take_front(R.end() - NS.begin());812return Results;813}
814
815// Checks whether \p FileName is a valid spelling of main file.
816bool isMainFile(llvm::StringRef FileName, const SourceManager &SM) {817auto FE = SM.getFileManager().getFile(FileName);818return FE && *FE == SM.getFileEntryForID(SM.getMainFileID());819}
820
821} // namespace822
823std::vector<std::string> visibleNamespaces(llvm::StringRef Code,824const LangOptions &LangOpts) {825std::string Current;826// Map from namespace to (resolved) namespaces introduced via using directive.827llvm::StringMap<llvm::StringSet<>> UsingDirectives;828
829parseNamespaceEvents(Code, LangOpts, [&](NamespaceEvent Event) {830llvm::StringRef NS = Event.Payload;831switch (Event.Trigger) {832case NamespaceEvent::BeginNamespace:833case NamespaceEvent::EndNamespace:834Current = std::move(Event.Payload);835break;836case NamespaceEvent::UsingDirective:837if (NS.consume_front("::"))838UsingDirectives[Current].insert(NS);839else {840for (llvm::StringRef Enclosing : ancestorNamespaces(Current)) {841if (Enclosing.empty())842UsingDirectives[Current].insert(NS);843else844UsingDirectives[Current].insert((Enclosing + "::" + NS).str());845}846}847break;848}849});850
851std::vector<std::string> Found;852for (llvm::StringRef Enclosing : ancestorNamespaces(Current)) {853Found.push_back(std::string(Enclosing));854auto It = UsingDirectives.find(Enclosing);855if (It != UsingDirectives.end())856for (const auto &Used : It->second)857Found.push_back(std::string(Used.getKey()));858}859
860llvm::sort(Found, [&](const std::string &LHS, const std::string &RHS) {861if (Current == RHS)862return false;863if (Current == LHS)864return true;865return LHS < RHS;866});867Found.erase(std::unique(Found.begin(), Found.end()), Found.end());868return 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)875static constexpr int MinWordLength = 4;876
877std::vector<CharRole> Roles(Content.size());878calculateRoles(Content, Roles);879
880llvm::StringSet<> Result;881llvm::SmallString<256> Word;882auto Flush = [&] {883if (Word.size() >= MinWordLength) {884for (char &C : Word)885C = llvm::toLower(C);886Result.insert(Word);887}888Word.clear();889};890for (unsigned I = 0; I < Content.size(); ++I) {891switch (Roles[I]) {892case Head:893Flush();894[[fallthrough]];895case Tail:896Word.push_back(Content[I]);897break;898case Unknown:899case Separator:900Flush();901break;902}903}904Flush();905
906return Result;907}
908
909static bool isLikelyIdentifier(llvm::StringRef Word, llvm::StringRef Before,910llvm::StringRef After) {911// `foo` is an identifier.912if (Before.ends_with("`") && After.starts_with("`"))913return true;914// In foo::bar, both foo and bar are identifiers.915if (Before.ends_with("::") || After.starts_with("::"))916return 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.920Before = Before.take_back(100); // Don't search too far back.921auto Pos = Before.find_last_of("\\@");922if (Pos != llvm::StringRef::npos) {923llvm::StringRef Tag = Before.substr(Pos + 1).rtrim(' ');924if (Tag == "p" || Tag == "c" || Tag == "class" || Tag == "tparam" ||925Tag == "param" || Tag == "param[in]" || Tag == "param[out]" ||926Tag == "param[in,out]" || Tag == "retval" || Tag == "throw" ||927Tag == "throws" || Tag == "link")928return true;929}930
931// Word contains underscore.932// This handles things like snake_case and MACRO_CASE.933if (Word.contains('_')) {934return 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".940bool HasLower = Word.find_if(clang::isLowercase) != StringRef::npos;941bool HasUpper = Word.substr(1).find_if(clang::isUppercase) != StringRef::npos;942if (HasLower && HasUpper) {943return true;944}945// FIXME: consider mid-sentence Capitalization?946return false;947}
948
949std::optional<SpelledWord> SpelledWord::touching(SourceLocation SpelledLoc,950const syntax::TokenBuffer &TB,951const LangOptions &LangOpts) {952const auto &SM = TB.sourceManager();953auto Touching = syntax::spelledTokensTouching(SpelledLoc, TB);954for (const auto &T : Touching) {955// If the token is an identifier or a keyword, don't use any heuristics.956if (tok::isAnyIdentifier(T.kind()) || tok::getKeywordSpelling(T.kind())) {957SpelledWord Result;958Result.Location = T.location();959Result.Text = T.text(SM);960Result.LikelyIdentifier = tok::isAnyIdentifier(T.kind());961Result.PartOfSpelledToken = &T;962Result.SpelledToken = &T;963auto Expanded =964TB.expandedTokens(SM.getMacroArgExpandedLocation(T.location()));965if (Expanded.size() == 1 && Expanded.front().text(SM) == Result.Text)966Result.ExpandedToken = &Expanded.front();967return Result;968}969}970FileID File;971unsigned Offset;972std::tie(File, Offset) = SM.getDecomposedLoc(SpelledLoc);973bool Invalid = false;974llvm::StringRef Code = SM.getBufferData(File, &Invalid);975if (Invalid)976return std::nullopt;977unsigned B = Offset, E = Offset;978while (B > 0 && isAsciiIdentifierContinue(Code[B - 1]))979--B;980while (E < Code.size() && isAsciiIdentifierContinue(Code[E]))981++E;982if (B == E)983return std::nullopt;984
985SpelledWord Result;986Result.Location = SM.getComposedLoc(File, B);987Result.Text = Code.slice(B, E);988Result.LikelyIdentifier =989isLikelyIdentifier(Result.Text, Code.substr(0, B), Code.substr(E)) &&990// should not be a keyword991tok::isAnyIdentifier(992IdentifierTable(LangOpts).get(Result.Text).getTokenID());993for (const auto &T : Touching)994if (T.location() <= Result.Location)995Result.PartOfSpelledToken = &T;996return Result;997}
998
999std::optional<DefinedMacro> locateMacroAt(const syntax::Token &SpelledTok,1000Preprocessor &PP) {1001if (SpelledTok.kind() != tok::identifier)1002return std::nullopt;1003SourceLocation Loc = SpelledTok.location();1004assert(Loc.isFileID());1005const auto &SM = PP.getSourceManager();1006IdentifierInfo *IdentifierInfo = PP.getIdentifierInfo(SpelledTok.text(SM));1007if (!IdentifierInfo || !IdentifierInfo->hadMacroDefinition())1008return 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.1018FileID FID = SM.getFileID(Loc);1019assert(Loc != SM.getLocForEndOfFile(FID));1020SourceLocation JustAfterToken = Loc.getLocWithOffset(1);1021auto *MacroInfo =1022PP.getMacroDefinitionAtLoc(IdentifierInfo, JustAfterToken).getMacroInfo();1023if (!MacroInfo && SM.getLocForStartOfFile(FID) != Loc) {1024SourceLocation JustBeforeToken = Loc.getLocWithOffset(-1);1025MacroInfo = PP.getMacroDefinitionAtLoc(IdentifierInfo, JustBeforeToken)1026.getMacroInfo();1027}1028if (!MacroInfo) {1029return std::nullopt;1030}1031return DefinedMacro{1032IdentifierInfo->getName(), MacroInfo,1033translatePreamblePatchLocation(MacroInfo->getDefinitionLoc(), SM)};1034}
1035
1036llvm::Expected<std::string> Edit::apply() const {1037return tooling::applyAllReplacements(InitialCode, Replacements);1038}
1039
1040std::vector<TextEdit> Edit::asTextEdits() const {1041return 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.1047auto LHS = llvm::MemoryBuffer::getMemBuffer(Code);1048llvm::line_iterator LHSIt(*LHS, /*SkipBlanks=*/false);1049
1050auto RHS = llvm::MemoryBuffer::getMemBuffer(InitialCode);1051llvm::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.1057while (!LHSIt.is_at_eof() && !RHSIt.is_at_eof()) {1058if (*LHSIt != *RHSIt)1059return 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.1067while (!LHSIt.is_at_eof()) {1068if (!LHSIt->empty())1069return false;1070++LHSIt;1071}1072while (!RHSIt.is_at_eof()) {1073if (!RHSIt->empty())1074return false;1075++RHSIt;1076}1077return true;1078}
1079
1080llvm::Error reformatEdit(Edit &E, const format::FormatStyle &Style) {1081if (auto NewEdits = cleanupAndFormat(E.InitialCode, E.Replacements, Style))1082E.Replacements = std::move(*NewEdits);1083else1084return NewEdits.takeError();1085return 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 exact
1091// string, with whitespace and newlines well-defined. But internally many
1092// editors treat text as an array of lines, and there can be ambiguity over
1093// whether the last line ends with a newline or not.
1094//
1095// This confusion can lead to incorrect edits being sent. Failing to apply them
1096// 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 the
1100// editor refers to the start of the next line (an impossible location), then
1101// we silently add a newline to make this valid.
1102// We will still validate that the rangeLength is correct, *including* the
1103// inferred newline.
1104//
1105// See https://github.com/neovim/neovim/issues/17085
1106static void inferFinalNewline(llvm::Expected<size_t> &Err,1107std::string &Contents, const Position &Pos) {1108if (Err)1109return;1110if (!Contents.empty() && Contents.back() == '\n')1111return;1112if (Pos.character != 0)1113return;1114if (Pos.line != llvm::count(Contents, '\n') + 1)1115return;1116log("Editor sent invalid change coordinates, inferring newline at EOF");1117Contents.push_back('\n');1118consumeError(Err.takeError());1119Err = Contents.size();1120}
1121
1122llvm::Error applyChange(std::string &Contents,1123const TextDocumentContentChangeEvent &Change) {1124if (!Change.range) {1125Contents = Change.text;1126return llvm::Error::success();1127}1128
1129const Position &Start = Change.range->start;1130llvm::Expected<size_t> StartIndex = positionToOffset(Contents, Start, false);1131inferFinalNewline(StartIndex, Contents, Start);1132if (!StartIndex)1133return StartIndex.takeError();1134
1135const Position &End = Change.range->end;1136llvm::Expected<size_t> EndIndex = positionToOffset(Contents, End, false);1137inferFinalNewline(EndIndex, Contents, End);1138if (!EndIndex)1139return EndIndex.takeError();1140
1141if (*EndIndex < *StartIndex)1142return error(llvm::errc::invalid_argument,1143"Range's end position ({0}) is before start position ({1})",1144End, 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.1153ssize_t ComputedRangeLength =1154lspLength(Contents.substr(*StartIndex, *EndIndex - *StartIndex));1155
1156if (Change.rangeLength && ComputedRangeLength != *Change.rangeLength)1157return error(llvm::errc::invalid_argument,1158"Change's rangeLength ({0}) doesn't match the "1159"computed range length ({1}).",1160*Change.rangeLength, ComputedRangeLength);1161
1162Contents.replace(*StartIndex, *EndIndex - *StartIndex, Change.text);1163
1164return llvm::Error::success();1165}
1166
1167EligibleRegion getEligiblePoints(llvm::StringRef Code,1168llvm::StringRef FullyQualifiedName,1169const LangOptions &LangOpts) {1170EligibleRegion ER;1171// Start with global namespace.1172std::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.1176parseNamespaceEvents(Code, LangOpts, [&](NamespaceEvent Event) {1177// Using Directives only introduces declarations to current scope, they do1178// not change the current namespace, so skip them.1179if (Event.Trigger == NamespaceEvent::UsingDirective)1180return;1181// Do not qualify the global namespace.1182if (!Event.Payload.empty())1183Event.Payload.append("::");1184
1185std::string CurrentNamespace;1186if (Event.Trigger == NamespaceEvent::BeginNamespace) {1187Enclosing.emplace_back(std::move(Event.Payload));1188CurrentNamespace = 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.1195CurrentNamespace = std::move(Enclosing.back());1196Enclosing.pop_back();1197assert(Enclosing.back() == Event.Payload);1198}1199
1200// Ignore namespaces that are not a prefix of the target.1201if (!FullyQualifiedName.starts_with(CurrentNamespace))1202return;1203
1204// Prefer the namespace that shares the longest prefix with target.1205if (CurrentNamespace.size() > ER.EnclosingNamespace.size()) {1206ER.EligiblePoints.clear();1207ER.EnclosingNamespace = CurrentNamespace;1208}1209if (CurrentNamespace.size() == ER.EnclosingNamespace.size())1210ER.EligiblePoints.emplace_back(std::move(Event.Pos));1211});1212// If there were no shared namespaces just return EOF.1213if (ER.EligiblePoints.empty()) {1214assert(ER.EnclosingNamespace.empty());1215ER.EligiblePoints.emplace_back(offsetToPosition(Code, Code.size()));1216}1217return ER;1218}
1219
1220bool isHeaderFile(llvm::StringRef FileName,1221std::optional<LangOptions> LangOpts) {1222// Respect the langOpts, for non-file-extension cases, e.g. standard library1223// files.1224if (LangOpts && LangOpts->IsHeaderFile)1225return true;1226namespace types = clang::driver::types;1227auto Lang = types::lookupTypeForExtension(1228llvm::sys::path::extension(FileName).substr(1));1229return Lang != types::TY_INVALID && types::onlyPrecompileType(Lang);1230}
1231
1232bool isProtoFile(SourceLocation Loc, const SourceManager &SM) {1233auto FileName = SM.getFilename(Loc);1234if (!FileName.ends_with(".proto.h") && !FileName.ends_with(".pb.h"))1235return false;1236auto FID = SM.getFileID(Loc);1237// All proto generated headers should start with this line.1238static const char *ProtoHeaderComment =1239"// Generated by the protocol buffer compiler. DO NOT EDIT!";1240// Double check that this is an actual protobuf header.1241return SM.getBufferData(FID).starts_with(ProtoHeaderComment);1242}
1243
1244SourceLocation translatePreamblePatchLocation(SourceLocation Loc,1245const SourceManager &SM) {1246auto DefFile = SM.getFileID(Loc);1247if (auto FE = SM.getFileEntryRefForID(DefFile)) {1248auto IncludeLoc = SM.getIncludeLoc(DefFile);1249// Preamble patch is included inside the builtin file.1250if (IncludeLoc.isValid() && SM.isWrittenInBuiltinFile(IncludeLoc) &&1251FE->getName().ends_with(PreamblePatch::HeaderName)) {1252auto Presumed = SM.getPresumedLoc(Loc);1253// Check that line directive is pointing at main file.1254if (Presumed.isValid() && Presumed.getFileID().isInvalid() &&1255isMainFile(Presumed.getFilename(), SM)) {1256Loc = SM.translateLineCol(SM.getMainFileID(), Presumed.getLine(),1257Presumed.getColumn());1258}1259}1260}1261return Loc;1262}
1263
1264clangd::Range rangeTillEOL(llvm::StringRef Code, unsigned HashOffset) {1265clangd::Range Result;1266Result.end = Result.start = offsetToPosition(Code, HashOffset);1267
1268// Span the warning until the EOL or EOF.1269Result.end.character +=1270lspLength(Code.drop_front(HashOffset).take_until([](char C) {1271return C == '\n' || C == '\r';1272}));1273return Result;1274}
1275} // namespace clangd1276} // namespace clang1277