1 //===--- SemanticSelection.cpp -----------------------------------*- 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 
9 #include "SemanticSelection.h"
10 #include "ParsedAST.h"
11 #include "Protocol.h"
12 #include "Selection.h"
13 #include "SourceCode.h"
14 #include "clang-pseudo/Bracket.h"
15 #include "clang-pseudo/DirectiveTree.h"
16 #include "clang-pseudo/Token.h"
17 #include "clang/AST/DeclBase.h"
18 #include "clang/Basic/SourceLocation.h"
19 #include "clang/Basic/SourceManager.h"
20 #include "clang/Tooling/Syntax/BuildTree.h"
21 #include "clang/Tooling/Syntax/Nodes.h"
22 #include "clang/Tooling/Syntax/TokenBufferTokenManager.h"
23 #include "clang/Tooling/Syntax/Tree.h"
24 #include "llvm/ADT/ArrayRef.h"
25 #include "llvm/Support/Casting.h"
26 #include "llvm/Support/Error.h"
27 #include <queue>
28 #include <vector>
29 
30 namespace clang {
31 namespace clangd {
32 namespace {
33 
34 // Adds Range \p R to the Result if it is distinct from the last added Range.
35 // Assumes that only consecutive ranges can coincide.
addIfDistinct(const Range & R,std::vector<Range> & Result)36 void addIfDistinct(const Range &R, std::vector<Range> &Result) {
37   if (Result.empty() || Result.back() != R) {
38     Result.push_back(R);
39   }
40 }
41 
toFoldingRange(SourceRange SR,const SourceManager & SM)42 llvm::Optional<FoldingRange> toFoldingRange(SourceRange SR,
43                                             const SourceManager &SM) {
44   const auto Begin = SM.getDecomposedLoc(SR.getBegin()),
45              End = SM.getDecomposedLoc(SR.getEnd());
46   // Do not produce folding ranges if either range ends is not within the main
47   // file. Macros have their own FileID so this also checks if locations are not
48   // within the macros.
49   if ((Begin.first != SM.getMainFileID()) || (End.first != SM.getMainFileID()))
50     return llvm::None;
51   FoldingRange Range;
52   Range.startCharacter = SM.getColumnNumber(Begin.first, Begin.second) - 1;
53   Range.startLine = SM.getLineNumber(Begin.first, Begin.second) - 1;
54   Range.endCharacter = SM.getColumnNumber(End.first, End.second) - 1;
55   Range.endLine = SM.getLineNumber(End.first, End.second) - 1;
56   return Range;
57 }
58 
59 llvm::Optional<FoldingRange>
extractFoldingRange(const syntax::Node * Node,const syntax::TokenBufferTokenManager & TM)60 extractFoldingRange(const syntax::Node *Node,
61                     const syntax::TokenBufferTokenManager &TM) {
62   if (const auto *Stmt = dyn_cast<syntax::CompoundStatement>(Node)) {
63     const auto *LBrace = cast_or_null<syntax::Leaf>(
64         Stmt->findChild(syntax::NodeRole::OpenParen));
65     // FIXME(kirillbobyrev): This should find the last child. Compound
66     // statements have only one pair of braces so this is valid but for other
67     // node kinds it might not be correct.
68     const auto *RBrace = cast_or_null<syntax::Leaf>(
69         Stmt->findChild(syntax::NodeRole::CloseParen));
70     if (!LBrace || !RBrace)
71       return llvm::None;
72     // Fold the entire range within braces, including whitespace.
73     const SourceLocation LBraceLocInfo =
74                              TM.getToken(LBrace->getTokenKey())->endLocation(),
75                          RBraceLocInfo =
76                              TM.getToken(RBrace->getTokenKey())->location();
77     auto Range = toFoldingRange(SourceRange(LBraceLocInfo, RBraceLocInfo),
78                                 TM.sourceManager());
79     // Do not generate folding range for compound statements without any
80     // nodes and newlines.
81     if (Range && Range->startLine != Range->endLine)
82       return Range;
83   }
84   return llvm::None;
85 }
86 
87 // Traverse the tree and collect folding ranges along the way.
88 std::vector<FoldingRange>
collectFoldingRanges(const syntax::Node * Root,const syntax::TokenBufferTokenManager & TM)89 collectFoldingRanges(const syntax::Node *Root,
90                      const syntax::TokenBufferTokenManager &TM) {
91   std::queue<const syntax::Node *> Nodes;
92   Nodes.push(Root);
93   std::vector<FoldingRange> Result;
94   while (!Nodes.empty()) {
95     const syntax::Node *Node = Nodes.front();
96     Nodes.pop();
97     const auto Range = extractFoldingRange(Node, TM);
98     if (Range)
99       Result.push_back(*Range);
100     if (const auto *T = dyn_cast<syntax::Tree>(Node))
101       for (const auto *NextNode = T->getFirstChild(); NextNode;
102            NextNode = NextNode->getNextSibling())
103         Nodes.push(NextNode);
104   }
105   return Result;
106 }
107 
108 } // namespace
109 
getSemanticRanges(ParsedAST & AST,Position Pos)110 llvm::Expected<SelectionRange> getSemanticRanges(ParsedAST &AST, Position Pos) {
111   std::vector<Range> Ranges;
112   const auto &SM = AST.getSourceManager();
113   const auto &LangOpts = AST.getLangOpts();
114 
115   auto FID = SM.getMainFileID();
116   auto Offset = positionToOffset(SM.getBufferData(FID), Pos);
117   if (!Offset) {
118     return Offset.takeError();
119   }
120 
121   // Get node under the cursor.
122   SelectionTree ST = SelectionTree::createRight(
123       AST.getASTContext(), AST.getTokens(), *Offset, *Offset);
124   for (const auto *Node = ST.commonAncestor(); Node != nullptr;
125        Node = Node->Parent) {
126     if (const Decl *D = Node->ASTNode.get<Decl>()) {
127       if (llvm::isa<TranslationUnitDecl>(D)) {
128         break;
129       }
130     }
131 
132     auto SR = toHalfOpenFileRange(SM, LangOpts, Node->ASTNode.getSourceRange());
133     if (!SR || SM.getFileID(SR->getBegin()) != SM.getMainFileID()) {
134       continue;
135     }
136     Range R;
137     R.start = sourceLocToPosition(SM, SR->getBegin());
138     R.end = sourceLocToPosition(SM, SR->getEnd());
139     addIfDistinct(R, Ranges);
140   }
141 
142   if (Ranges.empty()) {
143     // LSP provides no way to signal "the point is not within a semantic range".
144     // Return an empty range at the point.
145     SelectionRange Empty;
146     Empty.range.start = Empty.range.end = Pos;
147     return std::move(Empty);
148   }
149 
150   // Convert to the LSP linked-list representation.
151   SelectionRange Head;
152   Head.range = std::move(Ranges.front());
153   SelectionRange *Tail = &Head;
154   for (auto &Range :
155        llvm::makeMutableArrayRef(Ranges.data(), Ranges.size()).drop_front()) {
156     Tail->parent = std::make_unique<SelectionRange>();
157     Tail = Tail->parent.get();
158     Tail->range = std::move(Range);
159   }
160 
161   return std::move(Head);
162 }
163 
164 // FIXME(kirillbobyrev): Collect comments, PP conditional regions, includes and
165 // other code regions (e.g. public/private/protected sections of classes,
166 // control flow statement bodies).
167 // Related issue: https://github.com/clangd/clangd/issues/310
getFoldingRanges(ParsedAST & AST)168 llvm::Expected<std::vector<FoldingRange>> getFoldingRanges(ParsedAST &AST) {
169   syntax::Arena A;
170   syntax::TokenBufferTokenManager TM(AST.getTokens(), AST.getLangOpts(),
171                                      AST.getSourceManager());
172   const auto *SyntaxTree = syntax::buildSyntaxTree(A, TM, AST.getASTContext());
173   return collectFoldingRanges(SyntaxTree, TM);
174 }
175 
176 // FIXME(kirillbobyrev): Collect comments, PP conditional regions, includes and
177 // other code regions (e.g. public/private/protected sections of classes,
178 // control flow statement bodies).
179 // Related issue: https://github.com/clangd/clangd/issues/310
180 llvm::Expected<std::vector<FoldingRange>>
getFoldingRanges(const std::string & Code)181 getFoldingRanges(const std::string &Code) {
182   auto OrigStream = clang::pseudo::lex(Code, clang::pseudo::genericLangOpts());
183 
184   auto DirectiveStructure = clang::pseudo::DirectiveTree::parse(OrigStream);
185   clang::pseudo::chooseConditionalBranches(DirectiveStructure, OrigStream);
186 
187   // FIXME: Provide ranges in the disabled-PP regions as well.
188   auto Preprocessed = DirectiveStructure.stripDirectives(OrigStream);
189 
190   auto ParseableStream = cook(Preprocessed, clang::pseudo::genericLangOpts());
191   pseudo::pairBrackets(ParseableStream);
192 
193   std::vector<FoldingRange> Result;
194   for (const auto &Tok : ParseableStream.tokens()) {
195     if (auto *Paired = Tok.pair()) {
196       // Process only token at the start of the range. Avoid ranges on a single
197       // line.
198       if (Tok.Line < Paired->Line) {
199         Position Start = offsetToPosition(
200             Code,
201             OrigStream.tokens()[Tok.OriginalIndex].text().data() - Code.data());
202         Position End = offsetToPosition(
203             Code, OrigStream.tokens()[Paired->OriginalIndex].text().data() -
204                       Code.data());
205         FoldingRange FR;
206         FR.startLine = Start.line;
207         FR.startCharacter = Start.character + 1;
208         FR.endLine = End.line;
209         FR.endCharacter = End.character;
210         Result.push_back(FR);
211       }
212     }
213   }
214   return Result;
215 }
216 
217 } // namespace clangd
218 } // namespace clang
219