Skip to content

Open the value in property completions - #453

Open
holodorum wants to merge 14 commits into
kson-org:mainfrom
holodorum:feat/snippets-support
Open

holodorum wants to merge 14 commits into
kson-org:mainfrom
holodorum:feat/snippets-support

Conversation

@holodorum

Copy link
Copy Markdown
Collaborator

Summary

Completing a property key now also inserts the opening of its value when the schema fixes the type, so the caret lands inside {}, [] or ''. It also works for schemas built from a base type plus oneOf/anyOf variants, where almost every property sits behind an alternative.

Example

With settings: { type: object } in the schema, typing sett and accepting the completion gives:

settings: {|}

Before, it inserted just settings.

Changes

  • Schema navigation records which oneOf/anyOf/if branches it took to reach each schema. Hover, go-to-definition and the completion list are unchanged.
  • Snippet navigation keeps every branch in play and handles tuples, numeric property names and unresolved $refs conservatively.
  • Completions combine the branch types and add a snippet only when exactly one of object, array or string remains.
  • Language server sends the snippet as a textEdit to clients that support snippets.
  • Monaco bridge applies completion textEdits.

Limitations

  • The document doesn't pick a branch. A property whose type differs per variant stays plain, even after a discriminator like kind is typed.
  • There's no snippet for numbers, booleans or null, or where the type isn't certain.

Testing

  • SchemaBranchTrailTest covers the branch recording and snippet navigation.
  • PropertySnippetTest checks every snippet against the schema: a value of the opened kind validates, and values of other kinds don't.

Depends on

#443, #445, #449 and #450

A JsonPointer is bare tokens, but the AST and the value tree number a
list differently past an element in error.  Pointers built on the AST
have twice been resolved through a value tree; e99887d fixed those
sites by convention only.

TreePointer<N> carries the walker's node type, so resolving an AST
pointer on a value tree is now a compile error.  A type parameter
rather than two pointer classes lets the one generic navigator tag
what it builds and check what it resolves.  A pointer written against
no tree, such as a $ref fragment, is tagged explicitly where it is
resolved.

The past-committed-value check and the filled-property filter were
safe only because the strict ksonValue is null for a broken document.
They now read the AST, so a broken document behaves as a valid one;
two completion tests that encoded the old gap now expect this.
A key or empty line after a closing `}`, `]` or `>`, an end-dot or an
end-dash completed inside the value it closed rather than where the
parser puts the key.  A span includes its end, so the closer's start
found the container's last value instead of the container.

A caret past a closer now resolves from the closer's end, which finds
the container itself.  Hover and definition in whitespace after a
closer likewise resolve to the closed value, not its last child.
An `if` checks its condition in the caller's mode, and partial
validation skips `required`, `minProperties` and the like. So a condition
failing under full validation, which takes the `else`, could hold under
partial validation, which enforced the `then` instead and so rejected
values full validation accepts.

Under partial validation, an `if` whose condition fails under full
validation now also passes when its `else` holds or is missing. Where
the condition holds under full validation too, the `then` still applies
as it does there, even where the `else` would accept the value.

Schema navigation narrows with partial validation and assumes it
accepts whatever full validation does.
Flattening resolved the $refs in a schema's allOf, anyOf, oneOf and
if/then/else under the base URI the schema was reached under, skipping
its own $id, which only stepping into properties and items applied.
With p a $ref to a schema whose $id gives its anyOf's #/$defs/x its
own object, navigation took the root's x, a string, instead.

Flattening now applies the $id first, as stepping does. The fix goes
there rather than in the id lookup because the $id went missing
however the schema was reached: by its $id, by a JSON pointer, or as
an anyOf branch no $ref leads to. The lookup leaves a node's own $id
to whatever reads the node, as the validator's parser does, and
ResolvedRef's KDoc now says so.

Hover, go-to-definition and the completion list change with it: they
now use the schemas such $refs resolve to, so completing inside p
offers the properties of the target's own x, not the root's.
Resolving a $ref followed only one hop. Where that led to another
$ref, navigation stopped at the schema holding it and read the other
keywords beside it, which validation ignores: with p a $ref to a, and
a holding a $ref to b beside a string type, navigating to p reached
a's string rather than b, the schema validation applies.

The id lookup now follows the chain as the validator does: each $ref
resolves under the base URI its schema is read under, ignoring that
schema's own $id. A $ref that doesn't resolve, or leads back into the
chain, ends it at the schema holding it. Only schema navigation calls
it, so hover, go-to-definition and the completion list change with it:
they now use the schema at the end of the chain.
An `allOf` member inside a `oneOf`, `anyOf` or `if`/`then`/`else` was marked
`ALL_OF`, so value completions intersected it with the other branches
as if both had to hold. For `p: {anyOf: [{allOf: [{enum: [a, b]}]},
{enum: [c]}]}` nothing was offered instead of `a`, `b` and `c`.

Such a member now keeps its alternative's mark. The cost is that enums
within one alternative are unioned rather than intersected, so a few
cases offer a value that doesn't strictly fit. Some of those were
previously correct: `allOf` members under a matched `if` used to be
intersected. Hiding values another branch allows is the worse failure,
and an undecided `if` needs the mark so its `then` members aren't
intersected against `else`.
Property snippets build on kson-org#443's tree pointers and kson-org#450's alternative
marks, which this branch has already, and on kson-org#445's caret path after a
closer, which their tests of keys typed after a closed value rely on.
Snippets open the type navigation reaches, so they rely on kson-org#449
resolving $refs as validation does. It is a dependency PR: this merge
disappears once it lands.
Snippets assume a schema failing partial validation fails full
validation too, which kson-org#448 makes an if keep to. It is a dependency PR:
this merge disappears once it lands.
Each navigated schema now carries a branch trail: the oneOf, anyOf and
if/then/else branches taken to reach it. Unlike the flat resolution
mark, it tells two branches of one alternative from two alternatives
within one branch. Property snippets will use it; navigation results
are otherwise unchanged.
Snippets need the types a new property's value may take. Adding the
property may sway any branch or if, so a toNewProperty mode, used by
the next commit, leaves them all in play rather than narrowing by the
document. It also keeps a branch forbidding the path as false, steps
an item to its own schema, and gives no schema under numeric names or
unresolved $refs.
A property completion now opens its value, key: {$0}, key: [$0] or
key: '$0', where the schema pins it to an object, array or string.
Types merge across every oneOf, anyOf and if/then/else branch, since
schemas built from a base type and variants put most properties behind
one. The document rules none out, so a type differing per variant
stays plain even once a discriminator is typed; a snippet whose shape
could be wrong is never offered.
The bridge ignored a completion's textEdit and always inserted the
item's text over the word at the caret. It now inserts a TextEdit's
newText over its range, as LSP specifies, so the property snippets the
language server is about to send land in the right place.
The language server now passes the tooling's property snippets on as
the completion's textEdit, so accepting one leaves the caret inside the
opened value. Only clients declaring snippetSupport get them, since
others would insert $0 literally.
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