From 868ff041b81290e12d018053c5bf4da0c33ef20d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 29 Jun 2026 17:30:49 +0000 Subject: [PATCH 1/4] refactor(references): move type-aware ReferenceFinders to types.references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit KeywordReferenceFinder, NewExpressionReferenceFinder and PlatformMemberReferenceFinder resolve references by consulting the type system (TypeService, GlobalScopeProvider, type symbols) — they are type-aware adapters of the ReferenceFinder port, not part of the type-agnostic name-resolution core. The port (ReferenceFinder) and the pure finders stay in `references`; these three move to `types.references`, next to their dependencies, so `references` no longer depends on `types` through them. They keep implementing references.ReferenceFinder and are still collected by ReferenceResolver via List (Spring, package-independent). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01LkYRgB6NZZRRNZmBrdL54M --- .../references/KeywordReferenceFinder.java | 4 ++- .../NewExpressionReferenceFinder.java | 3 +- .../PlatformMemberReferenceFinder.java | 3 +- .../types/references/package-info.java | 34 +++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) rename src/main/java/com/github/_1c_syntax/bsl/languageserver/{ => types}/references/KeywordReferenceFinder.java (97%) rename src/main/java/com/github/_1c_syntax/bsl/languageserver/{ => types}/references/NewExpressionReferenceFinder.java (97%) rename src/main/java/com/github/_1c_syntax/bsl/languageserver/{ => types}/references/PlatformMemberReferenceFinder.java (95%) create mode 100644 src/main/java/com/github/_1c_syntax/bsl/languageserver/types/references/package-info.java diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/KeywordReferenceFinder.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/references/KeywordReferenceFinder.java similarity index 97% rename from src/main/java/com/github/_1c_syntax/bsl/languageserver/references/KeywordReferenceFinder.java rename to src/main/java/com/github/_1c_syntax/bsl/languageserver/types/references/KeywordReferenceFinder.java index cac6f071d82..671dc607e80 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/KeywordReferenceFinder.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/references/KeywordReferenceFinder.java @@ -19,11 +19,13 @@ * You should have received a copy of the GNU Lesser General Public * License along with BSL Language Server. */ -package com.github._1c_syntax.bsl.languageserver.references; +package com.github._1c_syntax.bsl.languageserver.types.references; import com.github._1c_syntax.bsl.languageserver.configuration.LanguageServerConfiguration; import com.github._1c_syntax.bsl.languageserver.context.ServerContextProvider; import com.github._1c_syntax.bsl.languageserver.context.symbol.KeywordSymbol; +import com.github._1c_syntax.bsl.languageserver.references.AnnotationReferenceFinder; +import com.github._1c_syntax.bsl.languageserver.references.ReferenceFinder; import com.github._1c_syntax.bsl.languageserver.references.model.Reference; import com.github._1c_syntax.bsl.languageserver.types.registry.GlobalScopeProvider; import com.github._1c_syntax.bsl.languageserver.utils.Ranges; diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/NewExpressionReferenceFinder.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/references/NewExpressionReferenceFinder.java similarity index 97% rename from src/main/java/com/github/_1c_syntax/bsl/languageserver/references/NewExpressionReferenceFinder.java rename to src/main/java/com/github/_1c_syntax/bsl/languageserver/types/references/NewExpressionReferenceFinder.java index 09c40842fc3..0599c5a6158 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/NewExpressionReferenceFinder.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/references/NewExpressionReferenceFinder.java @@ -19,10 +19,11 @@ * You should have received a copy of the GNU Lesser General Public * License along with BSL Language Server. */ -package com.github._1c_syntax.bsl.languageserver.references; +package com.github._1c_syntax.bsl.languageserver.types.references; import com.github._1c_syntax.bsl.languageserver.context.DocumentContext; import com.github._1c_syntax.bsl.languageserver.context.ServerContextProvider; +import com.github._1c_syntax.bsl.languageserver.references.ReferenceFinder; import com.github._1c_syntax.bsl.languageserver.references.model.OccurrenceType; import com.github._1c_syntax.bsl.languageserver.references.model.Reference; import com.github._1c_syntax.bsl.languageserver.types.TypeService; diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/PlatformMemberReferenceFinder.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/references/PlatformMemberReferenceFinder.java similarity index 95% rename from src/main/java/com/github/_1c_syntax/bsl/languageserver/references/PlatformMemberReferenceFinder.java rename to src/main/java/com/github/_1c_syntax/bsl/languageserver/types/references/PlatformMemberReferenceFinder.java index 130d6772603..0a24899e2fc 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/PlatformMemberReferenceFinder.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/references/PlatformMemberReferenceFinder.java @@ -19,9 +19,10 @@ * You should have received a copy of the GNU Lesser General Public * License along with BSL Language Server. */ -package com.github._1c_syntax.bsl.languageserver.references; +package com.github._1c_syntax.bsl.languageserver.types.references; import com.github._1c_syntax.bsl.languageserver.context.ServerContextProvider; +import com.github._1c_syntax.bsl.languageserver.references.ReferenceFinder; import com.github._1c_syntax.bsl.languageserver.references.model.OccurrenceType; import com.github._1c_syntax.bsl.languageserver.references.model.Reference; import com.github._1c_syntax.bsl.languageserver.types.TypeService; diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/references/package-info.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/references/package-info.java new file mode 100644 index 00000000000..6cad5aa5fca --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/references/package-info.java @@ -0,0 +1,34 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2026 + * Alexey Sosnoviy , Nikita Fedkin and contributors + * + * SPDX-License-Identifier: LGPL-3.0-or-later + * + * BSL Language Server is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * BSL Language Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with BSL Language Server. + */ + +/** + * Type-aware реализации порта {@link com.github._1c_syntax.bsl.languageserver.references.ReferenceFinder}. + *

+ * Резолвят ссылки, консультируясь с системой типов (платформенные/конфигурационные члены, + * конструкторы, ключевые слова). Сам порт и type-agnostic finder'ы живут в пакете + * {@code references}; эти адаптеры вынесены сюда, к своим зависимостям из {@code types}, + * чтобы пакет {@code references} не зависел от {@code types}. + */ +@NullMarked +package com.github._1c_syntax.bsl.languageserver.types.references; + +import org.jspecify.annotations.NullMarked; From d6705c4eb8df265a4e7e675a3cf8c57be74c33e9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 29 Jun 2026 17:31:03 +0000 Subject: [PATCH 2/4] =?UTF-8?q?refactor(references):=20break=20the=20refer?= =?UTF-8?q?ences=E2=86=94types=20cycle=20via=20OScript=20mdoRef=20unificat?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The last references→types edge was ReferenceIndexFiller depending on types.oscript.OScriptLibraryIndex to map an OScript library class/module name to its file URI (used as the reference mdoRef). BSL references resolve differently: the filler computes a logical mdoRef from metadata via context (MdoRefBuilder/findCommonModule) and resolves through ServerContext.getDocument(mdoRef, moduleType). OScript was the odd one out, keying by URI because .os library docs had no logical mdoRef. Unify OScript onto the same path: - ServerContext gains a workspace-level catalog of OScript library symbols (name → canonical name + ModuleType + URI), case-insensitive, with findLibraryClass/findLibraryModule — parallel to findCommonModule and using only context-safe types (no dependency on the `types` package). - OScriptLibraryIndex populates the catalog (types→context) during reindex and registerEntry, and clears/removes it in lockstep with its own entries. - .os documents are additionally registered in documentsByMDORef under each of their canonical library names, so getDocument(name, moduleType) resolves them — multi-identity (one file, several library names/roles) is handled by registering the doc under each name. The BSL registration path (addMdoRefByUri / mdoRefs) is untouched. - ReferenceIndexFiller now canonicalizes via ServerContext.findLibrary…, uses the canonical name as mdoRef and the syntactic ModuleType, and drops OScriptLibraryIndex and actualLibraryModuleType — so references no longer depends on types. The ArchitectureTest model is updated accordingly (References no longer accesses Types; only types→references remains). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01LkYRgB6NZZRRNZmBrdL54M --- .../languageserver/context/ServerContext.java | 149 ++++++++++++++++++ .../references/ReferenceIndexFiller.java | 53 +++---- .../types/oscript/OScriptLibraryIndex.java | 7 + .../architecture/ArchitectureTest.java | 14 +- 4 files changed, 182 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java index 3eae30b5d40..0d776e45c81 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java @@ -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; @@ -117,6 +119,15 @@ public class ServerContext { */ private final Map> documentsByMDORef = new ConcurrentHashMap<>(); + /** + * Каталог библиотечных сущностей OneScript: {@code nameKey → (каноничное имя, тип модуля, URI)}. + * Наполняется извне (индексатором OneScript-библиотек) и служит резолву имени класса/модуля + * библиотеки в каноничный mdoRef — аналогично тому, как {@link #findCommonModule(String)} + * резолвит общий модуль из метаданных конфигурации. Регистронезависимый (ключ — {@code nameKey}). + */ + private final Map oscriptLibrariesByNameKey = new ConcurrentHashMap<>(); + /** Обратный индекс {@code URI → nameKey'и записей} для снятия при удалении/пере-индексации файла. */ + private final Map> oscriptLibraryNameKeysByUri = new ConcurrentHashMap<>(); private final Map documentLocks = new ConcurrentHashMap<>(); private final Map states = new ConcurrentHashMap<>(); @@ -301,6 +312,7 @@ public void removeDocument(URI uri) { } removeDocumentMdoRefByUri(uri); + removeOScriptDocumentMdoRefs(uri); states.remove(documentContext); documents.remove(uri); documentLocks.remove(uri); @@ -331,6 +343,8 @@ public void clear() { states.clear(); documentsByMDORef.clear(); mdoRefs.clear(); + oscriptLibrariesByNameKey.clear(); + oscriptLibraryNameKeysByUri.clear(); documentLocks.clear(); commonModuleCache.invalidateAll(); configurationMetadata.clear(); @@ -470,11 +484,146 @@ public Optional findCommonModule(String name) { return commonModuleCache.get(name, key -> getConfiguration().findCommonModule(key)); } + /** + * Зарегистрировать библиотечную сущность OneScript (класс/модуль) в каталоге. + * Вызывается индексатором OneScript-библиотек. + * + * @param qualifiedName каноничное имя сущности + * @param moduleType {@link ModuleType#OScriptClass} или {@link ModuleType#OScriptModule} + * @param uri URI .os-файла + */ + public void registerOScriptLibrarySymbol(String qualifiedName, ModuleType moduleType, URI uri) { + var key = oscriptNameKey(qualifiedName); + oscriptLibrariesByNameKey.put(key, new OScriptLibrarySymbol(qualifiedName, moduleType, uri)); + oscriptLibraryNameKeysByUri.computeIfAbsent(uri, k -> ConcurrentHashMap.newKeySet()).add(key); + registerOScriptDocumentMdoRefs(uri); + } + + /** + * Снять все библиотечные записи OneScript, связанные с файлом (при удалении/пере-индексации). + */ + public void removeOScriptLibrarySymbolsByUri(URI uri) { + // сначала снимаем регистрации документа в индексе по mdoRef (пока каталог ещё населён), + // потом чистим сам каталог. + removeOScriptDocumentMdoRefs(uri); + var keys = oscriptLibraryNameKeysByUri.remove(uri); + if (keys != null) { + keys.forEach(oscriptLibrariesByNameKey::remove); + } + } + + /** + * Зарегистрировать .os-документ в индексе по mdoRef под каждым его каноничным lib-именем + * (если документ уже создан). Идемпотентно; вызывается и при создании документа, и при + * наполнении каталога — порядок этих событий не фиксирован. + */ + private void registerOScriptDocumentMdoRefs(URI uri) { + var documentContext = documents.get(uri); + var keys = oscriptLibraryNameKeysByUri.get(uri); + if (documentContext == null || keys == null) { + return; + } + for (var key : keys) { + var symbol = oscriptLibrariesByNameKey.get(key); + if (symbol != null) { + documentsByMDORef + .computeIfAbsent(symbol.qualifiedName(), + k -> Collections.synchronizedMap(new EnumMap<>(ModuleType.class))) + .put(symbol.moduleType(), documentContext); + } + } + } + + /** + * Полностью очистить каталог библиотечных сущностей OneScript и снять связанные регистрации + * документов в индексе по mdoRef. Вызывается перед полной пере-индексацией библиотек. + */ + public void clearOScriptLibrarySymbols() { + for (var uri : new ArrayList<>(oscriptLibraryNameKeysByUri.keySet())) { + removeOScriptDocumentMdoRefs(uri); + } + oscriptLibrariesByNameKey.clear(); + oscriptLibraryNameKeysByUri.clear(); + } + + /** + * Снять регистрации .os-документа в индексе по mdoRef под его lib-именами. + */ + private void removeOScriptDocumentMdoRefs(URI uri) { + var keys = oscriptLibraryNameKeysByUri.get(uri); + if (keys == null) { + return; + } + for (var key : keys) { + var symbol = oscriptLibrariesByNameKey.get(key); + if (symbol != null) { + var group = documentsByMDORef.get(symbol.qualifiedName()); + if (group != null) { + group.remove(symbol.moduleType()); + if (group.isEmpty()) { + documentsByMDORef.remove(symbol.qualifiedName()); + } + } + } + } + } + + /** + * Найти каноничное имя зарегистрированного library-класса OneScript по имени из исходного кода. + * + * @param name имя из кода (произвольный регистр) + * @return каноничное имя класса либо {@code empty} + */ + public Optional findLibraryClass(String name) { + return findOScriptLibrarySymbol(name, ModuleType.OScriptClass); + } + + /** + * Найти каноничное имя зарегистрированного library-модуля OneScript по имени из исходного кода. + * + * @param name имя из кода (произвольный регистр) + * @return каноничное имя модуля либо {@code empty} + */ + public Optional findLibraryModule(String name) { + return findOScriptLibrarySymbol(name, ModuleType.OScriptModule); + } + + private Optional findOScriptLibrarySymbol(String name, ModuleType moduleType) { + if (name == null || name.isBlank()) { + return Optional.empty(); + } + return Optional.ofNullable(oscriptLibrariesByNameKey.get(oscriptNameKey(name))) + .filter(symbol -> symbol.moduleType() == moduleType) + .map(OScriptLibrarySymbol::qualifiedName); + } + + /** + * Нормализация имени 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 qualifiedName каноничное имя + * @param moduleType тип модуля (класс/модуль) + * @param uri URI .os-файла + */ + public record OScriptLibrarySymbol(String qualifiedName, ModuleType moduleType, URI uri) { + } + private DocumentContext createDocumentContext(URI uri) { var documentContext = documentContextProvider.getObject(uri, this); documents.put(uri, documentContext); addMdoRefByUri(uri, documentContext); + // .os-документы библиотек дополнительно индексируются по своим каноничным lib-именам, + // если каталог уже населён индексатором (порядок не фиксирован — см. метод). + registerOScriptDocumentMdoRefs(uri); return documentContext; } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFiller.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFiller.java index b5fdaa22211..04f36240ca2 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFiller.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFiller.java @@ -30,7 +30,6 @@ 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; @@ -85,7 +84,6 @@ public class ReferenceIndexFiller { private final ReferenceIndex index; private final LanguageServerConfiguration configuration; - private final OScriptLibraryIndex oScriptLibraryIndex; @EventListener public void handleEvent(DocumentContextContentChangedEvent event) { @@ -267,35 +265,34 @@ 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 mdoRef = libClass.get(); var range = Ranges.create(typeName.IDENTIFIER()); - var ctor = libraryClassConstructor(libUri.get()); + var ctor = libraryClassConstructor(mdoRef); 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 libraryClassConstructor(URI libUri) { - return Optional.ofNullable(documentContext.getServerContext().getDocument(libUri)) + private Optional libraryClassConstructor(String mdoRef) { + return documentContext.getServerContext().getDocument(mdoRef, ModuleType.OScriptClass) .map(DocumentContext::getSymbolTree) .flatMap(SymbolTree::getConstructor); } @@ -312,41 +309,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(); // Ссылка на сам 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; - } - /** * Добавляет ссылку на модуль по позиции идентификатора, только если идентификатор является * именем общего модуля. Для вызовов вида Справочники.Имя.Метод() ссылка не добавляется, @@ -585,8 +568,8 @@ 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()) .orElse(null); } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/oscript/OScriptLibraryIndex.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/oscript/OScriptLibraryIndex.java index 97be1bf29fa..35676baf1d1 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/oscript/OScriptLibraryIndex.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/oscript/OScriptLibraryIndex.java @@ -138,6 +138,7 @@ public void handleWorkspaceAdded(WorkspaceAddedEvent event) { */ public List reindex(ServerContext serverContext) { oScriptModuleTypeResolver.clear(); + serverContext.clearOScriptLibrarySymbols(); entriesByUri.clear(); entriesByName.clear(); @@ -172,6 +173,8 @@ public void handleDocumentRemoved(ServerContextDocumentRemovedEvent event) { return; } oScriptModuleTypeResolver.unregister(uri); + serverContextProvider.getServerContext(uri) + .ifPresent(sc -> sc.removeOScriptLibrarySymbolsByUri(uri)); for (var entry : entries) { entriesByName.remove(nameKey(entry.qualifiedName())); } @@ -418,6 +421,10 @@ void registerEntry(String rawQualifiedName, Path osFile, EntryKind kind, ServerC entriesByUri.computeIfAbsent(uri, k -> new java.util.concurrent.CopyOnWriteArrayList<>()).add(entry); entriesByName.put(nameKey(qualifiedName), entry); + // Публикуем сущность в каталог ServerContext ДО добавления документа: при создании документа + // (createDocumentContext) он сразу проиндексируется в documentsByMDORef под своим lib-именем. + serverContext.registerOScriptLibrarySymbol(qualifiedName, moduleType, uri); + // Добавляем .os-файл в ServerContext как обычный документ. SymbolTreeComputer, // ReferenceIndexFiller, OScriptModuleMembersProvider и прочие подхватят его // через события. diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/architecture/ArchitectureTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/architecture/ArchitectureTest.java index cfce50266ab..9c38c3df02b 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/architecture/ArchitectureTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/architecture/ArchitectureTest.java @@ -201,10 +201,11 @@ class ArchitectureTest { // зависимости): в байткоде в него никто не входит (ссылки на него — только в Javadoc @link), // поэтому он не нарушает полноту. websocket — лист без внутренних зависимостей. // - // Известные циклы оставлены ОСОЗНАННО (правило их допускает, но они помечены как долг): - // - References↔Types: взаимные ссылки индексов. Разрывается позже. - // Появление НОВЫХ циклов среди уже ацикличных пакетов ловит отдельное правило ниже - // (acyclic_domains_stay_free_of_cycles). + // Цикл References↔Types разорван: type-aware finder'ы переехали в types.references, а + // ReferenceIndexFiller резолвит library-сущности OneScript по mdoRef через ServerContext + // (каталог наполняет types), поэтому references больше не зависит от types — осталось только + // types→references. Появление НОВЫХ циклов среди уже ацикличных пакетов ловит отдельное правило + // ниже (acyclic_domains_stay_free_of_cycles). // // Замечание про inline-константы: codeactions и diagnostics ссылаются на DiagnosticProvider.SOURCE // (public static final String) — javac встраивает значение, ребра в байткоде нет, поэтому Providers @@ -316,13 +317,14 @@ class ArchitectureTest { "Cfg", "Configuration", "Context", "DiagnosticsMetadata", "Formatting", "Infrastructure", "Recognizer", "References", "Types", "Utils") - // Домены-фундамент. References↔Types — известный цикл (см. комментарий выше). + // Домены-фундамент. references — нижний слой разрешения имён, от types НЕ зависит; + // types выше и зависит от references (вывод типов опирается на разрешение имён). .whereLayer("Configuration").mayOnlyAccessLayers( "DiagnosticsMetadata", "Events", "Infrastructure", "Utils") .whereLayer("Context").mayOnlyAccessLayers( "Client", "Configuration", "DiagnosticsMetadata", "Infrastructure", "Utils") .whereLayer("References").mayOnlyAccessLayers( - "Configuration", "Context", "Infrastructure", "Types", "Utils") + "Configuration", "Context", "Infrastructure", "Utils") .whereLayer("Types").mayOnlyAccessLayers( "Configuration", "Context", "Events", "Infrastructure", "References", "Utils") .whereLayer("Cfg").mayOnlyAccessLayers("Utils") From 90637fcd7c261ebba399c34b39c7bf7cac1f1161 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 30 Jun 2026 12:44:05 +0000 Subject: [PATCH 3/4] refactor(context): drive OScript mdoRef registration from the index, not generic doc lifecycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first cut registered .os documents in documentsByMDORef via a hook in the generic createDocumentContext/removeDocument paths (fired for every document, BSL included, no-op for non-OScript) and kept a URI→names reverse map in ServerContext to make that hook cheap. That leaked OScript-specific logic into the generic document lifecycle. OScriptLibraryIndex already owns the .os document lifecycle (it creates and removes them in registerEntry/handleDocumentRemoved and has its own URI→entries index). So drive the documentsByMDORef registration from there: ServerContext now exposes only registerOScriptLibrary(name, moduleType, doc) and removeOScriptLibrary(name, moduleType) (catalog entry + documentsByMDORef key together), the index calls them per entry, and the generic createDocumentContext/removeDocument hooks and the URI→names reverse map are gone. ServerContext keeps just the case-insensitive name catalog (the findLibraryClass/Module canonicalizer). No behaviour change. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01LkYRgB6NZZRRNZmBrdL54M --- .../languageserver/context/ServerContext.java | 115 +++++------------- .../types/oscript/OScriptLibraryIndex.java | 24 ++-- 2 files changed, 45 insertions(+), 94 deletions(-) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java index 0d776e45c81..65a6ec5e049 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java @@ -120,14 +120,12 @@ public class ServerContext { private final Map> documentsByMDORef = new ConcurrentHashMap<>(); /** - * Каталог библиотечных сущностей OneScript: {@code nameKey → (каноничное имя, тип модуля, URI)}. + * Каталог библиотечных сущностей OneScript: {@code nameKey → (каноничное имя, тип модуля)}. * Наполняется извне (индексатором OneScript-библиотек) и служит резолву имени класса/модуля - * библиотеки в каноничный mdoRef — аналогично тому, как {@link #findCommonModule(String)} - * резолвит общий модуль из метаданных конфигурации. Регистронезависимый (ключ — {@code nameKey}). + * библиотеки в каноничное имя — аналогично тому, как {@link #findCommonModule(String)} резолвит + * общий модуль из метаданных конфигурации. Регистронезависимый (ключ — {@code nameKey}). */ private final Map oscriptLibrariesByNameKey = new ConcurrentHashMap<>(); - /** Обратный индекс {@code URI → nameKey'и записей} для снятия при удалении/пере-индексации файла. */ - private final Map> oscriptLibraryNameKeysByUri = new ConcurrentHashMap<>(); private final Map documentLocks = new ConcurrentHashMap<>(); private final Map states = new ConcurrentHashMap<>(); @@ -312,7 +310,6 @@ public void removeDocument(URI uri) { } removeDocumentMdoRefByUri(uri); - removeOScriptDocumentMdoRefs(uri); states.remove(documentContext); documents.remove(uri); documentLocks.remove(uri); @@ -344,7 +341,6 @@ public void clear() { documentsByMDORef.clear(); mdoRefs.clear(); oscriptLibrariesByNameKey.clear(); - oscriptLibraryNameKeysByUri.clear(); documentLocks.clear(); commonModuleCache.invalidateAll(); configurationMetadata.clear(); @@ -485,85 +481,36 @@ public Optional findCommonModule(String name) { } /** - * Зарегистрировать библиотечную сущность OneScript (класс/модуль) в каталоге. - * Вызывается индексатором OneScript-библиотек. + * Зарегистрировать библиотечную сущность OneScript: занести её в каталог имён (для канонизации + * в {@link #findLibraryClass}/{@link #findLibraryModule}) и проиндексировать документ в + * {@code documentsByMDORef} под каноничным именем (чтобы он резолвился через + * {@link #getDocument(String, ModuleType)}). Вызывается индексатором OneScript-библиотек. * - * @param qualifiedName каноничное имя сущности - * @param moduleType {@link ModuleType#OScriptClass} или {@link ModuleType#OScriptModule} - * @param uri URI .os-файла + * @param qualifiedName каноничное имя сущности + * @param moduleType {@link ModuleType#OScriptClass} или {@link ModuleType#OScriptModule} + * @param documentContext документ .os-файла */ - public void registerOScriptLibrarySymbol(String qualifiedName, ModuleType moduleType, URI uri) { - var key = oscriptNameKey(qualifiedName); - oscriptLibrariesByNameKey.put(key, new OScriptLibrarySymbol(qualifiedName, moduleType, uri)); - oscriptLibraryNameKeysByUri.computeIfAbsent(uri, k -> ConcurrentHashMap.newKeySet()).add(key); - registerOScriptDocumentMdoRefs(uri); + public void registerOScriptLibrary(String qualifiedName, ModuleType moduleType, DocumentContext documentContext) { + oscriptLibrariesByNameKey.put(oscriptNameKey(qualifiedName), new OScriptLibrarySymbol(qualifiedName, moduleType)); + documentsByMDORef + .computeIfAbsent(qualifiedName, k -> Collections.synchronizedMap(new EnumMap<>(ModuleType.class))) + .put(moduleType, documentContext); } /** - * Снять все библиотечные записи OneScript, связанные с файлом (при удалении/пере-индексации). - */ - public void removeOScriptLibrarySymbolsByUri(URI uri) { - // сначала снимаем регистрации документа в индексе по mdoRef (пока каталог ещё населён), - // потом чистим сам каталог. - removeOScriptDocumentMdoRefs(uri); - var keys = oscriptLibraryNameKeysByUri.remove(uri); - if (keys != null) { - keys.forEach(oscriptLibrariesByNameKey::remove); - } - } - - /** - * Зарегистрировать .os-документ в индексе по mdoRef под каждым его каноничным lib-именем - * (если документ уже создан). Идемпотентно; вызывается и при создании документа, и при - * наполнении каталога — порядок этих событий не фиксирован. - */ - private void registerOScriptDocumentMdoRefs(URI uri) { - var documentContext = documents.get(uri); - var keys = oscriptLibraryNameKeysByUri.get(uri); - if (documentContext == null || keys == null) { - return; - } - for (var key : keys) { - var symbol = oscriptLibrariesByNameKey.get(key); - if (symbol != null) { - documentsByMDORef - .computeIfAbsent(symbol.qualifiedName(), - k -> Collections.synchronizedMap(new EnumMap<>(ModuleType.class))) - .put(symbol.moduleType(), documentContext); - } - } - } - - /** - * Полностью очистить каталог библиотечных сущностей OneScript и снять связанные регистрации - * документов в индексе по mdoRef. Вызывается перед полной пере-индексацией библиотек. - */ - public void clearOScriptLibrarySymbols() { - for (var uri : new ArrayList<>(oscriptLibraryNameKeysByUri.keySet())) { - removeOScriptDocumentMdoRefs(uri); - } - oscriptLibrariesByNameKey.clear(); - oscriptLibraryNameKeysByUri.clear(); - } - - /** - * Снять регистрации .os-документа в индексе по mdoRef под его lib-именами. + * Снять регистрацию библиотечной сущности OneScript: из каталога имён и из + * {@code documentsByMDORef}. Вызывается индексатором при удалении файла/пере-индексации. + * + * @param qualifiedName каноничное имя сущности + * @param moduleType тип модуля сущности */ - private void removeOScriptDocumentMdoRefs(URI uri) { - var keys = oscriptLibraryNameKeysByUri.get(uri); - if (keys == null) { - return; - } - for (var key : keys) { - var symbol = oscriptLibrariesByNameKey.get(key); - if (symbol != null) { - var group = documentsByMDORef.get(symbol.qualifiedName()); - if (group != null) { - group.remove(symbol.moduleType()); - if (group.isEmpty()) { - documentsByMDORef.remove(symbol.qualifiedName()); - } - } + public void removeOScriptLibrary(String qualifiedName, ModuleType moduleType) { + oscriptLibrariesByNameKey.remove(oscriptNameKey(qualifiedName)); + var group = documentsByMDORef.get(qualifiedName); + if (group != null) { + group.remove(moduleType); + if (group.isEmpty()) { + documentsByMDORef.remove(qualifiedName); } } } @@ -607,13 +554,12 @@ private static String oscriptNameKey(String name) { } /** - * Запись каталога библиотечных сущностей OneScript. + * Запись каталога библиотечных сущностей OneScript (для канонизации имени). * * @param qualifiedName каноничное имя * @param moduleType тип модуля (класс/модуль) - * @param uri URI .os-файла */ - public record OScriptLibrarySymbol(String qualifiedName, ModuleType moduleType, URI uri) { + public record OScriptLibrarySymbol(String qualifiedName, ModuleType moduleType) { } private DocumentContext createDocumentContext(URI uri) { @@ -621,9 +567,6 @@ private DocumentContext createDocumentContext(URI uri) { documents.put(uri, documentContext); addMdoRefByUri(uri, documentContext); - // .os-документы библиотек дополнительно индексируются по своим каноничным lib-именам, - // если каталог уже населён индексатором (порядок не фиксирован — см. метод). - registerOScriptDocumentMdoRefs(uri); return documentContext; } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/oscript/OScriptLibraryIndex.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/oscript/OScriptLibraryIndex.java index 35676baf1d1..f94d9cf7d57 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/oscript/OScriptLibraryIndex.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/oscript/OScriptLibraryIndex.java @@ -138,7 +138,9 @@ public void handleWorkspaceAdded(WorkspaceAddedEvent event) { */ public List reindex(ServerContext serverContext) { oScriptModuleTypeResolver.clear(); - serverContext.clearOScriptLibrarySymbols(); + // снимаем прежние регистрации lib-сущностей в ServerContext (каталог имён + documentsByMDORef) + entriesByName.values().forEach(entry -> + serverContext.removeOScriptLibrary(entry.qualifiedName(), moduleTypeOf(entry.kind()))); entriesByUri.clear(); entriesByName.clear(); @@ -173,10 +175,12 @@ public void handleDocumentRemoved(ServerContextDocumentRemovedEvent event) { return; } oScriptModuleTypeResolver.unregister(uri); - serverContextProvider.getServerContext(uri) - .ifPresent(sc -> sc.removeOScriptLibrarySymbolsByUri(uri)); + var serverContext = serverContextProvider.getServerContext(uri).orElse(null); for (var entry : entries) { entriesByName.remove(nameKey(entry.qualifiedName())); + if (serverContext != null) { + serverContext.removeOScriptLibrary(entry.qualifiedName(), moduleTypeOf(entry.kind())); + } } oScriptModuleMembersProvider.unregister(uri); } @@ -407,7 +411,7 @@ void registerEntry(String rawQualifiedName, Path osFile, EntryKind kind, ServerC // OScriptModuleMembersProvider → TypeRegistry, и в completion-метки. var qualifiedName = Normalizer.normalize(rawQualifiedName, Normalizer.Form.NFC); var uri = Absolute.uri(osFile.toUri()); - var moduleType = kind == EntryKind.CLASS ? ModuleType.OScriptClass : ModuleType.OScriptModule; + var moduleType = moduleTypeOf(kind); // Сначала сообщаем резолверу тип модуля — это нужно, чтобы при первом // событии DocumentContextContentChangedEvent документ уже знал свой // ModuleType (через DocumentContext.computeModuleType фолбэк). @@ -421,16 +425,16 @@ void registerEntry(String rawQualifiedName, Path osFile, EntryKind kind, ServerC entriesByUri.computeIfAbsent(uri, k -> new java.util.concurrent.CopyOnWriteArrayList<>()).add(entry); entriesByName.put(nameKey(qualifiedName), entry); - // Публикуем сущность в каталог ServerContext ДО добавления документа: при создании документа - // (createDocumentContext) он сразу проиндексируется в documentsByMDORef под своим lib-именем. - serverContext.registerOScriptLibrarySymbol(qualifiedName, moduleType, uri); - // Добавляем .os-файл в ServerContext как обычный документ. SymbolTreeComputer, // ReferenceIndexFiller, OScriptModuleMembersProvider и прочие подхватят его // через события. try { var dc = serverContext.addDocument(uri); serverContext.rebuildDocument(dc); + // Регистрируем сущность в каталоге имён ServerContext и индексируем документ в + // documentsByMDORef под каноничным именем — чтобы ReferenceIndexFiller резолвил + // ссылки на эту lib-сущность через ServerContext.getDocument(имя, moduleType). + serverContext.registerOScriptLibrary(qualifiedName, moduleType, dc); // Явный вызов: гарантирует регистрацию USER-типа в актуальном // workspace-scope (event-listener тоже сработает, но он не // обязан выполняться в том же scope/потоке, что и reindex). @@ -445,6 +449,10 @@ void registerEntry(String rawQualifiedName, Path osFile, EntryKind kind, ServerC } } + private static ModuleType moduleTypeOf(EntryKind kind) { + return kind == EntryKind.CLASS ? ModuleType.OScriptClass : ModuleType.OScriptModule; + } + @Nullable private static String libOriginOf(@Nullable Path libRoot) { if (libRoot == null) { From 4687137eff6186a027db3f4781df297cc9adfe96 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 07:50:56 +0000 Subject: [PATCH 4/4] refactor(references): return DocumentContext from findLibraryClass/Module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete the OScript mdoRef unification: a library .os document's mdoRef is now its canonical library name (not the URI), so it is indexed in documentsByMDORef under its own mdoRef — uniformly with BSL objects. ServerContext.findLibraryClass/findLibraryModule now return the resolved DocumentContext instead of the canonical name string; callers get the doc directly and read the reference-index key from doc.getMdoRef(), dropping the extra getDocument(mdoRef, moduleType) round-trip in ReferenceIndexFiller. The library name is seeded into ServerContext (registerOScriptLibraryName) before the document is added, so MdoRefBuilder resolves the mdoRef of an .os file without an MD object to that name. Also fix three dangling javadoc @link references to KeywordReferenceFinder, left in context.symbol/hover/providers after the finder moved to types.references. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01LkYRgB6NZZRRNZmBrdL54M --- .../languageserver/context/MdoRefBuilder.java | 4 + .../languageserver/context/ServerContext.java | 95 ++++++++++++++----- .../context/symbol/KeywordSymbol.java | 2 +- .../KeywordSymbolMarkupContentBuilder.java | 2 +- .../providers/HoverProvider.java | 2 +- .../references/ReferenceIndexFiller.java | 16 +--- .../types/oscript/OScriptLibraryIndex.java | 9 +- 7 files changed, 88 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/MdoRefBuilder.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/MdoRefBuilder.java index 08a25d7ce9e..3c3223e3e00 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/MdoRefBuilder.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/MdoRefBuilder.java @@ -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); } diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java index 65a6ec5e049..1a53450a6e4 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/ServerContext.java @@ -120,12 +120,22 @@ public class ServerContext { private final Map> documentsByMDORef = new ConcurrentHashMap<>(); /** - * Каталог библиотечных сущностей OneScript: {@code nameKey → (каноничное имя, тип модуля)}. + * Каталог библиотечных сущностей OneScript: {@code nameKey → (тип модуля, документ)}. * Наполняется извне (индексатором OneScript-библиотек) и служит резолву имени класса/модуля - * библиотеки в каноничное имя — аналогично тому, как {@link #findCommonModule(String)} резолвит - * общий модуль из метаданных конфигурации. Регистронезависимый (ключ — {@code nameKey}). + * библиотеки в документ .os-файла — аналогично тому, как {@link #findCommonModule(String)} + * резолвит общий модуль из метаданных конфигурации. Регистронезависимый (ключ — {@code nameKey}); + * под несколькими именами (алиасами) может резолвиться один и тот же документ. */ private final Map oscriptLibrariesByNameKey = new ConcurrentHashMap<>(); + /** + * {@code URI .os-файла → каноничное имя} его library-сущности. Наполняется индексатором + * OneScript-библиотек до добавления документа в контекст, чтобы {@code mdoRef} + * такого документа вычислялся как имя библиотеки (см. {@link MdoRefBuilder}), а не как URI: + * тогда документ индексируется в {@link #documentsByMDORef} под своим именем и резолвится + * единообразно с BSL-объектами. Первое зарегистрированное имя выигрывает (у одного файла + * может быть несколько ролей — см. {@code OScriptModuleTypeResolver}). + */ + private final Map oscriptLibraryNamesByUri = new ConcurrentHashMap<>(); private final Map documentLocks = new ConcurrentHashMap<>(); private final Map states = new ConcurrentHashMap<>(); @@ -341,6 +351,7 @@ public void clear() { documentsByMDORef.clear(); mdoRefs.clear(); oscriptLibrariesByNameKey.clear(); + oscriptLibraryNamesByUri.clear(); documentLocks.clear(); commonModuleCache.invalidateAll(); configurationMetadata.clear(); @@ -481,67 +492,99 @@ public Optional findCommonModule(String name) { } /** - * Зарегистрировать библиотечную сущность OneScript: занести её в каталог имён (для канонизации + * Привязать {@code URI .os-файла} к каноничному имени его library-сущности до + * добавления документа в контекст. Нужно, чтобы {@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 findOScriptLibraryName(URI uri) { + return Optional.ofNullable(oscriptLibraryNamesByUri.get(uri)); + } + + /** + * Зарегистрировать библиотечную сущность OneScript: занести её в каталог имён (для резолва * в {@link #findLibraryClass}/{@link #findLibraryModule}) и проиндексировать документ в - * {@code documentsByMDORef} под каноничным именем (чтобы он резолвился через - * {@link #getDocument(String, ModuleType)}). Вызывается индексатором OneScript-библиотек. + * {@code documentsByMDORef} под его {@code mdoRef} (= каноничным именем, см. + * {@link #registerOScriptLibraryName}), чтобы он резолвился через + * {@link #getDocument(String, ModuleType)}. Вызывается индексатором OneScript-библиотек. * - * @param qualifiedName каноничное имя сущности + * @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(qualifiedName, moduleType)); + oscriptLibrariesByNameKey.put(oscriptNameKey(qualifiedName), new OScriptLibrarySymbol(moduleType, documentContext)); documentsByMDORef - .computeIfAbsent(qualifiedName, k -> Collections.synchronizedMap(new EnumMap<>(ModuleType.class))) + .computeIfAbsent(documentContext.getMdoRef(), k -> Collections.synchronizedMap(new EnumMap<>(ModuleType.class))) .put(moduleType, documentContext); } /** - * Снять регистрацию библиотечной сущности OneScript: из каталога имён и из - * {@code documentsByMDORef}. Вызывается индексатором при удалении файла/пере-индексации. + * Снять регистрацию библиотечной сущности OneScript: из каталога имён, из + * {@code documentsByMDORef} и из привязки {@code URI → имя}. Вызывается индексатором при + * удалении файла/пере-индексации. * * @param qualifiedName каноничное имя сущности * @param moduleType тип модуля сущности */ public void removeOScriptLibrary(String qualifiedName, ModuleType moduleType) { - oscriptLibrariesByNameKey.remove(oscriptNameKey(qualifiedName)); - var group = documentsByMDORef.get(qualifiedName); + 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(qualifiedName); + documentsByMDORef.remove(documentContext.getMdoRef()); } } + oscriptLibraryNamesByUri.remove(documentContext.getUri()); } /** - * Найти каноничное имя зарегистрированного library-класса OneScript по имени из исходного кода. + * Найти документ зарегистрированного library-класса OneScript по имени из исходного кода. * * @param name имя из кода (произвольный регистр) - * @return каноничное имя класса либо {@code empty} + * @return документ .os-файла класса либо {@code empty} */ - public Optional findLibraryClass(String name) { + public Optional findLibraryClass(String name) { return findOScriptLibrarySymbol(name, ModuleType.OScriptClass); } /** - * Найти каноничное имя зарегистрированного library-модуля OneScript по имени из исходного кода. + * Найти документ зарегистрированного library-модуля OneScript по имени из исходного кода. * * @param name имя из кода (произвольный регистр) - * @return каноничное имя модуля либо {@code empty} + * @return документ .os-файла модуля либо {@code empty} */ - public Optional findLibraryModule(String name) { + public Optional findLibraryModule(String name) { return findOScriptLibrarySymbol(name, ModuleType.OScriptModule); } - private Optional findOScriptLibrarySymbol(String name, ModuleType moduleType) { + private Optional findOScriptLibrarySymbol(String name, ModuleType moduleType) { if (name == null || name.isBlank()) { return Optional.empty(); } return Optional.ofNullable(oscriptLibrariesByNameKey.get(oscriptNameKey(name))) .filter(symbol -> symbol.moduleType() == moduleType) - .map(OScriptLibrarySymbol::qualifiedName); + .map(OScriptLibrarySymbol::documentContext); } /** @@ -554,12 +597,12 @@ private static String oscriptNameKey(String name) { } /** - * Запись каталога библиотечных сущностей OneScript (для канонизации имени). + * Запись каталога библиотечных сущностей OneScript. * - * @param qualifiedName каноничное имя - * @param moduleType тип модуля (класс/модуль) + * @param moduleType тип модуля (класс/модуль) + * @param documentContext документ .os-файла сущности */ - public record OScriptLibrarySymbol(String qualifiedName, ModuleType moduleType) { + public record OScriptLibrarySymbol(ModuleType moduleType, DocumentContext documentContext) { } private DocumentContext createDocumentContext(URI uri) { diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/symbol/KeywordSymbol.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/symbol/KeywordSymbol.java index ab9ad2e28c0..981d2a04427 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/symbol/KeywordSymbol.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/context/symbol/KeywordSymbol.java @@ -28,7 +28,7 @@ /** * Synthetic-символ BSL-keyword'а ({@code Если}, {@code Истина}, {@code Цикл}…). *

- * Создаётся 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}. diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/KeywordSymbolMarkupContentBuilder.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/KeywordSymbolMarkupContentBuilder.java index c4d676f2a5d..6336a36cb67 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/KeywordSymbolMarkupContentBuilder.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/hover/KeywordSymbolMarkupContentBuilder.java @@ -35,7 +35,7 @@ * ({@code Если}, {@code Истина}, {@code Цикл}…). *

* {@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-формат: *

diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/HoverProvider.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/HoverProvider.java
index 4c6e4360677..77bb84810b8 100644
--- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/HoverProvider.java
+++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/HoverProvider.java
@@ -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}.
  *
diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFiller.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFiller.java
index 04f36240ca2..2f9e2b9e3c5 100644
--- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFiller.java
+++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/references/ReferenceIndexFiller.java
@@ -25,10 +25,8 @@
 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.context.MdoRefBuilder;
 import com.github._1c_syntax.bsl.languageserver.utils.Methods;
@@ -269,10 +267,11 @@ private void tryRegisterLibraryClassReference(BSLParser.NewExpressionContext ctx
       if (libClass.isEmpty()) {
         return;
       }
-      var mdoRef = libClass.get();
+      var libDocument = libClass.get();
+      var mdoRef = libDocument.getMdoRef();
       var range = Ranges.create(typeName.IDENTIFIER());
 
-      var ctor = libraryClassConstructor(mdoRef);
+      var ctor = libDocument.getSymbolTree().getConstructor();
       if (ctor.isPresent()) {
         index.addMethodCall(
           documentContext.getUri(),
@@ -291,12 +290,6 @@ private void tryRegisterLibraryClassReference(BSLParser.NewExpressionContext ctx
       }
     }
 
-    private Optional libraryClassConstructor(String mdoRef) {
-      return documentContext.getServerContext().getDocument(mdoRef, ModuleType.OScriptClass)
-        .map(DocumentContext::getSymbolTree)
-        .flatMap(SymbolTree::getConstructor);
-    }
-
     /**
      * Если идентификатор соответствует имени зарегистрированного OneScript
      * library-модуля, регистрирует:
@@ -313,7 +306,7 @@ private void tryRegisterLibraryModuleCall(@Nullable TerminalNode identifier, Opt
       if (libModule.isEmpty()) {
         return;
       }
-      var mdoRef = libModule.get();
+      var mdoRef = libModule.get().getMdoRef();
 
       // Ссылка на сам identifier модуля — нужна для go-to-definition без точки.
       index.addModuleReference(
@@ -570,6 +563,7 @@ public ParserRuleContext visitAssignment(BSLParser.AssignmentContext ctx) {
       }
       return documentContext.getServerContext()
         .findLibraryClass(typeName.IDENTIFIER().getText())
+        .map(DocumentContext::getMdoRef)
         .orElse(null);
     }
 
diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/oscript/OScriptLibraryIndex.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/oscript/OScriptLibraryIndex.java
index f94d9cf7d57..800ce82cc0c 100644
--- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/oscript/OScriptLibraryIndex.java
+++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/types/oscript/OScriptLibraryIndex.java
@@ -425,6 +425,11 @@ void registerEntry(String rawQualifiedName, Path osFile, EntryKind kind, ServerC
     entriesByUri.computeIfAbsent(uri, k -> new java.util.concurrent.CopyOnWriteArrayList<>()).add(entry);
     entriesByName.put(nameKey(qualifiedName), entry);
 
+    // Привязываем URI → каноничное имя ДО addDocument: тогда mdoRef создаваемого документа
+    // вычислится как имя библиотеки (а не URI), и он проиндексируется в documentsByMDORef под
+    // своим именем — единообразно с BSL-объектами.
+    serverContext.registerOScriptLibraryName(uri, qualifiedName);
+
     // Добавляем .os-файл в ServerContext как обычный документ. SymbolTreeComputer,
     // ReferenceIndexFiller, OScriptModuleMembersProvider и прочие подхватят его
     // через события.
@@ -432,8 +437,8 @@ void registerEntry(String rawQualifiedName, Path osFile, EntryKind kind, ServerC
       var dc = serverContext.addDocument(uri);
       serverContext.rebuildDocument(dc);
       // Регистрируем сущность в каталоге имён ServerContext и индексируем документ в
-      // documentsByMDORef под каноничным именем — чтобы ReferenceIndexFiller резолвил
-      // ссылки на эту lib-сущность через ServerContext.getDocument(имя, moduleType).
+      // documentsByMDORef под его mdoRef (= каноничным именем) — чтобы ReferenceIndexFiller
+      // резолвил ссылки на эту lib-сущность через ServerContext.getDocument(имя, moduleType).
       serverContext.registerOScriptLibrary(qualifiedName, moduleType, dc);
       // Явный вызов: гарантирует регистрацию USER-типа в актуальном
       // workspace-scope (event-listener тоже сработает, но он не