Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ public static String getMdoRef(DocumentContext documentContext) {
// осторожно! не менять на вызов documentContext.getMdoRef, а то зациклится
var mdoRef = documentContext.getMdObject()
.map(MD::getMdoRef)
// .os-файл library-сущности OneScript не имеет объекта метаданных: его mdoRef — каноничное
// имя библиотеки (зарегистрированное индексатором до добавления документа), чтобы он
// резолвился в documentsByMDORef единообразно с BSL-объектами.
.or(() -> documentContext.getServerContext().findOScriptLibraryName(documentContext.getUri()))
.orElseGet(() -> documentContext.getUri().toString());
return stringInterner.intern(mdoRef);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@
import java.io.File;
import java.net.URI;
import java.nio.file.Path;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -117,6 +119,23 @@
*/
private final Map<String, Map<ModuleType, DocumentContext>> documentsByMDORef
= new ConcurrentHashMap<>();
/**
* Каталог библиотечных сущностей OneScript: {@code nameKey → (тип модуля, документ)}.
* Наполняется извне (индексатором OneScript-библиотек) и служит резолву имени класса/модуля
* библиотеки в документ .os-файла — аналогично тому, как {@link #findCommonModule(String)}
* резолвит общий модуль из метаданных конфигурации. Регистронезависимый (ключ — {@code nameKey});
* под несколькими именами (алиасами) может резолвиться один и тот же документ.
*/
private final Map<String, OScriptLibrarySymbol> oscriptLibrariesByNameKey = new ConcurrentHashMap<>();
/**
* {@code URI .os-файла → каноничное имя} его library-сущности. Наполняется индексатором
* OneScript-библиотек <b>до</b> добавления документа в контекст, чтобы {@code mdoRef}
* такого документа вычислялся как имя библиотеки (см. {@link MdoRefBuilder}), а не как URI:
* тогда документ индексируется в {@link #documentsByMDORef} под своим именем и резолвится
* единообразно с BSL-объектами. Первое зарегистрированное имя выигрывает (у одного файла
* может быть несколько ролей — см. {@code OScriptModuleTypeResolver}).
*/
private final Map<URI, String> oscriptLibraryNamesByUri = new ConcurrentHashMap<>();
private final Map<URI, ReadWriteLock> documentLocks = new ConcurrentHashMap<>();

private final Map<DocumentContext, State> states = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -331,6 +350,8 @@
states.clear();
documentsByMDORef.clear();
mdoRefs.clear();
oscriptLibrariesByNameKey.clear();
oscriptLibraryNamesByUri.clear();
documentLocks.clear();
commonModuleCache.invalidateAll();
configurationMetadata.clear();
Expand Down Expand Up @@ -470,6 +491,120 @@
return commonModuleCache.get(name, key -> getConfiguration().findCommonModule(key));
}

/**
* Привязать {@code URI .os-файла} к каноничному имени его library-сущности <b>до</b>
* добавления документа в контекст. Нужно, чтобы {@code mdoRef} документа вычислился как имя
* библиотеки (см. {@link MdoRefBuilder}), а не как URI. Первое имя выигрывает: у одного файла
* может быть несколько ролей, но идентичность документа (mdoRef) должна быть стабильной.
*
* @param uri URI .os-файла (абсолютный, нормализованный)
* @param qualifiedName каноничное имя сущности
*/
public void registerOScriptLibraryName(URI uri, String qualifiedName) {
oscriptLibraryNamesByUri.putIfAbsent(uri, qualifiedName);
}

/**
* Каноничное имя library-сущности OneScript, привязанное к URI через
* {@link #registerOScriptLibraryName}. Используется {@link MdoRefBuilder} при вычислении
* {@code mdoRef} .os-документов, у которых нет объекта метаданных.
*
* @param uri URI .os-файла
* @return каноничное имя либо {@code empty}
*/
public Optional<String> findOScriptLibraryName(URI uri) {
return Optional.ofNullable(oscriptLibraryNamesByUri.get(uri));
}

/**
* Зарегистрировать библиотечную сущность OneScript: занести её в каталог имён (для резолва
* в {@link #findLibraryClass}/{@link #findLibraryModule}) и проиндексировать документ в
* {@code documentsByMDORef} под его {@code mdoRef} (= каноничным именем, см.
* {@link #registerOScriptLibraryName}), чтобы он резолвился через
* {@link #getDocument(String, ModuleType)}. Вызывается индексатором OneScript-библиотек.
*
* @param qualifiedName каноничное имя сущности (алиас в каталоге имён)
* @param moduleType {@link ModuleType#OScriptClass} или {@link ModuleType#OScriptModule}
* @param documentContext документ .os-файла
*/
public void registerOScriptLibrary(String qualifiedName, ModuleType moduleType, DocumentContext documentContext) {
oscriptLibrariesByNameKey.put(oscriptNameKey(qualifiedName), new OScriptLibrarySymbol(moduleType, documentContext));
documentsByMDORef
.computeIfAbsent(documentContext.getMdoRef(), k -> Collections.synchronizedMap(new EnumMap<>(ModuleType.class)))
.put(moduleType, documentContext);
}

/**
* Снять регистрацию библиотечной сущности OneScript: из каталога имён, из
* {@code documentsByMDORef} и из привязки {@code URI → имя}. Вызывается индексатором при
* удалении файла/пере-индексации.
*
* @param qualifiedName каноничное имя сущности
* @param moduleType тип модуля сущности
*/
public void removeOScriptLibrary(String qualifiedName, ModuleType moduleType) {
var symbol = oscriptLibrariesByNameKey.remove(oscriptNameKey(qualifiedName));
if (symbol == null) {
return;
}
var documentContext = symbol.documentContext();
var group = documentsByMDORef.get(documentContext.getMdoRef());
if (group != null) {
group.remove(moduleType);
if (group.isEmpty()) {
documentsByMDORef.remove(documentContext.getMdoRef());
}
}
oscriptLibraryNamesByUri.remove(documentContext.getUri());
}

/**
* Найти документ зарегистрированного library-класса OneScript по имени из исходного кода.
*
* @param name имя из кода (произвольный регистр)
* @return документ .os-файла класса либо {@code empty}
*/
public Optional<DocumentContext> findLibraryClass(String name) {
return findOScriptLibrarySymbol(name, ModuleType.OScriptClass);
}

/**
* Найти документ зарегистрированного library-модуля OneScript по имени из исходного кода.
*
* @param name имя из кода (произвольный регистр)
* @return документ .os-файла модуля либо {@code empty}
*/
public Optional<DocumentContext> findLibraryModule(String name) {
return findOScriptLibrarySymbol(name, ModuleType.OScriptModule);
}

private Optional<DocumentContext> findOScriptLibrarySymbol(String name, ModuleType moduleType) {
if (name == null || name.isBlank()) {

Check warning on line 582 in src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this expression which always evaluates to "false"

See more on https://sonarcloud.io/project/issues?id=1c-syntax_bsl-language-server&issues=AZ8UrCPurJXjAhCQ85EP&open=AZ8UrCPurJXjAhCQ85EP&pullRequest=4233
return Optional.empty();
}
return Optional.ofNullable(oscriptLibrariesByNameKey.get(oscriptNameKey(name)))
.filter(symbol -> symbol.moduleType() == moduleType)
.map(OScriptLibrarySymbol::documentContext);
}

/**
* Нормализация имени OneScript-сущности для регистронезависимого сравнения.
* Должна совпадать с {@code OScriptLibraryIndex.nameKey}: NFC + lower-case (Locale.ROOT) —
* имена в .os-файлах хранятся в NFD, а в коде набираются в NFC.
*/
private static String oscriptNameKey(String name) {
return Normalizer.normalize(name, Normalizer.Form.NFC).toLowerCase(Locale.ROOT);
}

/**
* Запись каталога библиотечных сущностей OneScript.
*
* @param moduleType тип модуля (класс/модуль)
* @param documentContext документ .os-файла сущности
*/
public record OScriptLibrarySymbol(ModuleType moduleType, DocumentContext documentContext) {
}

private DocumentContext createDocumentContext(URI uri) {
var documentContext = documentContextProvider.getObject(uri, this);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
/**
* Synthetic-символ BSL-keyword'а ({@code Если}, {@code Истина}, {@code Цикл}…).
* <p>
* Создаётся on-the-fly в {@link com.github._1c_syntax.bsl.languageserver.references.KeywordReferenceFinder}
* Создаётся on-the-fly в {@link com.github._1c_syntax.bsl.languageserver.types.references.KeywordReferenceFinder}
* при попадании курсора на keyword-токен — keyword'ы не являются
* source-defined-символами и не лежат в symbol-tree модуля, но участвуют
* в общем reference/hover-flow на правах обычного {@link Symbol}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
* ({@code Если}, {@code Истина}, {@code Цикл}…).
* <p>
* {@link KeywordSymbol} приходит уже с локализованным описанием
* (выбранным {@link com.github._1c_syntax.bsl.languageserver.references.KeywordReferenceFinder}
* (выбранным {@link com.github._1c_syntax.bsl.languageserver.types.references.KeywordReferenceFinder}
* по текущей локали LS и AST-контексту). Билдер только оборачивает
* keyword и описание в markdown-формат:
* <pre>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
* подбору ссылки, живёт в реализациях {@link com.github._1c_syntax.bsl.languageserver.references.ReferenceFinder}
* (в том числе synthetic-символы для аннотаций и keyword'ов —
* {@link com.github._1c_syntax.bsl.languageserver.references.AnnotationReferenceFinder},
* {@link com.github._1c_syntax.bsl.languageserver.references.KeywordReferenceFinder}),
* {@link com.github._1c_syntax.bsl.languageserver.types.references.KeywordReferenceFinder}),
* всё, что относится к формированию текста подсказки — в соответствующем
* {@code MarkupContentBuilder}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,9 @@
import com.github._1c_syntax.bsl.languageserver.context.DocumentContext;
import com.github._1c_syntax.bsl.languageserver.context.events.DocumentContextContentChangedEvent;
import com.github._1c_syntax.bsl.languageserver.context.events.ServerContextDocumentRemovedEvent;
import com.github._1c_syntax.bsl.languageserver.context.symbol.ConstructorSymbol;
import com.github._1c_syntax.bsl.languageserver.context.symbol.MethodSymbol;
import com.github._1c_syntax.bsl.languageserver.context.symbol.SourceDefinedSymbol;
import com.github._1c_syntax.bsl.languageserver.context.symbol.SymbolTree;
import com.github._1c_syntax.bsl.languageserver.context.symbol.VariableSymbol;
import com.github._1c_syntax.bsl.languageserver.types.oscript.OScriptLibraryIndex;
import com.github._1c_syntax.bsl.languageserver.context.MdoRefBuilder;
import com.github._1c_syntax.bsl.languageserver.utils.Methods;
import com.github._1c_syntax.bsl.languageserver.utils.ModuleReference;
Expand Down Expand Up @@ -85,7 +82,6 @@ public class ReferenceIndexFiller {

private final ReferenceIndex index;
private final LanguageServerConfiguration configuration;
private final OScriptLibraryIndex oScriptLibraryIndex;

@EventListener
public void handleEvent(DocumentContextContentChangedEvent event) {
Expand Down Expand Up @@ -267,39 +263,33 @@ private void tryRegisterLibraryClassReference(BSLParser.NewExpressionContext ctx
return;
}
var name = typeName.IDENTIFIER().getText();
var libUri = oScriptLibraryIndex.findClassUri(name);
if (libUri.isEmpty()) {
var libClass = documentContext.getServerContext().findLibraryClass(name);
if (libClass.isEmpty()) {
return;
}
var libMdoRef = libUri.get().toString();
var moduleType = actualLibraryModuleType(libUri.get(), ModuleType.OScriptClass);
var libDocument = libClass.get();
var mdoRef = libDocument.getMdoRef();
var range = Ranges.create(typeName.IDENTIFIER());

var ctor = libraryClassConstructor(libUri.get());
var ctor = libDocument.getSymbolTree().getConstructor();
if (ctor.isPresent()) {
index.addMethodCall(
documentContext.getUri(),
libMdoRef,
moduleType,
mdoRef,
ModuleType.OScriptClass,
ctor.get().getName(),
range
);
} else {
index.addModuleReference(
documentContext.getUri(),
libMdoRef,
moduleType,
mdoRef,
ModuleType.OScriptClass,
range
);
}
}

private Optional<ConstructorSymbol> libraryClassConstructor(URI libUri) {
return Optional.ofNullable(documentContext.getServerContext().getDocument(libUri))
.map(DocumentContext::getSymbolTree)
.flatMap(SymbolTree::getConstructor);
}

/**
* Если идентификатор соответствует имени зарегистрированного OneScript
* library-модуля, регистрирует:
Expand All @@ -312,41 +302,27 @@ private void tryRegisterLibraryModuleCall(@Nullable TerminalNode identifier, Opt
if (identifier == null) {
return;
}
var libUri = oScriptLibraryIndex.findModuleUri(identifier.getText());
if (libUri.isEmpty()) {
var libModule = documentContext.getServerContext().findLibraryModule(identifier.getText());
if (libModule.isEmpty()) {
return;
}
var libMdoRef = libUri.get().toString();
var moduleType = actualLibraryModuleType(libUri.get(), ModuleType.OScriptModule);
var mdoRef = libModule.get().getMdoRef();

// Ссылка на сам identifier модуля — нужна для go-to-definition без точки.
index.addModuleReference(
documentContext.getUri(),
libMdoRef,
moduleType,
mdoRef,
ModuleType.OScriptModule,
Ranges.create(identifier)
);

if (methodName.isPresent()) {
var methodNameToken = methodName.get();
addMethodCall(libMdoRef, moduleType, Strings.trimQuotes(methodNameToken.getText()),
addMethodCall(mdoRef, ModuleType.OScriptModule, Strings.trimQuotes(methodNameToken.getText()),
Ranges.create(methodNameToken));
}
}

/**
* Возвращает фактический {@link ModuleType} документа библиотечного .os-файла.
* Один .os может быть зарегистрирован одновременно и как класс, и как модуль
* (см. {@link OScriptLibraryIndex}); чтобы ссылка корректно резолвилась через
* {@code ServerContext.getDocument(mdoRef, moduleType)}, используем тип
* фактически загруженного {@link DocumentContext}, а не «теоретический»
* тип из роли регистрации.
*/
private ModuleType actualLibraryModuleType(java.net.URI libUri, ModuleType fallback) {
var dc = documentContext.getServerContext().getDocument(libUri);
return dc != null ? dc.getModuleType() : fallback;
}

/**
* Добавляет ссылку на модуль по позиции идентификатора, только если идентификатор является
* именем общего модуля. Для вызовов вида Справочники.Имя.Метод() ссылка не добавляется,
Expand Down Expand Up @@ -585,8 +561,9 @@ public ParserRuleContext visitAssignment(BSLParser.AssignmentContext ctx) {
if (typeName == null || typeName.IDENTIFIER() == null) {
return null;
}
return oScriptLibraryIndex.findClassUri(typeName.IDENTIFIER().getText())
.map(java.net.URI::toString)
return documentContext.getServerContext()
.findLibraryClass(typeName.IDENTIFIER().getText())
.map(DocumentContext::getMdoRef)
.orElse(null);
}

Expand Down
Loading
Loading