xref: /llvm-project/mlir/lib/Tools/lsp-server-support/SourceMgrUtils.cpp (revision abaa79b25dde740d5b54adab463432bee2840c85)
15de12bb7SRiver Riddle //===--- SourceMgrUtils.cpp - SourceMgr LSP Utils -------------------------===//
25de12bb7SRiver Riddle //
35de12bb7SRiver Riddle // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
45de12bb7SRiver Riddle // See https://llvm.org/LICENSE.txt for license information.
55de12bb7SRiver Riddle // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
65de12bb7SRiver Riddle //
75de12bb7SRiver Riddle //===----------------------------------------------------------------------===//
85de12bb7SRiver Riddle 
9305d7185SRiver Riddle #include "mlir/Tools/lsp-server-support/SourceMgrUtils.h"
10b0abd489SElliot Goodrich #include "llvm/ADT/StringExtras.h"
11682ca00eSRiver Riddle #include "llvm/Support/Path.h"
12a1fe1f5fSKazu Hirata #include <optional>
135de12bb7SRiver Riddle 
145de12bb7SRiver Riddle using namespace mlir;
155de12bb7SRiver Riddle using namespace mlir::lsp;
165de12bb7SRiver Riddle 
17682ca00eSRiver Riddle //===----------------------------------------------------------------------===//
18682ca00eSRiver Riddle // Utils
19682ca00eSRiver Riddle //===----------------------------------------------------------------------===//
20682ca00eSRiver Riddle 
215de12bb7SRiver Riddle /// Find the end of a string whose contents start at the given `curPtr`. Returns
225de12bb7SRiver Riddle /// the position at the end of the string, after a terminal or invalid character
235de12bb7SRiver Riddle /// (e.g. `"` or `\0`).
lexLocStringTok(const char * curPtr)245de12bb7SRiver Riddle static const char *lexLocStringTok(const char *curPtr) {
255de12bb7SRiver Riddle   while (char c = *curPtr++) {
265de12bb7SRiver Riddle     // Check for various terminal characters.
275de12bb7SRiver Riddle     if (StringRef("\"\n\v\f").contains(c))
285de12bb7SRiver Riddle       return curPtr;
295de12bb7SRiver Riddle 
305de12bb7SRiver Riddle     // Check for escape sequences.
315de12bb7SRiver Riddle     if (c == '\\') {
325de12bb7SRiver Riddle       // Check a few known escapes and \xx hex digits.
335de12bb7SRiver Riddle       if (*curPtr == '"' || *curPtr == '\\' || *curPtr == 'n' || *curPtr == 't')
345de12bb7SRiver Riddle         ++curPtr;
355de12bb7SRiver Riddle       else if (llvm::isHexDigit(*curPtr) && llvm::isHexDigit(curPtr[1]))
365de12bb7SRiver Riddle         curPtr += 2;
375de12bb7SRiver Riddle       else
385de12bb7SRiver Riddle         return curPtr;
395de12bb7SRiver Riddle     }
405de12bb7SRiver Riddle   }
415de12bb7SRiver Riddle 
425de12bb7SRiver Riddle   // If we hit this point, we've reached the end of the buffer. Update the end
435de12bb7SRiver Riddle   // pointer to not point past the buffer.
445de12bb7SRiver Riddle   return curPtr - 1;
455de12bb7SRiver Riddle }
465de12bb7SRiver Riddle 
convertTokenLocToRange(SMLoc loc,StringRef identifierChars)47305d7185SRiver Riddle SMRange lsp::convertTokenLocToRange(SMLoc loc, StringRef identifierChars) {
485de12bb7SRiver Riddle   if (!loc.isValid())
495de12bb7SRiver Riddle     return SMRange();
505de12bb7SRiver Riddle   const char *curPtr = loc.getPointer();
515de12bb7SRiver Riddle 
525de12bb7SRiver Riddle   // Check if this is a string token.
535de12bb7SRiver Riddle   if (*curPtr == '"') {
545de12bb7SRiver Riddle     curPtr = lexLocStringTok(curPtr + 1);
555de12bb7SRiver Riddle 
565de12bb7SRiver Riddle     // Otherwise, default to handling an identifier.
575de12bb7SRiver Riddle   } else {
585de12bb7SRiver Riddle     // Return if the given character is a valid identifier character.
59305d7185SRiver Riddle     auto isIdentifierChar = [=](char c) {
60305d7185SRiver Riddle       return isalnum(c) || c == '_' || identifierChars.contains(c);
615de12bb7SRiver Riddle     };
625de12bb7SRiver Riddle 
635de12bb7SRiver Riddle     while (*curPtr && isIdentifierChar(*(++curPtr)))
645de12bb7SRiver Riddle       continue;
655de12bb7SRiver Riddle   }
665de12bb7SRiver Riddle 
675de12bb7SRiver Riddle   return SMRange(loc, SMLoc::getFromPointer(curPtr));
685de12bb7SRiver Riddle }
69682ca00eSRiver Riddle 
700a81ace0SKazu Hirata std::optional<std::string>
extractSourceDocComment(llvm::SourceMgr & sourceMgr,SMLoc loc)710a81ace0SKazu Hirata lsp::extractSourceDocComment(llvm::SourceMgr &sourceMgr, SMLoc loc) {
723e2ad376SRiver Riddle   // This is a heuristic, and isn't intended to cover every case, but should
733e2ad376SRiver Riddle   // cover the most common. We essentially look for a comment preceding the
743e2ad376SRiver Riddle   // line, and if we find one, use that as the documentation.
753e2ad376SRiver Riddle   if (!loc.isValid())
761a36588eSKazu Hirata     return std::nullopt;
773e2ad376SRiver Riddle   int bufferId = sourceMgr.FindBufferContainingLoc(loc);
783e2ad376SRiver Riddle   if (bufferId == 0)
791a36588eSKazu Hirata     return std::nullopt;
803e2ad376SRiver Riddle   const char *bufferStart =
813e2ad376SRiver Riddle       sourceMgr.getMemoryBuffer(bufferId)->getBufferStart();
823e2ad376SRiver Riddle   StringRef buffer(bufferStart, loc.getPointer() - bufferStart);
833e2ad376SRiver Riddle 
843e2ad376SRiver Riddle   // Pop the last line from the buffer string.
850a81ace0SKazu Hirata   auto popLastLine = [&]() -> std::optional<StringRef> {
8633b51588Sserge-sans-paille     size_t newlineOffset = buffer.find_last_of('\n');
873e2ad376SRiver Riddle     if (newlineOffset == StringRef::npos)
881a36588eSKazu Hirata       return std::nullopt;
893e2ad376SRiver Riddle     StringRef lastLine = buffer.drop_front(newlineOffset).trim();
903e2ad376SRiver Riddle     buffer = buffer.take_front(newlineOffset);
913e2ad376SRiver Riddle     return lastLine;
923e2ad376SRiver Riddle   };
933e2ad376SRiver Riddle 
943e2ad376SRiver Riddle   // Try to pop the current line.
953e2ad376SRiver Riddle   if (!popLastLine())
961a36588eSKazu Hirata     return std::nullopt;
973e2ad376SRiver Riddle 
983e2ad376SRiver Riddle   // Try to parse a comment string from the source file.
993e2ad376SRiver Riddle   SmallVector<StringRef> commentLines;
1000a81ace0SKazu Hirata   while (std::optional<StringRef> line = popLastLine()) {
1013e2ad376SRiver Riddle     // Check for a comment at the beginning of the line.
10288d319a2SKazu Hirata     if (!line->starts_with("//"))
1033e2ad376SRiver Riddle       break;
1043e2ad376SRiver Riddle 
1053e2ad376SRiver Riddle     // Extract the document string from the comment.
106*abaa79b2SKazu Hirata     commentLines.push_back(line->ltrim('/'));
1073e2ad376SRiver Riddle   }
1083e2ad376SRiver Riddle 
1093e2ad376SRiver Riddle   if (commentLines.empty())
1101a36588eSKazu Hirata     return std::nullopt;
1113e2ad376SRiver Riddle   return llvm::join(llvm::reverse(commentLines), "\n");
1123e2ad376SRiver Riddle }
1133e2ad376SRiver Riddle 
contains(SMRange range,SMLoc loc)11499e24123SRiver Riddle bool lsp::contains(SMRange range, SMLoc loc) {
11599e24123SRiver Riddle   return range.Start.getPointer() <= loc.getPointer() &&
11699e24123SRiver Riddle          loc.getPointer() < range.End.getPointer();
11799e24123SRiver Riddle }
11899e24123SRiver Riddle 
119682ca00eSRiver Riddle //===----------------------------------------------------------------------===//
120682ca00eSRiver Riddle // SourceMgrInclude
121682ca00eSRiver Riddle //===----------------------------------------------------------------------===//
122682ca00eSRiver Riddle 
buildHover() const123682ca00eSRiver Riddle Hover SourceMgrInclude::buildHover() const {
124682ca00eSRiver Riddle   Hover hover(range);
125682ca00eSRiver Riddle   {
126682ca00eSRiver Riddle     llvm::raw_string_ostream hoverOS(hover.contents.value);
127682ca00eSRiver Riddle     hoverOS << "`" << llvm::sys::path::filename(uri.file()) << "`\n***\n"
128682ca00eSRiver Riddle             << uri.file();
129682ca00eSRiver Riddle   }
130682ca00eSRiver Riddle   return hover;
131682ca00eSRiver Riddle }
132682ca00eSRiver Riddle 
gatherIncludeFiles(llvm::SourceMgr & sourceMgr,SmallVectorImpl<SourceMgrInclude> & includes)133682ca00eSRiver Riddle void lsp::gatherIncludeFiles(llvm::SourceMgr &sourceMgr,
134682ca00eSRiver Riddle                              SmallVectorImpl<SourceMgrInclude> &includes) {
135682ca00eSRiver Riddle   for (unsigned i = 1, e = sourceMgr.getNumBuffers(); i < e; ++i) {
136682ca00eSRiver Riddle     // Check to see if this file was included by the main file.
137682ca00eSRiver Riddle     SMLoc includeLoc = sourceMgr.getBufferInfo(i + 1).IncludeLoc;
138682ca00eSRiver Riddle     if (!includeLoc.isValid() || sourceMgr.FindBufferContainingLoc(
139682ca00eSRiver Riddle                                      includeLoc) != sourceMgr.getMainFileID())
140682ca00eSRiver Riddle       continue;
141682ca00eSRiver Riddle 
142682ca00eSRiver Riddle     // Try to build a URI for this file path.
143682ca00eSRiver Riddle     auto *buffer = sourceMgr.getMemoryBuffer(i + 1);
144682ca00eSRiver Riddle     llvm::SmallString<256> path(buffer->getBufferIdentifier());
145682ca00eSRiver Riddle     llvm::sys::path::remove_dots(path, /*remove_dot_dot=*/true);
146682ca00eSRiver Riddle 
147682ca00eSRiver Riddle     llvm::Expected<URIForFile> includedFileURI = URIForFile::fromFile(path);
148682ca00eSRiver Riddle     if (!includedFileURI)
149682ca00eSRiver Riddle       continue;
150682ca00eSRiver Riddle 
151682ca00eSRiver Riddle     // Find the end of the include token.
152682ca00eSRiver Riddle     const char *includeStart = includeLoc.getPointer() - 2;
153682ca00eSRiver Riddle     while (*(--includeStart) != '\"')
154682ca00eSRiver Riddle       continue;
155682ca00eSRiver Riddle 
156682ca00eSRiver Riddle     // Push this include.
157682ca00eSRiver Riddle     SMRange includeRange(SMLoc::getFromPointer(includeStart), includeLoc);
158682ca00eSRiver Riddle     includes.emplace_back(*includedFileURI, Range(sourceMgr, includeRange));
159682ca00eSRiver Riddle   }
160682ca00eSRiver Riddle }
161