Skip to content

Commit 2f99c37

Browse files
nixel2007claude
andcommitted
refactor(inlayhints): вынести индекс вызовов в отдельный класс и устранить замечания Sonar
Co-Authored-By: Claude Fable 5 <[email protected]>
1 parent 985700e commit 2f99c37

3 files changed

Lines changed: 136 additions & 81 deletions

File tree

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* This file is a part of BSL Language Server.
3+
*
4+
* Copyright (c) 2018-2026
5+
* Alexey Sosnoviy <[email protected]>, Nikita Fedkin <[email protected]> and contributors
6+
*
7+
* SPDX-License-Identifier: LGPL-3.0-or-later
8+
*
9+
* BSL Language Server is free software; you can redistribute it and/or
10+
* modify it under the terms of the GNU Lesser General Public
11+
* License as published by the Free Software Foundation; either
12+
* version 3.0 of the License, or (at your option) any later version.
13+
*
14+
* BSL Language Server is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17+
* Lesser General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Lesser General Public
20+
* License along with BSL Language Server.
21+
*/
22+
package com.github._1c_syntax.bsl.languageserver.inlayhints;
23+
24+
import com.github._1c_syntax.bsl.languageserver.context.DocumentContext;
25+
import com.github._1c_syntax.bsl.languageserver.references.model.Reference;
26+
import com.github._1c_syntax.bsl.languageserver.utils.Ranges;
27+
import com.github._1c_syntax.bsl.languageserver.utils.Trees;
28+
import com.github._1c_syntax.bsl.parser.BSLParser;
29+
import org.antlr.v4.runtime.ParserRuleContext;
30+
import org.eclipse.lsp4j.Range;
31+
32+
import java.util.HashMap;
33+
import java.util.Map;
34+
import java.util.Optional;
35+
36+
/**
37+
* Индекс {@code doCall}-узлов документа по диапазону имени вызываемого метода.
38+
* <p>
39+
* Строится одним обходом AST документа и позволяет резолвить вызов по ссылке
40+
* на метод за {@code O(1)} вместо повторного обхода AST на каждую ссылку.
41+
* Ключом служит {@link Reference#selectionRange()} ссылки, совпадающий с
42+
* диапазоном имени вызываемого метода (для конструктора — с диапазоном имени типа).
43+
*/
44+
final class DoCallRangeIndex {
45+
46+
private final Map<String, BSLParser.DoCallContext> doCallsByMethodNameRange;
47+
48+
private DoCallRangeIndex(Map<String, BSLParser.DoCallContext> doCallsByMethodNameRange) {
49+
this.doCallsByMethodNameRange = doCallsByMethodNameRange;
50+
}
51+
52+
/**
53+
* Строит индекс по AST документа.
54+
* <p>
55+
* Все {@code doCall}-узлы документа собираются в карту по диапазону имени
56+
* вызываемого метода (для конструктора — по диапазону имени типа). Этот диапазон
57+
* совпадает с {@link Reference#selectionRange()} соответствующей ссылки.
58+
*
59+
* @param documentContext контекст документа, AST которого обходится
60+
* @return индекс вызовов документа; пустой, если вызовов нет
61+
*/
62+
static DoCallRangeIndex of(DocumentContext documentContext) {
63+
var ast = documentContext.getAst();
64+
var doCalls = Trees.findAllRuleNodes(ast, BSLParser.RULE_doCall);
65+
Map<String, BSLParser.DoCallContext> result = HashMap.newHashMap(doCalls.size());
66+
for (var node : doCalls) {
67+
var doCall = (BSLParser.DoCallContext) node;
68+
var doCallParent = doCall.getParent();
69+
if (doCallParent == null) {
70+
continue;
71+
}
72+
methodNameRange(doCallParent)
73+
.ifPresent(methodNameRange -> result.putIfAbsent(rangeKey(methodNameRange), doCall));
74+
}
75+
return new DoCallRangeIndex(result);
76+
}
77+
78+
/**
79+
* Возвращает {@code doCall}-узел, соответствующий ссылке на метод.
80+
*
81+
* @param reference ссылка на вызываемый метод; используется её {@link Reference#selectionRange()}
82+
* @return узел вызова либо {@link Optional#empty()}, если в документе нет вызова с таким диапазоном
83+
*/
84+
Optional<BSLParser.DoCallContext> doCallFor(Reference reference) {
85+
return Optional.ofNullable(doCallsByMethodNameRange.get(rangeKey(reference.selectionRange())));
86+
}
87+
88+
/**
89+
* Строковый ключ карты вызовов по диапазону имени метода.
90+
* <p>
91+
* Используется вместо {@link Range} из lsp4j, который не реализует
92+
* {@link Comparable}: {@link String} реализует {@link Comparable} и не зависит
93+
* от деталей {@link Range#hashCode()}, что устраняет риск деградации хэш-карты
94+
* при коллизиях ключей.
95+
*
96+
* @param range диапазон имени метода/типа
97+
* @return ключ вида {@code "startLine:startChar:endLine:endChar"}
98+
*/
99+
private static String rangeKey(Range range) {
100+
var start = range.getStart();
101+
var end = range.getEnd();
102+
return start.getLine() + ":" + start.getCharacter() + ":" + end.getLine() + ":" + end.getCharacter();
103+
}
104+
105+
/**
106+
* Диапазон имени вызываемого метода для родителя {@code doCall}-узла —
107+
* именно его {@link com.github._1c_syntax.bsl.languageserver.references.ReferenceIndex}
108+
* хранит в {@link Reference#selectionRange()}.
109+
*
110+
* @param doCallParent родительский узел вызова (methodCall, globalMethodCall или newExpression)
111+
* @return диапазон имени метода/типа либо {@link Optional#empty()},
112+
* если узел не является вызовом метода
113+
*/
114+
private static Optional<Range> methodNameRange(ParserRuleContext doCallParent) {
115+
if (doCallParent instanceof BSLParser.MethodCallContext methodCallContext) {
116+
var methodName = methodCallContext.methodName();
117+
return methodName == null ? Optional.empty() : Optional.of(Ranges.create(methodName));
118+
} else if (doCallParent instanceof BSLParser.GlobalMethodCallContext globalMethodCallContext) {
119+
var methodName = globalMethodCallContext.methodName();
120+
return methodName == null ? Optional.empty() : Optional.of(Ranges.create(methodName));
121+
} else if (doCallParent instanceof BSLParser.NewExpressionContext newExpressionContext) {
122+
var typeName = newExpressionContext.typeName();
123+
if (typeName != null && typeName.IDENTIFIER() != null) {
124+
return Optional.of(Ranges.create(typeName.IDENTIFIER()));
125+
}
126+
return Optional.empty();
127+
} else {
128+
return Optional.empty();
129+
}
130+
}
131+
}

src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/SourceDefinedMethodCallInlayHintSupplier.java

Lines changed: 4 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,19 @@
2929
import com.github._1c_syntax.bsl.languageserver.references.ReferenceIndex;
3030
import com.github._1c_syntax.bsl.languageserver.references.model.Reference;
3131
import com.github._1c_syntax.bsl.languageserver.utils.Ranges;
32-
import com.github._1c_syntax.bsl.languageserver.utils.Trees;
3332
import com.github._1c_syntax.bsl.parser.BSLParser;
34-
import org.antlr.v4.runtime.ParserRuleContext;
3533
import org.apache.commons.lang3.Strings;
3634
import org.eclipse.lsp4j.InlayHint;
3735
import org.eclipse.lsp4j.InlayHintKind;
3836
import org.eclipse.lsp4j.InlayHintParams;
3937
import org.eclipse.lsp4j.MarkupContent;
4038
import org.eclipse.lsp4j.MarkupKind;
4139
import org.eclipse.lsp4j.Position;
42-
import org.eclipse.lsp4j.Range;
4340
import org.eclipse.lsp4j.SymbolKind;
4441
import org.springframework.stereotype.Component;
4542

4643
import java.util.ArrayList;
47-
import java.util.HashMap;
4844
import java.util.List;
49-
import java.util.Map;
50-
import java.util.Optional;
5145

5246
/**
5347
* Поставщик подсказок о параметрах вызываемого метода.
@@ -83,58 +77,15 @@ public List<InlayHint> getInlayHints(DocumentContext documentContext, InlayHintP
8377
return List.of();
8478
}
8579

86-
// Один обход AST документа на все ссылки: сопоставляем каждый вызов с
80+
// Один обход AST документа на все ссылки: индекс сопоставляет каждый вызов с
8781
// диапазоном имени метода (тем же, что хранится в reference.selectionRange()),
8882
// чтобы дальше резолвить вызов по ссылке за O(1) вместо обхода AST на каждую ссылку.
89-
var doCallsByMethodNameRange = collectDoCallsByMethodNameRange(documentContext);
83+
var doCallRangeIndex = DoCallRangeIndex.of(documentContext);
9084

9185
var result = new ArrayList<InlayHint>();
9286
for (var reference : references) {
93-
var doCall = doCallsByMethodNameRange.get(rangeKey(reference.selectionRange()));
94-
if (doCall != null) {
95-
result.addAll(toInlayHints(reference, doCall));
96-
}
97-
}
98-
return result;
99-
}
100-
101-
/**
102-
* Строковый ключ карты вызовов по диапазону имени метода.
103-
* <p>
104-
* Используется вместо {@link Range} из lsp4j, который не реализует
105-
* {@link Comparable}: {@link String} реализует {@link Comparable} и не зависит
106-
* от деталей {@link Range#hashCode()}, что устраняет риск деградации хэш-карты
107-
* при коллизиях ключей.
108-
*
109-
* @param range диапазон имени метода/типа
110-
* @return ключ вида {@code "startLine:startChar:endLine:endChar"}
111-
*/
112-
private static String rangeKey(Range range) {
113-
var start = range.getStart();
114-
var end = range.getEnd();
115-
return start.getLine() + ":" + start.getCharacter() + ":" + end.getLine() + ":" + end.getCharacter();
116-
}
117-
118-
/**
119-
* Собирает все {@code doCall}-узлы документа в карту по диапазону имени
120-
* вызываемого метода (для конструктора — по диапазону имени типа).
121-
* <p>
122-
* Этот диапазон совпадает с {@link Reference#selectionRange()} соответствующей
123-
* ссылки, что позволяет резолвить вызов по ссылке без повторного обхода AST.
124-
*
125-
* @param documentContext контекст документа, AST которого обходится
126-
* @return карта «ключ диапазона имени метода → узел вызова»; пустая, если вызовов нет
127-
*/
128-
private static Map<String, BSLParser.DoCallContext> collectDoCallsByMethodNameRange(
129-
DocumentContext documentContext
130-
) {
131-
var ast = documentContext.getAst();
132-
var doCalls = Trees.findAllRuleNodes(ast, BSLParser.RULE_doCall);
133-
Map<String, BSLParser.DoCallContext> result = HashMap.newHashMap(doCalls.size());
134-
for (var node : doCalls) {
135-
var doCall = (BSLParser.DoCallContext) node;
136-
methodNameRange(doCall.getParent())
137-
.ifPresent(methodNameRange -> result.putIfAbsent(rangeKey(methodNameRange), doCall));
87+
doCallRangeIndex.doCallFor(reference)
88+
.ifPresent(doCall -> result.addAll(toInlayHints(reference, doCall)));
13889
}
13990
return result;
14091
}
@@ -217,31 +168,4 @@ private void setTooltip(InlayHint inlayHint, ParameterDefinition parameter) {
217168
var tooltip = new MarkupContent(MarkupKind.MARKDOWN, markdown);
218169
inlayHint.setTooltip(tooltip);
219170
}
220-
221-
222-
/**
223-
* Диапазон имени вызываемого метода для родителя {@code doCall}-узла —
224-
* именно его {@link ReferenceIndex} хранит в {@link Reference#selectionRange()}.
225-
*
226-
* @param doCallParent родительский узел вызова (methodCall, globalMethodCall или newExpression)
227-
* @return диапазон имени метода/типа либо {@link Optional#empty()},
228-
* если узел не является вызовом метода
229-
*/
230-
private static Optional<Range> methodNameRange(ParserRuleContext doCallParent) {
231-
if (doCallParent instanceof BSLParser.MethodCallContext methodCallContext) {
232-
var methodName = methodCallContext.methodName();
233-
return methodName == null ? Optional.empty() : Optional.of(Ranges.create(methodName));
234-
} else if (doCallParent instanceof BSLParser.GlobalMethodCallContext globalMethodCallContext) {
235-
var methodName = globalMethodCallContext.methodName();
236-
return methodName == null ? Optional.empty() : Optional.of(Ranges.create(methodName));
237-
} else if (doCallParent instanceof BSLParser.NewExpressionContext newExpressionContext) {
238-
var typeName = newExpressionContext.typeName();
239-
if (typeName != null && typeName.IDENTIFIER() != null) {
240-
return Optional.of(Ranges.create(typeName.IDENTIFIER()));
241-
}
242-
return Optional.empty();
243-
} else {
244-
return Optional.empty();
245-
}
246-
}
247171
}

src/test/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/PlatformMethodCallInlayHintSupplierUnitTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ void getInlayHintsWithNullAstReturnsEmpty() {
8787
// when
8888
var hints = supplier.getInlayHints(documentContext, params);
8989

90-
// then — L96 return List.of().
90+
// then — при отсутствии AST подсказок нет.
9191
assertThat(hints).isEmpty();
9292
}
9393

0 commit comments

Comments
 (0)