Skip to content

feat: update DevContext and LSP to support composable schemas#2965

Merged
miparnisari merged 2 commits intomainfrom
tstirrat/file-aware-development-system
Mar 23, 2026
Merged

feat: update DevContext and LSP to support composable schemas#2965
miparnisari merged 2 commits intomainfrom
tstirrat/file-aware-development-system

Conversation

@tstirrat15
Copy link
Copy Markdown
Contributor

@tstirrat15 tstirrat15 commented Mar 10, 2026

Description

Filesystem-aware development system — The DevContext and schema compilation now accept a filesystem
(fs.FS), enabling validation of schemas that use import syntax (composable schemas).

Key changes:

  1. pkg/development/ — devcontext.go and schema.go updated to thread an fs.FS through schema
    compilation. The position mapper (schema_position_mapper.go) was reworked significantly to
    handle multi-file schemas correctly, with new tests.
  2. pkg/schemadsl/compiler/ — The importer and position mapper now work with filesystem-backed
    sources. development.go gained helpers for filesystem-aware compilation. The translator
    tracks source file information more precisely.
  3. LSP (internal/lsp/) — New overlay filesystem (overlay.go) lets the LSP validate open/unsaved files
    by layering in-memory content over the real filesystem. Handlers updated to support
    textDocument/definition (go-to-definition) and fix requestDiagnostics. Substantial new tests.

Testing

you can test this by bringing it over to VSCode and trying it out with schemas of your own. you have to

  1. On spicedb repo:  gh pr checkout 2965 && mage build:binary
  2. On vscode repo : gh pr checkout 51
  3. edit line 95 on package.json to point to the binary you created in step 1
  4. hit F5 to run the extension

if you want to test in zed:

  1. On zed repo: gh pr checkout 635 && mage build:binary
  2. you can now do ./zed validate ....

@github-actions github-actions bot added the area/schema Affects the Schema Language label Mar 10, 2026
@tstirrat15 tstirrat15 force-pushed the tstirrat/file-aware-development-system branch from cffe476 to e20db9f Compare March 10, 2026 20:38
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 10, 2026

Codecov Report

❌ Patch coverage is 76.33136% with 80 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.92%. Comparing base (5d081d3) to head (e09bfdd).

Files with missing lines Patch % Lines
internal/lsp/handlers.go 75.61% 15 Missing and 5 partials ⚠️
pkg/development/devcontext.go 53.49% 17 Missing and 3 partials ⚠️
pkg/schemadsl/compiler/development.go 70.22% 9 Missing and 5 partials ⚠️
pkg/development/schema_position_mapper.go 82.44% 11 Missing and 2 partials ⚠️
internal/lsp/overlay.go 83.34% 5 Missing ⚠️
pkg/schemadsl/compiler/importer.go 78.58% 3 Missing ⚠️
pkg/schemadsl/compiler/translator.go 70.00% 2 Missing and 1 partial ⚠️
pkg/schemadsl/compiler/positionmapper.go 87.50% 2 Missing ⚠️

❌ Your project check has failed because the head coverage (74.92%) is below the target coverage (75.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2965      +/-   ##
==========================================
+ Coverage   74.79%   74.92%   +0.14%     
==========================================
  Files         499      500       +1     
  Lines       61065    61229     +164     
==========================================
+ Hits        45666    45869     +203     
+ Misses      12200    12158      -42     
- Partials     3199     3202       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@miparnisari miparnisari force-pushed the tstirrat/file-aware-development-system branch 2 times, most recently from 3998ee0 to e20db9f Compare March 13, 2026 20:57
@github-actions github-actions bot added the area/tooling Affects the dev or user toolchain (e.g. tests, ci, build tools) label Mar 13, 2026
@miparnisari miparnisari force-pushed the tstirrat/file-aware-development-system branch 5 times, most recently from 9ef3b65 to 5dcc950 Compare March 16, 2026 17:37
@miparnisari miparnisari changed the title chore: make development system filesystem-aware feat: update DevContext and LSP to support composable schemas Mar 16, 2026
@miparnisari miparnisari force-pushed the tstirrat/file-aware-development-system branch 10 times, most recently from 12bc73d to a1130f6 Compare March 17, 2026 02:39
@miparnisari miparnisari marked this pull request as ready for review March 17, 2026 02:41
@miparnisari miparnisari requested a review from a team as a code owner March 17, 2026 02:41
@miparnisari miparnisari force-pushed the tstirrat/file-aware-development-system branch 2 times, most recently from 254aa41 to 10d5e84 Compare March 17, 2026 06:22
Comment thread internal/lsp/handlers.go
Comment thread internal/lsp/handlers.go Outdated
Comment thread pkg/caveats/parameters.go
Comment thread pkg/development/devcontext.go Outdated
Context: errWithSource.SourceCodeString,
Line: lineNumber,
Column: columnPosition,
Path: []string{path},
Copy link
Copy Markdown
Contributor

@miparnisari miparnisari Mar 17, 2026

Choose a reason for hiding this comment

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

FYI zed uses this to show the correct content from the file that has the error

@miparnisari miparnisari force-pushed the tstirrat/file-aware-development-system branch 3 times, most recently from fe72591 to dae057f Compare March 17, 2026 06:45
Comment thread internal/lsp/handlers.go Outdated

// Get errors.
for _, devErr := range devErrs.GetInputErrors() {
if !slices.Contains(devErr.Path, string(uri)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If I recall correctly, I had a map somewhere from URI <-> Path; if not, we likely should add it or helper functions, to make it clear when using one vs the other

Comment thread internal/lsp/handlers.go Outdated
Comment thread internal/lsp/handlers.go
Comment thread internal/lsp/handlers.go Outdated
targetURI := params.TextDocument.URI
if resolved.TargetSource != nil && *resolved.TargetSource != "schema" {
sourceDir := uriToSourceDir(params.TextDocument.URI)
targetURI = lsp.DocumentURI("file://" + filepath.Join(sourceDir, string(*resolved.TargetSource)))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And you'd use the new map/mapping functions here

Comment thread internal/lsp/handlers.go Outdated
Comment thread internal/lsp/overlay.go
Comment thread pkg/caveats/parameters.go
Comment thread pkg/development/devcontext.go Outdated
Comment thread pkg/development/schema_position_mapper_test.go
Comment thread pkg/development/warnings.go
return nil, err
}

targetSource := r.resolveTargetSource(relation.Name, source)
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.

FYI this is what VSCode uses to know which file contains the error and display the right text content

Comment thread pkg/schemadsl/compiler/development.go Outdated
Comment on lines +58 to +60
// DefinitionNodeSource returns the input source for the AST node defining the given name.
// For definitions compiled from imports, this will be the imported file path (e.g. "users.zed").
// For definitions in the root schema, this will be the root source (e.g. "schema").
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.

If there is a better way to do this please let me know

Comment thread internal/lsp/lspdefs.go Outdated
Comment on lines +17 to +19
nodeSource, _ := toplevel.GetString(dslshape.NodePredicateSource)
errMsg := fmt.Errorf("failed to read import %q: %w", filePath, err)
return nil, "", toContextError(errMsg.Error(), nodeSource, toplevel, mapper)
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.

FYI changing to contextError so that we don't lose line number info of where the error happened

Comment thread pkg/schemadsl/generator/generator.go Outdated
Comment thread pkg/composableschemadsl/compiler/translator.go Outdated
@miparnisari miparnisari force-pushed the tstirrat/file-aware-development-system branch from dae057f to 36b1c7e Compare March 18, 2026 21:34
@miparnisari miparnisari force-pushed the tstirrat/file-aware-development-system branch 6 times, most recently from f4798a6 to 2f18c9e Compare March 20, 2026 04:36
@miparnisari miparnisari force-pushed the tstirrat/file-aware-development-system branch from 2f18c9e to 79b1c89 Compare March 20, 2026 20:11
tstirrat15 and others added 2 commits March 23, 2026 13:02
…d support for textDocument/definition, support composable schemas). fix position mapper
@miparnisari miparnisari force-pushed the tstirrat/file-aware-development-system branch from 79b1c89 to e09bfdd Compare March 23, 2026 20:05
Copy link
Copy Markdown
Contributor Author

@tstirrat15 tstirrat15 left a comment

Choose a reason for hiding this comment

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

See comments, otherwise LGTM

Comment thread internal/lsp/overlay.go
// resolveURI resolves a relative path against the directory of baseURI,
// returning a new file:// DocumentURI.
func resolveURI(baseURI lsp.DocumentURI, relativePath string) lsp.DocumentURI {
return lsp.DocumentURI("file://" + filepath.Join(uriToSourceDir(baseURI), relativePath))
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread pkg/development/schema.go
Comment on lines +29 to +33
// WithRootFileName saves the root file of the schema
// so errors can be displayed accurately on UIs.
func WithRootFileName(name string) CompileOption {
return func(cfg *compileConfig) { cfg.rootFileName = name }
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Barak and I were talking about this - would it make sense to hang the filename off of the definition such that error handling can look up the filename along with the position straight off the node?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll do this in a follow-on PR

Comment on lines +302 to +307
func (r *SchemaPositionMapper) resolveTargetSource(name string, fallback input.Source) input.Source {
if defSource := r.schema.GetPathToDefinitionOrPartialOrCaveat(name); defSource != "" {
return input.Source(defSource)
}
return fallback
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I think that would clean up this code a bit

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TODO for later

Comment on lines +510 to +513
sourceFS := fstest.MapFS{
"path/partials.zed": &fstest.MapFile{Data: []byte("use partial\npartial secret {\nrelation secret: user\n}")},
"path/users.zed": &fstest.MapFile{Data: []byte("definition user {}\ncaveat is_raining(day string) {\nday == \"saturday\"\n}")},
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oooh I like this

Comment on lines +58 to +62
// GetPathToDefinitionOrPartialOrCaveat returns the input source for the AST node defining the given name.
// For definitions compiled from imports, this will be the imported file path (e.g. "users.zed").
// For definitions in the root schema, this will be the root source (e.g. "schema").
// Returns empty string if not found.
func (cs *CompiledSchema) GetPathToDefinitionOrPartialOrCaveat(name string) string {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I think we should annotate the node rather than treating it as a calculated value. I can make that change today.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Deferring to future refactor

Comment on lines +156 to +161
// Skip nodes from imported files whose rune positions may overlap with the root file.
if nodeSource, err := node.GetString(dslshape.NodePredicateSource); err == nil {
if nodeSource != rootSource {
return nil, nil
}
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why is this part necessary?

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.

Makes this test pass: "cursor on the import path" on schema_position_mapper_test.go


// Take a filepath and ensure that it's local to the current context.
func validateFilepath(path string) error {
func validateFilepath(path string, toplevel *dslNode, mapper input.PositionMapper) error {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think we might be able to delegate this to DirFS's restrictions. I guess we'd still need to rewrite the error in that case, though.

@miparnisari miparnisari enabled auto-merge (squash) March 23, 2026 20:53
@miparnisari miparnisari merged commit b041b8f into main Mar 23, 2026
45 of 46 checks passed
@miparnisari miparnisari deleted the tstirrat/file-aware-development-system branch March 23, 2026 20:55
@github-actions github-actions bot locked and limited conversation to collaborators Mar 23, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area/schema Affects the Schema Language area/tooling Affects the dev or user toolchain (e.g. tests, ci, build tools)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants