1 /**
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "navigate_to.h"
17 #include "quick_info.h"
18 #include "internal_api.h"
19 #include "lsp/include/get_adjusted_location.h"
20 #include "public/public.h"
21
22 namespace ark::es2panda::lsp {
23
PatternMatcher(const std::string & pattern,bool isCaseSensitive)24 PatternMatcher::PatternMatcher(const std::string &pattern, bool isCaseSensitive)
25 : pattern_(pattern), isCaseSensitive_(isCaseSensitive)
26 {
27 try {
28 regexPattern_ = isCaseSensitive ? std::regex(pattern) : std::regex(pattern, std::regex_constants::icase);
29 } catch (const std::regex_error &) {
30 regexPattern_ = std::nullopt;
31 }
32 }
33
IsPatternValid() const34 bool PatternMatcher::IsPatternValid() const
35 {
36 return regexPattern_.has_value();
37 }
38
MatchesExact(const std::string & candidate) const39 bool PatternMatcher::MatchesExact(const std::string &candidate) const
40 {
41 return regexPattern_ && std::regex_match(candidate, *regexPattern_);
42 }
43
MatchesPrefix(const std::string & candidate) const44 bool PatternMatcher::MatchesPrefix(const std::string &candidate) const
45 {
46 if (candidate.size() < pattern_.size()) {
47 return false;
48 }
49
50 for (size_t i = 0; i < pattern_.size(); ++i) {
51 char a = pattern_[i];
52 char b = candidate[i];
53 if (isCaseSensitive_) {
54 if (a != b) {
55 return false;
56 }
57 } else {
58 if (std::tolower(a) != std::tolower(b)) {
59 return false;
60 }
61 }
62 }
63
64 return true;
65 }
66
MatchesSubstring(const std::string & candidate) const67 bool PatternMatcher::MatchesSubstring(const std::string &candidate) const
68 {
69 return regexPattern_ && std::regex_search(candidate, *regexPattern_);
70 }
71
DetermineMatchKind(const std::string & candidate,const PatternMatcher & matcher)72 MatchKind DetermineMatchKind(const std::string &candidate, const PatternMatcher &matcher)
73 {
74 if (matcher.MatchesExact(candidate)) {
75 return MatchKind::EXACT;
76 }
77 if (matcher.MatchesPrefix(candidate)) {
78 return MatchKind::PREFIX;
79 }
80 if (matcher.MatchesSubstring(candidate)) {
81 return MatchKind::SUBSTRING;
82 }
83 return MatchKind::NONE;
84 }
85
86 // improve get declarations correct and better
GetItemsFromNamedDeclaration(es2panda_Context * context,const SourceFile & file,const PatternMatcher & matcher)87 std::vector<NavigateToItem> GetItemsFromNamedDeclaration(es2panda_Context *context, const SourceFile &file,
88 const PatternMatcher &matcher)
89 {
90 std::vector<NavigateToItem> items;
91 auto filePath = std::string {file.filePath};
92 auto fileContent = std::string {file.source};
93 auto ctx = reinterpret_cast<public_lib::Context *>(context);
94 auto ast = reinterpret_cast<ir::AstNode *>(ctx->parserProgram->Ast());
95
96 auto children = GetChildren(ast, ctx->allocator);
97
98 for (const auto child : children) {
99 if (child->IsIdentifier()) {
100 auto name = child->AsIdentifier()->Name();
101 auto matchKind = DetermineMatchKind(std::string(name), matcher);
102 if (matchKind != MatchKind::NONE) {
103 auto nodeType = child->Type();
104 auto containerId = GetContainerNode(child);
105 auto containerName = GetIdentifierName(containerId);
106 auto containerType = containerId->Type();
107 items.push_back({std::string(name), ToString(nodeType), matchKind, true, filePath, containerName,
108 ToString(containerType)});
109 }
110 }
111 }
112
113 return items;
114 }
115
116 // Helper: tries to emit a single item, returns false if limit reached
TryEmitItem(const NavigateToItem & item,size_t & remaining,std::set<std::pair<std::string,std::string>> & seenPairs,std::vector<NavigateToItem> & results)117 static bool TryEmitItem(const NavigateToItem &item, size_t &remaining,
118 std::set<std::pair<std::string, std::string>> &seenPairs, std::vector<NavigateToItem> &results)
119 {
120 if (remaining == 0) {
121 return false;
122 }
123 auto key = std::make_pair(item.name, item.containerName);
124 if (!seenPairs.insert(key).second) {
125 return true; // duplicate, but still under limit
126 }
127 results.emplace_back(item);
128 --remaining;
129 return remaining > 0;
130 }
131
GetNavigateToItems(es2panda_Context * context,const std::vector<SourceFile> & srcFiles,size_t maxResultCount,const std::string & searchValue,bool isCaseSensitive)132 std::vector<NavigateToItem> GetNavigateToItems(es2panda_Context *context, const std::vector<SourceFile> &srcFiles,
133 size_t maxResultCount, const std::string &searchValue,
134 bool isCaseSensitive)
135 {
136 static std::unordered_map<std::string, size_t> totalEmitted;
137 size_t &emittedSoFar = totalEmitted[searchValue];
138
139 std::vector<NavigateToItem> results;
140 std::set<std::pair<std::string, std::string>> seenPairs;
141 PatternMatcher matcher(searchValue, isCaseSensitive);
142
143 if (!matcher.IsPatternValid() || emittedSoFar >= maxResultCount) {
144 return results;
145 }
146
147 size_t remaining = maxResultCount - emittedSoFar;
148
149 for (const auto &file : srcFiles) {
150 auto items = GetItemsFromNamedDeclaration(context, file, matcher);
151 for (const auto &item : items) {
152 if (!TryEmitItem(item, remaining, seenPairs, results)) {
153 emittedSoFar = maxResultCount;
154 return results;
155 }
156 }
157 }
158
159 emittedSoFar = maxResultCount - remaining;
160 return results;
161 }
162
163 } // namespace ark::es2panda::lsp