Skip to content

config: generate & check schema for configuration automatically#413

Merged
aviatesk merged 25 commits intomasterfrom
abap34/schema-ci
Feb 25, 2026
Merged

config: generate & check schema for configuration automatically#413
aviatesk merged 25 commits intomasterfrom
abap34/schema-ci

Conversation

@abap34
Copy link
Collaborator

@abap34 abap34 commented Dec 25, 2025

Summary

As the configuration has grown more complex, it has become increasingly difficult for users to write correct configuration files without tool support. This PR adds JSON Schema generation for the configuration to support user.

The schema is automatically generated at release time and published as a release asset, enabling:

  • Autocompletion in editors that support schema-based TOML editing
  • Type validation for configuration values
jetls-schema-demo.mov

Implementation details

For this feature, I developed Struct2JSONSchema.jl, which generates JSON Schemas without requiring any modifications to existing struct definitions. This approach is well-suited for projects like this one, where schema generation is optional and should remain decoupled from the core codebase.

A new script scripts/schema/generate.jl uses Struct2JSONSchema.jl to generate a JSON Schema from the JETLSConfig struct definition.

Users can reference the schema in their editor configuration:

https://github.com/aviatesk/JETLS.jl/releases/latest/download/jetls-config.schema.json

Notes

  • Struct2JSONSchema.jl is not yet registered in the Julia package registry, so the release workflow currently references it directly from GitHub. Once it is registered, this will be switched to using the registry version. It would be also reasonable choice to wait for that registration before merging this PR. (This is why this PR is draft) registered.
  • After this feature has been merged and stabilized, I plan to submit the schema to SchemaStore. Once registered, most editors will be able to provide configuration support out of the box, without requiring users to manually configure a schema URL.

Try it now

For testing purposes, a temporary schema is currently hosted at https://www.abap34.com/jetls-config.schema.json. You can try it by either:

  • Configuring your editor's schema extension to use this URL, or
  • Installing the taplo language server and adding the following line at the top of your .JETLSConfig.toml:
    #:schema https://www.abap34.com/jetls-config.schema.json

Tasks

  • update CHANGELOG
  • add description
  • add default value

@abap34 abap34 marked this pull request as ready for review December 25, 2025 11:34
@abap34 abap34 requested a review from aviatesk December 25, 2025 11:36
@abap34
Copy link
Collaborator Author

abap34 commented Dec 27, 2025

Update:
Struct2JSONSchema.jl now supports adding descriptions to individual fields, which enables documentation to be shown on hover in configuration files.

image

Based on the discussion with @aviatesk, the ideal system we want to achieve is as follows:

  1. Generate a schema from JETLSConfig (i.e. a plain Julia struct)
  2. Distribute it as a JSON Schema
  3. Also distribute it via package.json

Steps 1 and 2 have already been achieved.

To achieve step 3, there are still some remaining issues.

First, the configuration section of the package.json specification differs from standard JSON Schema.
For example, $ref and definitions cannot be used.
See:
https://code.visualstudio.com/api/references/contribution-points#c:~:text=Unsupported%20JSON%20Schema%20properties

However, this is easy problem, since it can be worked around by inlining references.

The biggest remaining task is support for default values.
Struct2JSONSchema.jl does not currently support default values.
More precisely, this can already be achieved via an override mechanism, but it is not exposed as a simple interface.

I plan to implement a straightforward API for registering default values, such as:
register_default_value!(ctx, JETLSConfig, DEFAULT_CONFIG)

In some cases, such as the formatter section, the hierarchy of the Julia structs differs from that of the resulting schema. Handling this requires additional user customization, but it should be feasible.

After that is done, I think it will be ready to merge once we finalize a small interface for how and where to define description and other data, and update the devdocs accordingly.

@abap34 abap34 marked this pull request as draft December 27, 2025 15:55
Copy link
Owner

@aviatesk aviatesk left a comment

Choose a reason for hiding this comment

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

This is a great change. Really appreciate you working on this.
Your approach for default values sounds good too.

Just one thing to keep in mind: I will merge #415, so we'll need to update things like diagnostic descriptions. (Apologies for making schema changes while this PR was in progress.)

Anyway, just let me know when you think it's good to go, and I think we can merge it directly.

@abap34
Copy link
Collaborator Author

abap34 commented Dec 30, 2025

I think it will take a few days to implement features related to default values, so please feel free to update the configuration structures as you like :)

Additional note: It would be nice if we could generate descriptions from docstrings, but unfortunately Configurations.jl does not seem to retain docstrings for individual fields. As we discussed before, this may require creating a separate package tailored for this purpose.

@aviatesk
Copy link
Owner

Yeah, we need some macro hacks to preserve field docs information (just realized this when I were writing LSP.@interface), so it's not surprising if Configurations.jl doesn't implement it.

Anyway, the current description.toml approach should be fine. Once the default value work is done, I understand that when we add a new configuration, we'll need to edit these additional places:

  • description.toml
  • docs/src/configuration.md

And I understand that jetls-client/package.json (or part of it) will be generated from Struct2JSONSchema.

@abap34
Copy link
Collaborator Author

abap34 commented Dec 30, 2025

I understand that when we add a new configuration, we'll need to edit these additional places...

Yes, that’s correct.
Would it be sufficient to document this workflow in DEVELOPMENT.md?

@aviatesk
Copy link
Owner

Yes, this kind of information for developers should go to DEVELOPMENT.md.

@abap34
Copy link
Collaborator Author

abap34 commented Jan 25, 2026

UPDATE:

Struct2JSONSchema.jl now supports features such as setting default values easily.
There are no remaining missing core features, and the merge should be soon I think.

Remaining tasks:

  • Write developer documentation.
  • Update the CI to also generate package.json.
  • Update the implementation to align with the current configuration options.

@aviatesk
Copy link
Owner

Great progress. Let me know if you need a new review round.

@abap34 abap34 marked this pull request as ready for review February 2, 2026 07:21
@abap34
Copy link
Collaborator Author

abap34 commented Feb 2, 2026

Support for updating jetls-client/package.json is now working.

As shown below, package.json is updated automatically when JETLSConfig in types.jl is updated.
(The script execution part of the video is x10 sped up)

schema_pr_demo.mov

There is still one point that needs discussion.

  • When and by whom should this process be run?
    • Currently, the schema file (jetls-config.schema.json) is generated and distributed by CI at release time and is not kept in the repository.
    • Updating package.json at release time in the same way seems natural.
    • However, if we want master to always remain in a runnable state, it may be necessary for CI to update it on each change.
      Since configuration changes are unlikely to be frequent, this is not expected to significantly clutter the Git history.

Once this point is decided, I will write the developer documentation, review description and some implementations, and then it should be ready to merge.

@aviatesk
Copy link
Owner

aviatesk commented Feb 2, 2026

Automatically updating package.json with bots or actions is one idea, but I'm concerned that it might make the operational policy a bit complicated.

Since config updates don't happen very frequently, it might be fine to force devs to update package.json whenever we make changes to configuration using the update script in ./scripts? And, on the jetls-client side, we would set up CI that verfies whether the currently checked-in package.json is consistent with the current JETLS configuration schema.
Regarding releases, I agree with you that preparing a script for release time distribution is a good idea.

@abap34 abap34 force-pushed the abap34/schema-ci branch 2 times, most recently from 7ccd404 to b1dc704 Compare February 3, 2026 09:51
@abap34
Copy link
Collaborator Author

abap34 commented Feb 3, 2026

Taking your feedback into account, I changed the setup as follows.

  • On release: generate the JSON schema and distribute it as an asset.
  • On push to master: check whether there are any diffs.

I also rewrote description.toml more carefully and pushed the regenerated package.json.
While a large part of the diff comes from semantically equivalent changes such as field ordering, there were also semantic differences, including the following:

1. Description updates

For example, the description of debounce was Debounce time in seconds before triggering full analysis after a document change, but I believe this should actually refer to saving. I tried to apply this kind of correction based on the docs as accurately as possible. However, there may still be mistakes in my understanding (and Claude’s, who helped me 😀), so I would appreciate it if you could review this part carefully and feel free to edit it. 

2. Added fields

Fields such as inlay_hint and latex_emoji were missing in package.json, so they have been added.

@abap34 abap34 requested a review from aviatesk February 3, 2026 10:01
@abap34 abap34 changed the title config: generate schema for configuration at release config: generate & check schema for configuration automatically Feb 3, 2026
@aviatesk
Copy link
Owner

aviatesk commented Feb 4, 2026

Fields such as inlay_hint and latex_emoji were missing in package.json, so they have been added.

Thanks 👍

else
settings["properties"] = expected_props
open(package_json_path, "w") do io
write(io, JSON.json(package_json, 2))
Copy link
Owner

Choose a reason for hiding this comment

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

Should we use OrderedDict or similar technique to ensure stable output? Or does JSON.jl already handle that functionality? I kinda want the output to be deterministic and ideally sorted alphabetically in terms of JSON's key order.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As you pointed out, it seems necessary to make it deterministic.
Since computation time and implementation are not an issue, explicitly sorting the results before generating the output should be sufficient. I’ll add that.

Copy link
Owner

Choose a reason for hiding this comment

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

That's good.

Comment on lines +1 to +12
include("lib.jl")

gen_ctx = SchemaContext()
setup_ctx!(gen_ctx)

schema = generate_schema(JETLS.JETLSConfig; ctx = gen_ctx)

schemafile_path = joinpath(@__DIR__, "..", "..", "jetls-config.schema.json")
open(schemafile_path, "w") do io
write(io, JSON.json(schema.doc, 2))
end

Copy link
Owner

Choose a reason for hiding this comment

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

I'm thinking of adding a --check option to this script as well.
The reason is, I want to add a feature like jetls schema to the main JETLS.jl app, so that JETLS can output the schema itself. For this, I'm thinking we could check in the JSON schema directly into the JETLS.jl git checkout, similar to package.json for jetls-client. The idea is that JETLS would then include_dependency the schema file and use it for the jetls schema app output.
Some editors can get JSON schemas directly from language server extensions and use them for autocompletion (zed-industries/zed#48334). While editors might be able to pull schemas from a remote source, I think outputting it locally would be the best solution for consistency.

Moving back to the original point, for CI, similar to package.json, we could check if the currently checked-in schema is consistent with the JETLS state.

@aviatesk
Copy link
Owner

aviatesk commented Feb 4, 2026

Great job. Can we generate a similar schema for InitOptions too? We can work on that in a separate PR if you want though.

completion = "Completion configuration. See [Completion configuration](https://aviatesk.github.io/JETLS.jl/release/configuration/#config/completion)."
code_lens = "Code lens configuration. See [Code lens configuration](https://aviatesk.github.io/JETLS.jl/release/configuration/#config/code_lens)."
inlay_hint = "Inlay hint configuration. See [Inlay hint configuration](https://aviatesk.github.io/JETLS.jl/release/configuration/#config/inlay_hint)."
initialization_options = "Static initialization options that are set once at server startup and require a server restart to take effect. See [Initialization options](https://aviatesk.github.io/JETLS.jl/release/launching/#init-options)."
Copy link
Owner

Choose a reason for hiding this comment

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

I don't think the schema for JETLSConfig (, which basically corresponds to what handled by workspace/configuration) should not contain anything about initialization_options, since they are just different ways to configure the server.

Copy link
Owner

Choose a reason for hiding this comment

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

On second thought, maybe description.toml itself should be as is, as the single source for all descriptions of dynamic settings (workspace/configuration) and initialization options. Rather, JETLS should output three types of schemas, and CI should probably validate each of them:

  • Schema for dynamic settings
  • Schema for initialization options
  • Schema for .JETLSConfig.toml (schema of dynamic settings + initialization options)

abap34 and others added 23 commits February 25, 2026 17:16
Co-Authored-By: Claude <claude@users.noreply.github.com>
Co-Authored-By: Claude <claude@users.noreply.github.com>
Co-Authored-By: Claude <claude@users.noreply.github.com>
Co-Authored-By: Claude <claude@users.noreply.github.com>
Co-Authored-By: Claude <claude@users.noreply.github.com>
Add `jetls schema` CLI subcommand that prints JSON schema
files from `schemas/` directory to stdout. Supports
`--settings`, `--init-options`, and `--config-toml` flags.

Add process-based tests in `test/app/test_jetls_schema.jl`
that spawn `julia -m JETLS schema` subprocesses, matching
the existing pattern for `test_jetls_check.jl` and
`test_jetls_serve.jl`.

Add a reusable `check-schemas.yml` workflow that verifies
schema files are up to date by running `generate.jl --check`.
This is called from both `JETLS.jl.yml` and `release.yml`.

Also update `JETLS.jl.yml` path triggers to include
`schemas/**` and `scripts/schema/**`, update
`prepare-release.sh` checklist.
- Add `jetls schema` command entry to `launching.md` and
  link to the new JSON Schema section in `configuration.md`
- Add `analysis_overrides` initialization option reference
  to `launching.md`
- Add `## JSON Schema` section to `configuration.md` listing
  the three schema files (`config-toml.schema.json`,
  `settings.schema.json`, `init-options.schema.json`) with
  download links, and a `jetls schema` CLI usage example
- Update CHANGELOG `## Unreleased` with Added entries for
  `jetls schema` CLI and the schema generation infrastructure
- Upload schema files as GitHub Release assets in `release.yml`
@aviatesk
Copy link
Owner

Working beautifully. Thanks so much for your work on this, @abap34 !
Screenshot 2026-02-25 at 20 10 53

@aviatesk aviatesk merged commit 4d3351a into master Feb 25, 2026
16 of 17 checks passed
@aviatesk aviatesk deleted the abap34/schema-ci branch February 25, 2026 11:12
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.

2 participants