Skip to content

feat(inlayhints): хинты типов и inlayHint/resolve#4100

Merged
nixel2007 merged 4 commits into
developfrom
claude/inlayhint-type-hints
Jun 14, 2026
Merged

feat(inlayhints): хинты типов и inlayHint/resolve#4100
nixel2007 merged 4 commits into
developfrom
claude/inlayhint-type-hints

Conversation

@nixel2007

@nixel2007 nixel2007 commented Jun 13, 2026

Copy link
Copy Markdown
Member

Проблема

До изменений inlay-хинты ограничивались kind=Parameter (имена аргументов вызовов и метки сложности) — выведенного типа переменной не показывалось. Кроме того, resolveProvider=false: tooltip строился жадно, хотя дорогие поля можно отдавать лениво.

Решение

Часть 1 — хинты выведенного типа (InlayHintKind.Type).
Новый VariableTypeInlayHintSupplier (Spring @Component, id variableType) для присваивания Перем = Выражение показывает выведенный тип сразу после имени переменной: Контрагент: Массив = Новый Массив(). Тип берётся через TypeService.expressionTypesAt по правой части. Хинт даётся только когда:

  • цель — простая переменная (lValue.IDENTIFIER() без acceptor, т.е. не Перем.Поле = ...);
  • выведен ровно один нетривиальный тип (не Произвольный/any, не UNKNOWN);
  • тип не очевиден из литерала справа (= 1, = "Текст" — пропускаются).

Включение управляется штатным механизмом InlayHintsConfiguration.supplierIsEnabled по id сапплаера (как у complexity-тогглов).

Часть 2 — отложенный tooltip через inlayHint/resolve.
Заявлен resolveProvider=true. Tooltip (полное описание типа) строится лениво: при жадном расчёте в InlayHint.data кладётся VariableTypeInlayHintData (id сапплаера, uri, имя типа); BSLTextDocumentService.resolveInlayHint находит документ по uri и делегирует в InlayHintProvider.resolveInlayHint, который по id диспетчеризует в сапплаер-владельца (InlayHintSupplier.resolve — новый default-метод). Round-trip data единообразно конвертируется через JsonMapper.convertValue (и для in-process объекта, и для JSON от клиента). Лёгкие поля (label/position/kind) остаются жадными; после резолва data очищается.

Тесты

  • VariableTypeInlayHintSupplierTest — присваивание с выводимым типом → хинт kind=Type с именем типа и позицией после имени; литеральное присваивание → нет хинта.
  • VariableTypeInlayHintResolveTest — жадный хинт несёт data без tooltip; после resolveInlayHint tooltip заполнен описанием типа, data очищена.
  • Регресс: InlayHintProviderTest, SourceDefinedMethodCallInlayHintSupplierTest — зелёные.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added inlay hints that display inferred variable types for simple assignments.
    • Enabled lazy inlay hint tooltip resolution so details load on demand.
    • Updated the language server to advertise inlay hint resolution support to clients.
  • Bug Fixes
    • Improved fallback behavior for unknown inferred types to still show the stored type name.
  • Tests
    • Added/updated tests covering inlay hint generation and lazy tooltip resolution scenarios.

nixel2007 and others added 2 commits June 13, 2026 12:26
Новый VariableTypeInlayHintSupplier показывает выведенный тип переменной
сразу после её имени в присваивании (Контрагент: Массив = ...). Хинт даётся
только для простой переменной-цели с единственным нетривиальным типом
(не Произвольный/any) и не дублирует тип, очевидный из литерала справа.

Co-Authored-By: Claude Fable 5 <[email protected]>
Заявлен resolveProvider=true; tooltip хинтов типа строится лениво в
inlayHint/resolve, а не жадно при расчёте. Хинт несёт в data ссылку на тип
(VariableTypeInlayHintData), по которой InlayHintProvider.resolveInlayHint
находит сапплаер-владельца и дорассчитывает полное описание типа. Лёгкие
поля (label/position/kind) остаются жадными; BSLTextDocumentService
переопределяет resolveInlayHint и делегирует в провайдер.

Co-Authored-By: Claude Fable 5 <[email protected]>
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements lazy inlay hint resolution for the BSL Language Server. The server now advertises resolution support, adds a new variable-type hint supplier that generates type hints for assignments and resolves their tooltips on demand, and wires the resolution pipeline through the service and provider layers with comprehensive test coverage.

Changes

Inlay Hint Resolution & Variable Type Hints

Layer / File(s) Summary
LSP capability advertisement
src/main/java/.../BSLLanguageServer.java
Server advertises inlay hint resolution support by setting resolveProvider = true in LSP capabilities during initialization.
Resolution interfaces & data contracts
src/main/java/.../InlayHintSupplier.java, src/main/java/.../InlayHintData.java, src/main/java/.../DefaultInlayHintData.java, src/main/java/.../VariableTypeInlayHintData.java
InlayHintSupplier becomes generic with a default resolve() hook and required getInlayHintDataClass() method. InlayHintData interface defines URI and supplier ID accessors for client round-trips. Two concrete data classes carry supplier-specific metadata: DefaultInlayHintData stores URI and supplier ID, while VariableTypeInlayHintData additionally stores the inferred type name for deferred tooltip resolution.
Update existing suppliers for generic interface
src/main/java/.../AbstractComplexityInlayHintSupplier.java, src/main/java/.../AbstractMethodCallInlayHintSupplier.java
Complexity and method-call suppliers are updated to implement the generic InlayHintSupplier<DefaultInlayHintData> and override getInlayHintDataClass() to expose their data type.
Service entry point for resolution
src/main/java/.../BSLTextDocumentService.java
New resolveInlayHint() method receives LSP inlayHint/resolve requests, extracts document URI from hint data, looks up DocumentContext, and delegates to the provider under a fresh document context.
Provider orchestration & data extraction
src/main/java/.../InlayHintProvider.java
Provider refactored to use a Map<String, InlayHintSupplier<InlayHintData>> keyed by supplier ID. Implements resolveInlayHint() to orchestrate resolution by finding the matching supplier, delegating hint resolution, and clearing data to reduce payload. Provides extractData() helper to parse JSON-round-tripped hint data via JsonMapper.
Variable type hints supplier implementation
src/main/java/.../VariableTypeInlayHintSupplier.java
Generates type inlay hints for assignment statements with simple identifier targets and non-trivial expressions. Formats hints as : <type> positioned after variable names and stores lazy tooltip data. Implements resolve() to reconstruct type references from stored data via TypeService, and populates markdown tooltips with type display names and optional descriptions. Includes helper methods to filter eligible assignments and suppress trivial or constructor-style expressions.
Configuration for polymorphic serialization & bean wiring
src/main/java/.../JsonMapperConfiguration.java, src/main/java/.../InlayHintsConfiguration.java
JsonMapperConfiguration extended to register inlay hint data subtypes for Jackson polymorphism. InlayHintsConfiguration introduces inlayHintSuppliersById() bean to convert flat supplier collections into a Map keyed by supplier ID for provider lookup.
Variable type supplier hint generation tests
src/test/java/.../VariableTypeInlayHintSupplierTest.java
Test suite validates hint generation conditions: produces exactly one hint for simple variable assignments with inferred types, excludes new-expression assignments, literal assignments, member-target assignments, unresolvable types, and out-of-range requests.
Inlay hint resolution behavior tests
src/test/java/.../VariableTypeInlayHintResolveTest.java, src/test/java/.../InlayHintProviderTest.java
Tests verify lazy tooltip resolution pipeline: tooltips populate on-demand while hint data clears, hints without resolution data remain unchanged, graceful fallback for unknown types, and URI extraction and parsing of hint data via extractData() helper.

Sequence Diagram

sequenceDiagram
  participant Client
  participant BSLTextDocumentService
  participant InlayHintProvider
  participant VariableTypeInlayHintSupplier
  participant TypeService
  
  Client->>BSLTextDocumentService: inlayHint/resolve (unresolved hint with data)
  BSLTextDocumentService->>BSLTextDocumentService: extract URI from hint data
  BSLTextDocumentService->>BSLTextDocumentService: lookup DocumentContext
  BSLTextDocumentService->>InlayHintProvider: resolveInlayHint(documentContext, hint)
  InlayHintProvider->>InlayHintProvider: extract supplierId from hint data
  InlayHintProvider->>InlayHintProvider: find matching VariableTypeInlayHintSupplier
  InlayHintProvider->>VariableTypeInlayHintSupplier: resolve(documentContext, hint, data)
  VariableTypeInlayHintSupplier->>TypeService: resolve type reference by name
  TypeService-->>VariableTypeInlayHintSupplier: TypeRef with display name & description
  VariableTypeInlayHintSupplier->>VariableTypeInlayHintSupplier: set markdown tooltip
  VariableTypeInlayHintSupplier-->>InlayHintProvider: resolved hint with tooltip
  InlayHintProvider->>InlayHintProvider: clear hint data field
  InlayHintProvider-->>BSLTextDocumentService: resolved hint (tooltip populated, data cleared)
  BSLTextDocumentService-->>Client: inlayHint/resolve response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • 1c-syntax/bsl-language-server#3735: Extends JsonMapperConfiguration's polymorphic subtype registration to include inlay-hint data, building directly on this PR's data contract and Jackson configuration setup.

Poem

🐰 A rabbit's ode to lazy hints
When types hide in hints yet unfold,
We resolve them gently, whisker by whisker,
Till tooltips bloom and data flows home,
One supplier, one promise: hints made whole

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main feature additions: type inference hints and lazy-loaded tooltip resolution via inlayHint/resolve.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/inlayhint-type-hints

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Test Results

 3 288 files  +12   3 288 suites  +12   1h 26m 35s ⏱️ + 2m 36s
 3 204 tests +12   3 189 ✅ +12  15 💤 ±0  0 ❌ ±0 
19 224 runs  +72  19 134 ✅ +72  90 💤 ±0  0 ❌ ±0 

Results for commit 65eb4c2. ± Comparison against base commit 8ce9f2f.

♻️ This comment has been updated with latest results.

… Sonar

VariableTypeInlayHintSupplier: simpleTargetIdentifier/inferType/extractData/
toInlayHint теперь возвращают Optional вместо null (методы @NullMarked-пакета).
getInlayHints: flatMap(Optional::stream) + toList() (S6204). resolve и getInlayHints
переведены на Optional.isEmpty(); убраны ставшие мёртвыми null-проверки (S2583/S2589):
expression==null в isTrivialLiteral (параметр @nonnull), identifier/inferredType/data.
S864: скобки вокруг конкатенации в тернаре tooltip.

InlayHintProvider: dataField/extractSupplierId возвращают Optional<String>;
extractUri через map(Absolute::uri); resolveInlayHint на Optional.isEmpty().

Тесты: непокрытые ветки type-хинтов и resolve — цель-член (хинта нет),
тип не выводится (хинта нет), запрос вне диапазона, resolve без data,
resolve с неизвестным типом (fallback по имени), provider.resolveInlayHint
без data и с неизвестным сапплаером, extractUri пусто/из data.

Co-Authored-By: Claude Fable 5 <[email protected]>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java`:
- Around line 647-650: The call to withFreshDocumentContext(documentContext, ()
-> inlayHintProvider.resolveInlayHint(documentContext, unresolved)) can return
null if the context disappears; update the resolve path in
BSLTextDocumentService so you capture the result of withFreshDocumentContext
into a local (e.g., resolved = withFreshDocumentContext(...)) and if resolved is
null return the original unresolved inlay hint instead of propagating null;
reference the methods withFreshDocumentContext and
inlayHintProvider.resolveInlayHint and ensure the public resolveInlayHint path
never returns null by falling back to the original hint.

In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/InlayHintProvider.java`:
- Around line 133-143: InlayHintProvider.java (133-143): in dataField(InlayHint,
String) guard jsonMapper.convertValue(rawData, Map.class) in a try/catch and
treat any exception or a null result as "no data" by returning Optional.empty();
also check dataMap == null before calling get(...).
VariableTypeInlayHintSupplier.java (130-139): in the code path that reconstructs
VariableTypeInlayHintData (the block that currently builds a TypeRef from the
deserialized data), treat a missing or blank typeName as unresolved and return
the original inlayHint (no change) instead of creating/using an invalid TypeRef.
VariableTypeInlayHintSupplier.java (154-164): wrap
jsonMapper.convertValue(rawData, VariableTypeInlayHintData.class) in a try/catch
and return Optional.empty() when conversion throws or yields null so downstream
resolve(...) sees empty maybeData and no-op.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 601edaea-1fbf-4bfb-a1b3-4046157753a6

📥 Commits

Reviewing files that changed from the base of the PR and between 8ce9f2f and 65eb4c2.

⛔ Files ignored due to path filters (1)
  • src/test/resources/inlayhints/VariableTypeInlayHintSupplier.bsl is excluded by !src/test/resources/**
📒 Files selected for processing (9)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLLanguageServer.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/InlayHintSupplier.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintData.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplier.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/InlayHintProvider.java
  • src/test/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintResolveTest.java
  • src/test/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplierTest.java
  • src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/InlayHintProviderTest.java

Comment on lines +647 to +650
return withFreshDocumentContext(
documentContext,
() -> inlayHintProvider.resolveInlayHint(documentContext, unresolved)
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ensure resolveInlayHint never resolves to null on context races.

If server context disappears between pre-check and async execution, withFreshDocumentContext(...) can produce null, which leaks through this new public resolve path instead of returning the original hint.

Suggested fix
   return withFreshDocumentContext(
     documentContext,
     () -> inlayHintProvider.resolveInlayHint(documentContext, unresolved)
-  );
+  ).thenApply(resolved -> resolved == null ? unresolved : resolved);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return withFreshDocumentContext(
documentContext,
() -> inlayHintProvider.resolveInlayHint(documentContext, unresolved)
);
return withFreshDocumentContext(
documentContext,
() -> inlayHintProvider.resolveInlayHint(documentContext, unresolved)
).thenApply(resolved -> resolved == null ? unresolved : resolved);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java`
around lines 647 - 650, The call to withFreshDocumentContext(documentContext, ()
-> inlayHintProvider.resolveInlayHint(documentContext, unresolved)) can return
null if the context disappears; update the resolve path in
BSLTextDocumentService so you capture the result of withFreshDocumentContext
into a local (e.g., resolved = withFreshDocumentContext(...)) and if resolved is
null return the original unresolved inlay hint instead of propagating null;
reference the methods withFreshDocumentContext and
inlayHintProvider.resolveInlayHint and ensure the public resolveInlayHint path
never returns null by falling back to the original hint.

Comment on lines +133 to +143
private Optional<String> dataField(InlayHint inlayHint, String field) {
var rawData = inlayHint.getData();
if (rawData == null) {
return Optional.empty();
}
// Клиент присылает data назад как JSON-объект (round-trip); in-process
// (тесты, single-jvm клиент) — как исходный объект сапплаера. Конвертируем
// через JsonMapper в карту единообразно для обоих случаев.
Map<String, Object> dataMap = jsonMapper.convertValue(rawData, Map.class);
var value = dataMap.get(field);
return value == null ? Optional.empty() : Optional.of(value.toString());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the exact files (in case paths differ)
fd -a "InlayHintProvider.java" src/main/java | head -n 20
fd -a "VariableTypeInlayHintSupplier.java" src/main/java | head -n 20

# Show the referenced line ranges with context
sed -n '110,170p' src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/InlayHintProvider.java
echo "----"
sed -n '110,200p' src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplier.java

Repository: 1c-syntax/bsl-language-server

Length of output: 6364


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --hidden --no-mmap "convertValue\(\s*rawData\s*,\s*Map\.class\s*\)" src/main/java || true
rg -n --hidden --no-mmap "convertValue\(\s*rawData\s*,\s*VariableTypeInlayHintData\.class\s*\)" src/main/java || true
rg -n --hidden --no-mmap "getData\(\)" src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints src/main/java/com/github/_1c_syntax/bsl/languageserver/providers || true

Repository: 1c-syntax/bsl-language-server

Length of output: 2732


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find jsonMapper declarations in the two files
rg -n "jsonMapper" src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/InlayHintProvider.java
rg -n "jsonMapper" src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplier.java

# Show surrounding sections where jsonMapper is injected/constructed
sed -n '1,220p' src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/InlayHintProvider.java | sed -n '1,220p'

Repository: 1c-syntax/bsl-language-server

Length of output: 7279


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show VariableTypeInlayHintSupplier around jsonMapper usage and resolve
sed -n '1,240p' src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplier.java

# Try to locate JsonMapper wrapper in repo (tools/...) if present
rg -n "package tools\.jackson\.databind\.json;|class JsonMapper" -S . || true
rg -n "tools\.jackson\.databind\.json\.JsonMapper" -S src/main/java || true

Repository: 1c-syntax/bsl-language-server

Length of output: 11305


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show JsonMapperConfiguration
sed -n '1,240p' src/main/java/com/github/_1c_syntax/bsl/languageserver/databind/JsonMapperConfiguration.java

# Find how JsonMapper bean is created/ configured
rg -n "new JsonMapper|JsonMapper\.builder|Builder" src/main/java/com/github/_1c_syntax/bsl/languageserver/databind/JsonMapperConfiguration.java src/main/java/com/github/_1c_syntax/bsl/languageserver -S || true

Repository: 1c-syntax/bsl-language-server

Length of output: 42433


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "resolveInlayHint\\(" src/main/java/com/github/_1c_syntax/bsl/languageserver || true
rg -n "inlayHint/resolve|inlayHint resolve|resolve\\s*\\(" src/main/java/com/github/_1c_syntax/bsl/languageserver -S | head -n 50

Repository: 1c-syntax/bsl-language-server

Length of output: 9026


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '600,720p' src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java

Repository: 1c-syntax/bsl-language-server

Length of output: 4394


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "withFreshDocumentContext\\(" -S src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java
# Show definition area
rg -n "withFreshDocumentContext" src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java
sed -n '1,140p' src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java

Repository: 1c-syntax/bsl-language-server

Length of output: 10681


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1000,1120p' src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java

Repository: 1c-syntax/bsl-language-server

Length of output: 3416


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "convertValue\\(" src/main/java/com/github/_1c_syntax/bsl/languageserver | head -n 200
rg -n "try \\{[^}]*convertValue\\(" -S src/main/java/com/github/_1c_syntax/bsl/languageserver || true
rg -n "catch \\(.*\\).*convertValue\\(" -S src/main/java/com/github/_1c_syntax/bsl/languageserver || true

Repository: 1c-syntax/bsl-language-server

Length of output: 440


🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -a "VariableTypeInlayHintData.java" src/main/java
sed -n '1,200p' src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintData.java

Repository: 1c-syntax/bsl-language-server

Length of output: 1815


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "Optional<\\s*TypeRef\\s*>\\s*resolve\\(" -S src/main/java/com/github/_1c_syntax/bsl/languageserver/types src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeService.java
sed -n '1,220p' src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeService.java

Repository: 1c-syntax/bsl-language-server

Length of output: 10550


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '240,340p' src/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeService.java

Repository: 1c-syntax/bsl-language-server

Length of output: 4331


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '200,360p' src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistry.java

Repository: 1c-syntax/bsl-language-server

Length of output: 6279


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' src/main/java/com/github/_1c_syntax/bsl/languageserver/types/model/TypeRef.java
rg -n "record TypeRef|class TypeRef" src/main/java/com/github/_1c_syntax/bsl/languageserver/types/model/TypeRef.java

Repository: 1c-syntax/bsl-language-server

Length of output: 7229


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "displayName\\(" src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistry.java
rg -n "getDescription\\(" -S src/main/java/com/github/_1c_syntax/bsl/languageserver/types src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistry.java

# Show relevant parts around displayName and description methods
sed -n '1,260p' src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistry.java | head -n 260

Repository: 1c-syntax/bsl-language-server

Length of output: 16964


Handle malformed/deserialized InlayHint.data safely to prevent inlayHint/resolve from failing
InlayHint.data coming from the client is deserialized with jsonMapper.convertValue(...) without any failure handling; if conversion throws (or returns null), the exception propagates (the LSP service doesn’t catch it) and breaks resolveInlayHint (also via extractUri/extractSupplierId).

private Optional<String> dataField(InlayHint inlayHint, String field) {
  var rawData = inlayHint.getData();
  if (rawData == null) {
    return Optional.empty();
  }
  // Клиент присылает data назад как JSON-объект (round-trip); in-process
  // (тесты, single-jvm клиент) — как исходный объект сапплаера. Конвертируем
  // через JsonMapper в карту единообразно для обоих случаев.
  Map<String, Object> dataMap = jsonMapper.convertValue(rawData, Map.class);
  var value = dataMap.get(field);
  return value == null ? Optional.empty() : Optional.of(value.toString());
}
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/InlayHintProvider.java#L133-L143: wrap jsonMapper.convertValue(rawData, Map.class) and subsequent dataMap.get(...) in a safe block; on any conversion failure (or dataMap == null) return Optional.empty().
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplier.java#L154-L164: wrap jsonMapper.convertValue(rawData, VariableTypeInlayHintData.class); on conversion failure (or null result) return Optional.empty() so resolve(...) can no-op via maybeData.isEmpty().
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplier.java#L130-L139: treat reconstructed VariableTypeInlayHintData with missing/blank typeName as unresolved (return inlayHint), instead of constructing/using a TypeRef from invalid data.
📍 Affects 2 files
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/InlayHintProvider.java#L133-L143 (this comment)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplier.java#L130-L139
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplier.java#L154-L164
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/InlayHintProvider.java`
around lines 133 - 143, InlayHintProvider.java (133-143): in
dataField(InlayHint, String) guard jsonMapper.convertValue(rawData, Map.class)
in a try/catch and treat any exception or a null result as "no data" by
returning Optional.empty(); also check dataMap == null before calling get(...).
VariableTypeInlayHintSupplier.java (130-139): in the code path that reconstructs
VariableTypeInlayHintData (the block that currently builds a TypeRef from the
deserialized data), treat a missing or blank typeName as unresolved and return
the original inlayHint (no change) instead of creating/using an invalid TypeRef.
VariableTypeInlayHintSupplier.java (154-164): wrap
jsonMapper.convertValue(rawData, VariableTypeInlayHintData.class) in a try/catch
and return Optional.empty() when conversion throws or yields null so downstream
resolve(...) sees empty maybeData and no-op.

…ay hints

Зеркалит инфраструктуру данных линз для inlay hints (замечания 1, 5, 6):
- InlayHintData (интерфейс, @JsonTypeInfo по id) + DefaultInlayHintData
- InlayHintSupplier стал generic <T extends InlayHintData> с
  getInlayHintDataClass() и типизированным resolve(ctx, unresolved, data)
- VariableTypeInlayHintData переведён в @value @NonFinal; поле supplierId
  переименовано в id (замечание 1)
- извлечение данных вынесено в InlayHintProvider.extractData (замечание 5),
  карта сапплаеров собирается в поле inlayHintSuppliersById (замечание 6)
- регистрация Jackson-подтипов inlay hints в JsonMapperConfiguration
- BSLTextDocumentService.resolveInlayHint использует data.getUri()

Замечание 4: Ranges.create(identifier) вместо create(identifier, identifier).
Замечание 2: пропуск хинта для конструктора «Новый Тип(...)» — тип очевиден.

Co-Authored-By: Claude Fable 5 <[email protected]>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplier.java (1)

145-147: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard typeName before resolution to keep malformed data a no-op.

resolve(...) trusts data.getTypeName() unconditionally. If deserialization yields null/blank, this path can fail or produce invalid tooltip state; unresolved hint should be returned unchanged in that case.

Suggested fix
   public InlayHint resolve(
     DocumentContext documentContext,
     InlayHint unresolved,
     VariableTypeInlayHintData data
   ) {
-    var typeRef = typeService.resolve(data.getTypeName(), documentContext.getFileType())
-      .orElse(new TypeRef(TypeRef.UNKNOWN.kind(), data.getTypeName()));
+    var typeName = data.getTypeName();
+    if (typeName == null || typeName.isBlank()) {
+      return unresolved;
+    }
+    var typeRef = typeService.resolve(typeName, documentContext.getFileType())
+      .orElse(new TypeRef(TypeRef.UNKNOWN.kind(), typeName));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplier.java`
around lines 145 - 147, Before calling typeService.resolve() in the
VariableTypeInlayHintSupplier class, add a guard check to validate that
data.getTypeName() is not null and not blank. If the type name is null or blank,
return an unresolved TypeRef (such as new TypeRef(TypeRef.UNKNOWN.kind(),
data.getTypeName())) directly as a no-op without calling resolve(), since
malformed or missing type names should not be processed further and should
prevent invalid tooltip state from occurring.
src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/InlayHintProvider.java (1)

116-129: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

extractData can crash inlayHint/resolve on non-JSON data payloads.

jsonMapper.readValue(rawData.toString(), InlayHintData.class) assumes toString() is valid JSON. For map/object round-trips this is often false, and with @SneakyThrows the exception bubbles up through resolve path instead of returning “unresolved as-is”.

Suggested fix
-  `@SneakyThrows`
   public `@Nullable` InlayHintData extractData(InlayHint inlayHint) {
     var rawData = inlayHint.getData();

     if (rawData == null) {
       return null;
     }

     if (rawData instanceof InlayHintData data) {
       return data;
     }

-    return jsonMapper.readValue(rawData.toString(), InlayHintData.class);
+    try {
+      return jsonMapper.convertValue(rawData, InlayHintData.class);
+    } catch (RuntimeException ex) {
+      return null;
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/InlayHintProvider.java`
around lines 116 - 129, The extractData method assumes that rawData.toString()
always produces valid JSON when passed to jsonMapper.readValue(), but non-JSON
payloads will cause parsing to fail. With `@SneakyThrows`, this exception
propagates up and crashes the inlayHint/resolve operation. Wrap the
jsonMapper.readValue(rawData.toString(), InlayHintData.class) call in a
try-catch block to handle JsonProcessingException or similar parsing failures,
returning null when the conversion fails instead of allowing the exception to
bubble up. This allows the resolve operation to gracefully handle unresolvable
data payloads.
🧹 Nitpick comments (1)
src/main/java/com/github/_1c_syntax/bsl/languageserver/databind/JsonMapperConfiguration.java (1)

56-65: ⚡ Quick win

Replace side-effect collect(...) calls with direct accumulation.

These collect(Collectors.toCollection(() -> namedTypes)) calls are used only for side effects and trigger PMD UselessPureMethodCall. Using forEach(namedTypes::add) is clearer and avoids the warning.

Suggested refactor
-    codeLensResolvers.stream()
-      .map(JsonMapperConfiguration::toNamedType)
-      .collect(Collectors.toCollection(() -> namedTypes));
-    commandSuppliers.stream()
-      .map(JsonMapperConfiguration::toNamedType)
-      .collect(Collectors.toCollection(() -> namedTypes));
-    inlayHintSuppliers.stream()
-      .filter(inlayHintSupplier -> inlayHintSupplier.getInlayHintDataClass() != null)
-      .map(JsonMapperConfiguration::toNamedType)
-      .collect(Collectors.toCollection(() -> namedTypes));
+    codeLensResolvers.stream()
+      .map(JsonMapperConfiguration::toNamedType)
+      .forEach(namedTypes::add);
+    commandSuppliers.stream()
+      .map(JsonMapperConfiguration::toNamedType)
+      .forEach(namedTypes::add);
+    inlayHintSuppliers.stream()
+      .filter(inlayHintSupplier -> inlayHintSupplier.getInlayHintDataClass() != null)
+      .map(JsonMapperConfiguration::toNamedType)
+      .forEach(namedTypes::add);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/databind/JsonMapperConfiguration.java`
around lines 56 - 65, The code uses collect(Collectors.toCollection(() ->
namedTypes)) calls purely for side effects to add items to the namedTypes
collection, which triggers a PMD UselessPureMethodCall warning. Replace all
three of these collect calls (one for codeLensResolvers stream, one for
commandSuppliers stream, and one for inlayHintSuppliers stream) with
forEach(namedTypes::add) instead, which is clearer in intent and explicitly
designed for side effects. This change applies to all three stream chains in the
diff.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplier.java`:
- Around line 145-147: Before calling typeService.resolve() in the
VariableTypeInlayHintSupplier class, add a guard check to validate that
data.getTypeName() is not null and not blank. If the type name is null or blank,
return an unresolved TypeRef (such as new TypeRef(TypeRef.UNKNOWN.kind(),
data.getTypeName())) directly as a no-op without calling resolve(), since
malformed or missing type names should not be processed further and should
prevent invalid tooltip state from occurring.

In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/InlayHintProvider.java`:
- Around line 116-129: The extractData method assumes that rawData.toString()
always produces valid JSON when passed to jsonMapper.readValue(), but non-JSON
payloads will cause parsing to fail. With `@SneakyThrows`, this exception
propagates up and crashes the inlayHint/resolve operation. Wrap the
jsonMapper.readValue(rawData.toString(), InlayHintData.class) call in a
try-catch block to handle JsonProcessingException or similar parsing failures,
returning null when the conversion fails instead of allowing the exception to
bubble up. This allows the resolve operation to gracefully handle unresolvable
data payloads.

---

Nitpick comments:
In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/databind/JsonMapperConfiguration.java`:
- Around line 56-65: The code uses collect(Collectors.toCollection(() ->
namedTypes)) calls purely for side effects to add items to the namedTypes
collection, which triggers a PMD UselessPureMethodCall warning. Replace all
three of these collect calls (one for codeLensResolvers stream, one for
commandSuppliers stream, and one for inlayHintSuppliers stream) with
forEach(namedTypes::add) instead, which is clearer in intent and explicitly
designed for side effects. This change applies to all three stream chains in the
diff.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0c7cfe86-a6ee-402b-8ad9-baa2d463bf96

📥 Commits

Reviewing files that changed from the base of the PR and between 65eb4c2 and a325695.

⛔ Files ignored due to path filters (1)
  • src/test/resources/inlayhints/VariableTypeInlayHintSupplier.bsl is excluded by !src/test/resources/**
📒 Files selected for processing (14)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/databind/JsonMapperConfiguration.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/AbstractComplexityInlayHintSupplier.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/AbstractMethodCallInlayHintSupplier.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/DefaultInlayHintData.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/InlayHintData.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/InlayHintSupplier.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintData.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplier.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/infrastructure/InlayHintsConfiguration.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/InlayHintProvider.java
  • src/test/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintResolveTest.java
  • src/test/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplierTest.java
  • src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/InlayHintProviderTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java
  • src/test/java/com/github/_1c_syntax/bsl/languageserver/inlayhints/VariableTypeInlayHintSupplierTest.java

@sonarqubecloud

Copy link
Copy Markdown

@nixel2007
nixel2007 merged commit 85295f5 into develop Jun 14, 2026
48 checks passed
@nixel2007
nixel2007 deleted the claude/inlayhint-type-hints branch June 14, 2026 07:13
nixel2007 added a commit that referenced this pull request Jun 14, 2026
…енивого резолва

Объявление параметра/типа уже разрешено на этапе построения хинта, поэтому
ссылка части метки (InlayHintLabelPart.location) проставляется жадно, без
отложенного inlayHint/resolve.

- SourceDefinedMethodCallInlayHintSupplier: убран ленивый путь location и
  гейтинг по inlayHint.resolveSupport(label.location); хинт вызова метода не
  несёт тяжёлых отложенных полей, поэтому переведён на DefaultInlayHintData и
  больше не кладёт data/не переопределяет resolve. Удалён неиспользуемый DTO
  MethodCallInlayHintData.
- VariableTypeInlayHintData: оставлены только поля для ленивого tooltip
  (uri, id, typeName); убраны targetUri, координаты и hasLocation/NO_LOCATION.
- VariableTypeInlayHintSupplier: location проставляется жадно, tooltip
  по-прежнему дорассчитывается лениво через inlayHint/resolve (как в #4100).
- Тесты: убраны проверки ленивого round-trip'а location, оставлены жадные
  проверки location и ленивая проверка tooltip.

Co-Authored-By: Claude Fable 5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant