Skip to content

Improved schema completions and navigation#360

Open
holodorum wants to merge 15 commits intokson-org:mainfrom
holodorum:feat/conditional-schema-completions
Open

Improved schema completions and navigation#360
holodorum wants to merge 15 commits intokson-org:mainfrom
holodorum:feat/conditional-schema-completions

Conversation

@holodorum
Copy link
Copy Markdown
Collaborator

  • Navigate if/then/else conditional schemas — schema navigation (completions, hover,
    go-to-definition) now traverses if/then/else constructs, evaluating conditions against
    the document to include only the matching branch
  • Navigate oneOf/anyOf with sibling awareness — when a schema couples properties via
    oneOf (e.g., integration determines valid integrationJob values), setting one
    property narrows completions for the other by filtering branches whose sibling constraints
    conflict with the document
  • Intersection semantics for completion narrowing — when multiple reductive schemas
    (allOf, if/then) constrain a value, only completions present in ALL schemas survive,
    replacing the previous const-only narrowing
  • Partial document resilience — completions work in broken documents by using
    error-tolerant partial AST values for schema navigation, so sibling property values are
    visible for narrowing even when the cursor position has no value yet

@holodorum holodorum changed the title Feat/conditional schema completions Improved schema completions and navigation Mar 30, 2026
@holodorum holodorum force-pushed the feat/conditional-schema-completions branch 3 times, most recently from a16ecfd to e752cdd Compare April 13, 2026 21:01
Schema navigation (go-to-definition, completions, hover) previously
dead-ended at if/then/else constructs because navigateObjectProperty
and navigateArrayItems only knew about direct properties, pattern
properties, and combinators (allOf/anyOf/oneOf).  Schemas where allOf
contains if/then blocks that conditionally map property values to
parameter $refs were invisible to the tooling.

Add navigateThroughConditionals (paralleling navigateThroughCombinators)
which traverses into then and else sub-schemas.  Wire it into both
navigateObjectProperty and navigateArrayItems, and expand conditionals
in expandCombinators so completions also discover conditional branches.

Introduce IF_THEN and IF_ELSE SchemaResolutionType variants so
downstream code (e.g. SchemaFilteringService) can distinguish how a
schema was reached.  These new types fall into the unfiltered path,
matching allOf/direct-property behavior — condition evaluation against
the document is left as a future enhancement.
…ument

Two-layer filtering for conditional schemas:

1. During navigation, evaluate the "if" condition against the document
   value at the current schema level.  This is critical when the condition
   checks a sibling property that is not visible from the target property.
   When a document value is available, only the matching branch (then or
   else) is included.

2. During post-navigation filtering, validate IF_THEN/IF_ELSE schemas
   against the document the same way ANY_OF/ONE_OF are already filtered.
   This catches cases where the resolved schema itself is incompatible.

Consolidate scattered ANY_OF/ONE_OF checks into a shared
FILTERABLE_RESOLUTION_TYPES set and rename isCombinatorBranch to
isFilterableBranch.
Several fixture schemas (DogParams/CatParams, long orchestra examples)
were exploratory — useful while building if/then navigation, noisy now
that the assertions are stable. Shrink them to minimal repros so the
intent of each test is obvious at a glance. Also pass the parsed
document through in SchemaFilteringServiceTest's navigate helper so it
exercises if/then condition evaluation.
extractValueCompletions handled enum, boolean, and null but not const.
A schema like { const: "SNOWFLAKE" } produced zero completions. This
matters for if/then schemas that narrow a property to a single const
value based on a sibling — the navigation correctly resolves the const
schema but completions were silently empty.
The same helper was duplicated across SchemaCompletionLocationTest and
follow-up completion tests. Move it to a shared interface so new tests
can mix it in instead of recopying.
…tics

The previous narrowing logic only kicked in when a reductive schema had a
const value. This missed the common case where an if/then branch narrows
a base property's enum to a subset.

Replace the const-specific check with general intersection: when multiple
reductive schemas (allOf, if/then, direct properties) provide value
completions, only values present in ALL schemas survive. Falls back to
union when the intersection is empty, which handles the case where no
document value was available to evaluate if/then conditions.
When the cursor is at an empty value position (e.g. `key:`), KSON can't
fully parse the document, so `ksonValue` is null. Without a document
value, if/then conditions can't evaluate sibling properties and all
branches are included — defeating the narrowing.

Add `toPartialKsonValue()` which walks the AST and silently skips
error nodes, preserving successfully-parsed siblings. Use this for
schema navigation during completions so that sibling values are
visible for if/then evaluation even when the cursor position has no
value.

The partial value is only used for navigation (if/then evaluation).
Validation filtering and property dedup continue to use `ksonValue` to
avoid side effects from partial document state.
The `schemaIdLookup` and `parsedDocument` fields on ResolvedSchemaContext
were unused by callers once the partial-AST navigation was pulled inline
into getCompletions. Drop the data class wrapper and make
resolveAndFilterSchemas a plain function returning List<ResolvedRef>.
…tion

Completions had its own inline schema resolution path to use
partialKsonValue for if/then navigation while hover and go-to-definition
used the shared resolveAndFilterSchemas with ksonValue (which is null
when the document has parse errors).

Change resolveAndFilterSchemas to take ToolingDocument directly and use
partialKsonValue for navigation, ksonValue for validation filtering.
All three features now share the same path, giving hover and
go-to-definition the same broken-document resilience completions had.
When a schema uses allOf+oneOf to couple interdependent properties (e.g.,
integration determines which job values are valid), setting one property
now narrows completions for the other.

The mechanism: navigateThroughCombinators records which oneOf/anyOf branch
each result came from (via the new parentBranch field on ResolvedRef).
SchemaFilteringService uses this context in a dedicated first pass to
check sibling property const/enum constraints against the document.  A
second pass does the existing leaf-level validation.

The two passes use different document values: sibling filtering uses
partialKsonValue (works even with parse errors at the cursor), while
leaf validation uses the fully parsed ksonValue.  Each pass has its own
fallback so that completions degrade gracefully.
navigateByDocumentPointer always started with baseUri="" but schemas
with $id store their root under that id in the lookup map. $ref
resolution then failed because idMap[""] didn't exist. Use the root
schema's $id as the starting base URI so refs resolve correctly.
The two classifications — which types contribute reductive (intersected)
value completions, and which types need validation-based filtering — lived
as setOf() constants in two unrelated files. A reader had to cross-reference
to understand what an enum value meant in the system.

Lift both into properties on SchemaResolutionType itself (isReductive,
requiresValidationFiltering). Call sites become self-explanatory; the
taxonomy is discoverable from the enum.
The function was expanded to handle if/then/else but the name still said
combinators only. Rename to reflect what it actually does — flatten any
branching construct (combinators + conditionals) into tagged ResolvedRefs.
Three separate comments scattered across getValidSchemas and filterByValidation
described individual "give up on filtering" cases. A reader had to piece the
overall pipeline together themselves.

Consolidate the four skip/fallback conditions into a single class-level KDoc
section. Also document the load-bearing nature of the containsKey("oneOf"/
"anyOf"/"if") check in requiresValidationFiltering — it catches preserved
parent schemas that would otherwise slip through filtering unchecked.
Navigation, expansion, and filter-time sibling checks each used to
implement their own view of "decompose a schema's branches," with
subtly different semantics.  The worst effect: blind expansion at the
target level leaked else-branch properties into completions when if
matched (regression tests added at root and nested levels).

Introduce SchemaNavigator as an inner class of SchemaIdLookup, mirroring
TreeNavigator's shape from the walker package.  It exposes one entry
point, navigate(pointer, docVal), built from two primitives:
  - stepInto: structural per-token descent, no combinator logic
  - flatten:  doc-aware decomposition of top-level branches
              (oneOf/anyOf/allOf unconditionally, if/then/else narrowed
              by strict isValid)
Navigation calls flatten at every level, including the target, so the
previous post-nav expandBranches pass disappears entirely.
navigateThroughCombinators/Conditionals are absorbed into flatten.

SchemaFilteringService drops its expandBranches call site — navigate
already returns fully-flattened refs.  Two existing SchemaNavigationTest
cases are updated to reflect the new contract (target schema is now the
head of the flattened result, followed by its expanded inner branches).
@holodorum holodorum force-pushed the feat/conditional-schema-completions branch from e752cdd to 4c98eb5 Compare April 27, 2026 03:51
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