Skip to content

TML-2946: Add PSL type and generic block completions#871

Open
SevInf wants to merge 52 commits into
mainfrom
lsp-autocomplete
Open

TML-2946: Add PSL type and generic block completions#871
SevInf wants to merge 52 commits into
mainfrom
lsp-autocomplete

Conversation

@SevInf

@SevInf SevInf commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Linked issue

Refs TML-2946

At a glance

model User {
  id Int @id
}

type Address {
  street String
}

model Post {
  id Int @id
  author |
}

policy UserAccess {
  wh|
}

At the author | model field type position, textDocument/completion returns configured scalar types plus visible model/composite/type-alias candidates such as Boolean, DateTime, Int, String, Post, User, and Address, plus namespace qualifiers such as auth.. After the user types or accepts a namespace qualifier, positions like auth.| complete model/composite members inside that namespace. At descriptor-backed generic block parameter-key positions like wh|, it returns declared block parameters such as where and filters out sibling keys that already exist in the block.

Decision

This PR ships the first PSL autocomplete surfaces in @prisma-next/language-server: configured PSL model field type completions and descriptor-backed generic block parameter-key completions. It adds a typed cursor classifier over cached parser artifacts, completion providers backed by configured scalars, the current project symbol table, and the project control-stack PSL block descriptors, an LSP completion route, and README/guardrail coverage for the intentionally narrow scope.

Summary

The language server now answers completion requests for open configured PSL documents at model field type positions and descriptor-backed generic block parameter-key positions. Unsupported contexts stay empty, including ordinary PSL @ / @@ attributes, attribute arguments, generic block parameter values, unconfigured documents, relation-aware scenarios, and external contract-space discovery gaps.

Reviewer notes

  • completion-context.ts owns the cursor-context boundary: token lookup, ancestor rejection, blank model type positions, partial qualified names, generic block body/key detection, and explicit unsupported reasons.
  • Contract-space-qualified syntax is supported as syntax/filtering only. The provider uses visible namespace candidates from the cached symbol table; it does not add a new external contract-space symbol index.
  • Generic block completions are intentionally limited to descriptor-declared parameter keys. Parameter value completions for ref, option, value, or list descriptors remain out of scope.
  • projects/lsp-autocomplete/** is included for Drive traceability: it contains the project/slice specs, dispatch briefs, and trace for this project. Close-out removes/migrates project artifacts after the overall project is done.

How it fits together

  1. The classifier maps an LSP position over cached DocumentAst / SourceFile data into either a model-field-type context, a generic-block-parameter context, or an explicit unsupported reason. This keeps completion on the existing recovered CST/AST path instead of reparsing with a marker.
  2. The provider consumes that typed context and current candidates. For model field types, it returns stable LSP CompletionItems for configured scalars, top-level model/composite/scalar/type-alias symbols, bare namespace qualifier candidates such as auth., and namespace-local model/composite members only after a qualifier is present. For generic blocks, it resolves the descriptor by block keyword and returns declared parameter keys that match the typed prefix.
  3. The server advertises completionProvider and routes textDocument/completion through the same configured-input and cached-artifact ownership checks used by diagnostics/formatting, passing both the project symbol table and pslBlockDescriptors into the provider.
  4. The README and server tests pin the narrow surface so future slices can add value completions or relation-aware suggestions without accidentally turning ordinary PSL attributes into completion contexts.

Behavior changes & evidence

  • Configured PSL inputs now complete model field type positions. The server advertises completion and routes requests only after it finds an open configured document, cached parse artifacts, and a project symbol table.

    • Implementation: packages/1-framework/3-tooling/language-server/src/server.ts, packages/1-framework/3-tooling/language-server/src/completion-context.ts, packages/1-framework/3-tooling/language-server/src/completion-provider.ts
    • Evidence: packages/1-framework/3-tooling/language-server/test/server.test.ts, packages/1-framework/3-tooling/language-server/test/completion-context.test.ts, packages/1-framework/3-tooling/language-server/test/completion-provider.test.ts
  • Descriptor-backed generic block parameter keys now complete. The classifier identifies blank and partial key positions inside GenericBlockDeclarationAst bodies, the provider resolves the matching AuthoringPslBlockDescriptor, and completions exclude already-present sibling keys.

    • Implementation: packages/1-framework/3-tooling/language-server/src/completion-context.ts, packages/1-framework/3-tooling/language-server/src/completion-provider.ts, packages/1-framework/3-tooling/language-server/src/server.ts
    • Evidence: packages/1-framework/3-tooling/language-server/test/completion-context.test.ts, packages/1-framework/3-tooling/language-server/test/completion-provider.test.ts, packages/1-framework/3-tooling/language-server/test/server.test.ts
  • Model type candidates are stable and scoped to visible type-like symbols. The provider orders configured scalars before top-level candidates and namespace qualifiers, filters prefixes, completes namespace qualifiers such as auth. before namespace-local model names, and excludes generic block symbols from model-field-type completions.

    • Implementation: packages/1-framework/3-tooling/language-server/src/completion-provider.ts
    • Evidence: packages/1-framework/3-tooling/language-server/test/completion-provider.test.ts
  • Qualified type positions preserve the typed shape. Bare namespace prefixes like a| replace the typed segment with auth., namespace-qualified positions like auth.U| complete only the typed member segment, and contract-space-qualified positions like supabase:auth.P| replace only the typed name segment and use currently visible candidates.

    • Implementation: packages/1-framework/3-tooling/language-server/src/completion-context.ts, packages/1-framework/3-tooling/language-server/src/completion-provider.ts
    • Evidence: packages/1-framework/3-tooling/language-server/test/completion-context.test.ts, packages/1-framework/3-tooling/language-server/test/completion-provider.test.ts
  • Unsupported contexts return [] instead of misleading suggestions. Unconfigured documents, ordinary field/model attributes, attribute arguments, generic block parameter values, comments, constructor arguments, and other non-type/non-key positions are rejected before provider output reaches the client.

    • Implementation: packages/1-framework/3-tooling/language-server/src/completion-context.ts, packages/1-framework/3-tooling/language-server/src/server.ts
    • Evidence: packages/1-framework/3-tooling/language-server/test/server.test.ts, packages/1-framework/3-tooling/language-server/test/completion-context.test.ts
  • The supported editor surface is documented. The README now says completion is supported for configured PSL model field type positions and descriptor-backed generic block parameter-key positions, and explicitly excludes ordinary attributes, attribute arguments, generic block parameter values, relation-aware suggestions, and external contract-space discovery.

    • Implementation: packages/1-framework/3-tooling/language-server/README.md

Compatibility / migration / risk

No contract emission, runtime, migration, adapter, or public TypeScript API behavior changes are expected. The main risk is editor-surface false positives; this PR mitigates it by returning [] outside configured model field type and descriptor-backed generic block parameter-key positions.

Testing performed

  • pnpm --filter @prisma-next/framework-components build — passed; rebuilt missing local dist/ needed by language-server tests in this worktree
  • pnpm --filter @prisma-next/language-server test — 9 files / 103 tests passed
  • pnpm --filter @prisma-next/language-server typecheck — passed
  • pnpm --filter @prisma-next/language-server lint — passed, 26 files checked

Skill update

n/a — no reusable agent skill update is required. The language-server README documents the new editor-facing behavior and exclusions.

Follow-ups

  • Generic block parameter value completions can be added later by interpreting descriptor value kinds (ref, option, value, list) at the value cursor position.
  • Relation-aware field completions and external contract-space candidate discovery remain separate work.

Alternatives considered

  • Completion-marker reparsing. Rejected because the existing recovered CST/AST plus red-tree offsets are sufficient for this slice, and marker reparsing would create a second parser path to keep consistent.
  • Provider-side context rediscovery. Rejected because explicit classifier → provider dispatch makes unsupported contexts visible and keeps descriptor-backed generic-block completion from duplicating cursor analysis.
  • External contract-space candidate discovery. Rejected for this slice because the current language-server artifacts only expose visible project candidates. The syntax shape is supported, but new cross-contract indexing belongs in separate work.
  • Ordinary PSL attribute completions. Rejected for this slice because @ / @@ attributes are a different surface from descriptor-backed generic block parameters.

Checklist

  • All commits are signed off (git commit -s) per the DCO. The DCO status check will block merge if any commit is missing a Signed-off-by: trailer.
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated (or n/a if the change is doc-only / refactor with no behavioural delta).
  • The PR title is in TML-NNNN: <sentence-case title> form (Linear ticket prefix + concise title naming the concrete deliverable). See .claude/skills/create-pr/SKILL.md for the full convention.
  • The Skill update section above is filled in (or stated n/a — internal only).

Summary by CodeRabbit

  • New Features
    • Added context-aware PSL autocompletion in the language server for declaration keywords, model/composite field types, and generic block parameter keys, with . trigger and snippet insertion when supported.
  • Bug Fixes
    • Improved completion accuracy for partial/qualified identifiers and cursor-adjacent edits, returning no suggestions in unsupported locations.
    • Refreshed completion results based on the latest open document content.
    • Improved decorator semantic token ranges to better align with @ markers.
  • Documentation
    • Updated language-server and LSP playground documentation, including the runtime config endpoint.
  • Tests
    • Expanded completion and syntax/AST navigation test coverage.

@SevInf SevInf requested a review from a team as a code owner June 25, 2026 13:34
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds PSL completion support in the language server, updates parser syntax helpers used by completion, adjusts semantic-token decorator ranges, and changes the playground to serve and load runtime config over HTTP.

Changes

PSL completion stack

Layer / File(s) Summary
Syntax tree navigation
packages/1-framework/2-authoring/psl-parser/src/syntax/red.ts, packages/1-framework/2-authoring/psl-parser/src/syntax/navigation.ts, packages/1-framework/2-authoring/psl-parser/src/exports/syntax.ts, packages/1-framework/2-authoring/psl-parser/test/syntax/red.test.ts, packages/1-framework/2-authoring/psl-parser/test/syntax/navigation.test.ts
SyntaxToken becomes a class, offset/token-boundary APIs are added, trivia-aware navigation helpers are introduced, and the corresponding exports and tests are updated.
Braced blocks and qualified names
packages/1-framework/2-authoring/psl-parser/src/syntax/ast-helpers.ts, packages/1-framework/2-authoring/psl-parser/src/syntax/ast/declarations.ts, packages/1-framework/2-authoring/psl-parser/src/syntax/ast/qualified-name.ts, packages/1-framework/2-authoring/psl-parser/src/exports/syntax.ts, packages/1-framework/2-authoring/psl-parser/test/syntax/ast.test.ts
BracedBlock is added, declaration AST nodes implement it, and qualified-name segment resolution changes around . and : with new edge-case coverage.
Completion context classification
packages/1-framework/3-tooling/language-server/src/completion-context.ts, packages/1-framework/3-tooling/language-server/test/completion-context.test.ts
Defines the completion-context union and classifies cursor positions for declaration keywords, generic-block keys and values, model types, namespace members, space members, and unsupported contexts.
Completion item generation
packages/1-framework/3-tooling/language-server/src/completion-provider.ts, packages/1-framework/3-tooling/language-server/test/completion-provider.test.ts
Builds completion items for declaration keywords, generic-block parameters, model types, and namespace members with deterministic ordering, filtering, and snippet-aware insertion.
LSP completion wiring and server tests
packages/1-framework/3-tooling/language-server/src/server.ts, packages/1-framework/3-tooling/language-server/src/semantic-tokens.ts, packages/1-framework/3-tooling/language-server/test/server.test.ts, packages/1-framework/3-tooling/language-server/README.md
The server advertises and handles textDocument/completion, passes cached artifacts and snippet support through the completion path, updates semantic-token decorator range handling, and the tests and README are updated to cover the new flow.

LSP playground runtime config

Layer / File(s) Summary
Runtime config HTTP endpoint
apps/lsp-playground/src/cli.ts, apps/lsp-playground/README.md
Adds the runtime-config endpoint path, request parsing, in-memory config object, and custom HTTP handling that serves JSON and routes other requests to Vite.
Runtime config loading in browser client
apps/lsp-playground/src/client/main.ts, apps/lsp-playground/src/client/runtime.ts
The client fetches and validates runtime config at startup and uses it for schema display, file registration, WebSocket setup, workspace location, and editor resources.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • prisma/prisma-next#753: The completion PR uses pslBlockDescriptors as the source for generic-block keyword candidates, and this PR introduces that descriptor infrastructure.
  • prisma/prisma-next#776: The completion context/provider logic depends on the PSL parser AST types and syntax-tree APIs introduced there.
  • prisma/prisma-next#849: The model-type and namespace-member completion paths depend on the qualified-name parsing behavior changed in that PR.

Suggested reviewers

  • wmadden

Poem

🐇 I hopped through tokens, crisp and neat,
Through braces, dots, and : beats.
The server hums, the snippets bloom,
A tiny cursor finds its room.
Now PSL sings with fluffy light ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.98% 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 accurately captures the main change: adding PSL type and generic block completions.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lsp-autocomplete

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.

@pkg-pr-new

pkg-pr-new Bot commented Jun 25, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@871

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@871

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@871

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@871

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@871

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@871

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@871

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@871

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@871

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@871

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@871

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@871

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@871

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@871

@prisma-next/extension-supabase

npm i https://pkg.pr.new/@prisma-next/extension-supabase@871

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@871

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@871

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@871

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@871

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@871

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@871

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@871

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@871

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@871

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@871

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@871

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@871

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@871

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@871

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@871

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@871

@prisma-next/config-loader

npm i https://pkg.pr.new/@prisma-next/config-loader@871

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@871

@prisma-next/language-server

npm i https://pkg.pr.new/@prisma-next/language-server@871

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@871

prisma-next

npm i https://pkg.pr.new/prisma-next@871

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@871

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@871

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@871

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@871

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@871

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@871

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@871

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@871

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@871

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@871

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@871

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@871

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@871

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@871

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@871

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@871

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@871

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@871

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@871

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@871

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@871

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@871

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@871

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@871

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@871

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@871

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@871

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@871

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@871

commit: ee9c443

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 160.35 KB (0%)
postgres / emit 147.55 KB (0%)
mongo / no-emit 79.86 KB (0%)
mongo / emit 72.68 KB (0%)
cf-worker / no-emit 188.11 KB (0%)
cf-worker / emit 173.57 KB (0%)

@SevInf SevInf changed the title TML-2946: Add PSL model type completions TML-2946: Add PSL type and generic block completions Jun 25, 2026

@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 `@packages/1-framework/3-tooling/language-server/README.md`:
- Around line 37-39: The server module description is incomplete because it only
mentions model field type completions and omits the generic-block parameter-key
completions that `createServer(connection)` also wires up. Update the
`server.ts` bullet in the README so it reflects both supported completion
contexts, matching the behavior described in `completion-context.ts` and
`completion-provider.ts`.

In `@packages/1-framework/3-tooling/language-server/src/server.ts`:
- Around line 253-295: Refresh the completion path in completeDocument so it
does not rely on stale cached parse artifacts from
project.artifacts.getDocument(uri) when the buffer has just changed. Before
calling classifyPslCompletionContext(), rebuild or refresh the document
artifacts from the current document.getText() and use those updated artifacts
for the completion context and providePslCompletionItems call, while keeping the
existing early returns and try/catch behavior intact.
🪄 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.yml

Review profile: CHILL

Plan: Pro

Run ID: b388a8f7-7b3e-4afb-a96d-db96126c5a9d

📥 Commits

Reviewing files that changed from the base of the PR and between 01b1488 and 6d2947a.

⛔ Files ignored due to path filters (9)
  • projects/lsp-autocomplete/plan.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/model-type-completions/dispatches/01-cursor-classifier-substrate.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/model-type-completions/dispatches/02-model-type-provider.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/model-type-completions/dispatches/03-lsp-completion-route.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/model-type-completions/dispatches/04-scope-docs-and-final-guardrails.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/model-type-completions/plan.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/model-type-completions/spec.md is excluded by !projects/**
  • projects/lsp-autocomplete/spec.md is excluded by !projects/**
  • projects/lsp-autocomplete/trace.jsonl is excluded by !projects/**
📒 Files selected for processing (7)
  • packages/1-framework/3-tooling/language-server/README.md
  • packages/1-framework/3-tooling/language-server/src/completion-context.ts
  • packages/1-framework/3-tooling/language-server/src/completion-provider.ts
  • packages/1-framework/3-tooling/language-server/src/server.ts
  • packages/1-framework/3-tooling/language-server/test/completion-context.test.ts
  • packages/1-framework/3-tooling/language-server/test/completion-provider.test.ts
  • packages/1-framework/3-tooling/language-server/test/server.test.ts

Comment thread packages/1-framework/3-tooling/language-server/README.md Outdated
Comment thread packages/1-framework/3-tooling/language-server/src/server.ts
@SevInf SevInf force-pushed the lsp-autocomplete branch from 6d2947a to 8ef6f1a Compare June 25, 2026 15:49

@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.

🧹 Nitpick comments (1)
packages/1-framework/3-tooling/language-server/src/completion-provider.ts (1)

178-186: 🎯 Functional Correctness | 🔵 Trivial

Conditionally generate name placeholders based on descriptor name.required metadata.

While all current descriptors in the codebase define name: { required: true }, the AuthoringPslBlockDescriptor type permits required: false. The current implementation unconditionally inserts a name snippet placeholder, which would generate invalid boilerplate for any future optional-name blocks.

Update genericBlockDeclarationKeywordCandidates to inspect the descriptor's name.required property and omit the name placeholder in insertText and snippetText when optional.

🤖 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 `@packages/1-framework/3-tooling/language-server/src/completion-provider.ts`
around lines 178 - 186, Update genericBlockDeclarationKeywordCandidates to
respect each descriptor’s name.required metadata instead of always inserting a
name placeholder. Inspect the descriptor returned by descriptorBlockKeywords/its
source descriptor data and, when name.required is false, omit the
nameSnippetPlaceholder from both insertText and snippetText so optional-name
blocks generate valid boilerplate. Keep the current placeholder behavior only
for descriptors whose name is required.
🤖 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.

Nitpick comments:
In `@packages/1-framework/3-tooling/language-server/src/completion-provider.ts`:
- Around line 178-186: Update genericBlockDeclarationKeywordCandidates to
respect each descriptor’s name.required metadata instead of always inserting a
name placeholder. Inspect the descriptor returned by descriptorBlockKeywords/its
source descriptor data and, when name.required is false, omit the
nameSnippetPlaceholder from both insertText and snippetText so optional-name
blocks generate valid boilerplate. Keep the current placeholder behavior only
for descriptors whose name is required.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: b182fb52-90c5-497a-a328-6f1cc92b9db2

📥 Commits

Reviewing files that changed from the base of the PR and between 8ef6f1a and d63f505.

⛔ Files ignored due to path filters (6)
  • projects/lsp-autocomplete/plan.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/top-level-keyword-completions/dispatches/01-declaration-keyword-completions.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/top-level-keyword-completions/plan.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/top-level-keyword-completions/spec.md is excluded by !projects/**
  • projects/lsp-autocomplete/spec.md is excluded by !projects/**
  • projects/lsp-autocomplete/trace.jsonl is excluded by !projects/**
📒 Files selected for processing (7)
  • packages/1-framework/3-tooling/language-server/README.md
  • packages/1-framework/3-tooling/language-server/src/completion-context.ts
  • packages/1-framework/3-tooling/language-server/src/completion-provider.ts
  • packages/1-framework/3-tooling/language-server/src/server.ts
  • packages/1-framework/3-tooling/language-server/test/completion-context.test.ts
  • packages/1-framework/3-tooling/language-server/test/completion-provider.test.ts
  • packages/1-framework/3-tooling/language-server/test/server.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/1-framework/3-tooling/language-server/src/server.ts
  • packages/1-framework/3-tooling/language-server/test/completion-context.test.ts

@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: 1

🧹 Nitpick comments (1)
apps/lsp-playground/src/client/main.ts (1)

19-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the runtime-config contract into a shared module.

RUNTIME_CONFIG_PATH and RuntimeConfig are re-declared here and in apps/lsp-playground/src/cli.ts, so a future rename can still typecheck and fail only once the browser boots. Pull the path constant and TypeScript type into one shared file, and keep only the runtime validator local.

Also applies to: 33-55

🤖 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 `@apps/lsp-playground/src/client/main.ts` around lines 19 - 27, The
runtime-config contract is duplicated between the client and CLI, so extract
`RUNTIME_CONFIG_PATH` and `RuntimeConfig` into a shared module and import them
from both `main.ts` and `cli.ts`. Keep the runtime-specific validation logic
local in each entrypoint, but remove the redeclared path constant and interface
so there is a single source of truth for the shared shape.
🤖 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 `@apps/lsp-playground/src/cli.ts`:
- Around line 197-199: The request path parsing in the playground handler should
not depend on request.headers.host, since using the incoming Host header as the
base for new URL can throw before a response is sent. Update the logic around
the request.url parsing in the cli.ts handler to use a fixed local base URL, or
wrap the URL construction in error handling and return a 400 when parsing fails.
Keep the change focused on the requestPath/baseUrl logic so the playground
process cannot be taken down by a malformed Host header.

---

Nitpick comments:
In `@apps/lsp-playground/src/client/main.ts`:
- Around line 19-27: The runtime-config contract is duplicated between the
client and CLI, so extract `RUNTIME_CONFIG_PATH` and `RuntimeConfig` into a
shared module and import them from both `main.ts` and `cli.ts`. Keep the
runtime-specific validation logic local in each entrypoint, but remove the
redeclared path constant and interface so there is a single source of truth for
the shared shape.
🪄 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.yml

Review profile: CHILL

Plan: Pro

Run ID: 853eb3b7-31d2-4bc1-83b5-8c84988b9357

📥 Commits

Reviewing files that changed from the base of the PR and between d63f505 and 77d6293.

📒 Files selected for processing (4)
  • apps/lsp-playground/README.md
  • apps/lsp-playground/src/cli.ts
  • apps/lsp-playground/src/client/main.ts
  • apps/lsp-playground/src/client/runtime.ts
💤 Files with no reviewable changes (1)
  • apps/lsp-playground/src/client/runtime.ts

Comment thread apps/lsp-playground/src/cli.ts Outdated
readonly position: Position;
}

export type UnsupportedPslCompletionReason =

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 do we need this?

let previousSignificant: SyntaxToken | undefined;
let touching: SyntaxToken | undefined;

for (const token of root.tokens()) {

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.

We can use binary search here if SyntaxNode allow to access chidren by index, can't we?

};
}

function isTrivia(token: SyntaxToken): boolean {

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.

Don't we already have a helper for this in parser module?

return tokenContext({ current, previous, previousSignificant, touching });
}

function tokenContext(input: {

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.

WTF is this function doing? Just construct goddamn object from a list of properties, it's fine if some property value is explicitly undefined


interface TokenContext {
readonly current?: SyntaxToken;
readonly previous?: SyntaxToken;

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.

This property is not used anywhere

if (offset >= token.offset && offset < tokenEnd) {
current = token;
}
if (offset > token.offset && offset <= tokenEnd) {

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.

This condition does not make sense to me. It looks like you only want to know if offset is directly at end of current token?

}

const node = findDeepestNodeAtOffset(input.document.syntax, offset);
const previousNode =

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.

SyntaxNode has methods for navigating siblings specifically for that, use those rather than reinvent the wheel

@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: 1

🤖 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 `@packages/1-framework/2-authoring/psl-parser/src/syntax/navigation.ts`:
- Around line 57-63: previousNonTriviaToken currently returns undefined for
zero-width SyntaxNode inputs because it relies on firstToken, so it never walks
to the preceding significant token. Update previousNonTriviaToken to detect
empty nodes and resolve a suitable starting token from the node’s
position/surrounding siblings or ancestor chain before iterating prevToken,
while still skipping trivia. Keep the behavior for SyntaxToken inputs unchanged
and preserve the helper’s contract for incomplete trees.
🪄 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.yml

Review profile: CHILL

Plan: Pro

Run ID: 08fa15a3-ccc2-4f84-b769-6a338e751a22

📥 Commits

Reviewing files that changed from the base of the PR and between 77d6293 and 6f2d504.

⛔ Files ignored due to path filters (7)
  • projects/lsp-autocomplete/plan.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/completion-context-simplification/plan.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/completion-context-simplification/spec.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/parser-red-tree-navigation/plan.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/parser-red-tree-navigation/spec.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/top-level-keyword-completions/dispatches/02-review-feedback-and-classifier-navigation.md is excluded by !projects/**
  • projects/lsp-autocomplete/slices/top-level-keyword-completions/plan.md is excluded by !projects/**
📒 Files selected for processing (11)
  • apps/lsp-playground/src/cli.ts
  • packages/1-framework/2-authoring/psl-parser/src/exports/syntax.ts
  • packages/1-framework/2-authoring/psl-parser/src/syntax/navigation.ts
  • packages/1-framework/2-authoring/psl-parser/src/syntax/red.ts
  • packages/1-framework/2-authoring/psl-parser/test/syntax/navigation.test.ts
  • packages/1-framework/2-authoring/psl-parser/test/syntax/red.test.ts
  • packages/1-framework/3-tooling/language-server/src/completion-context.ts
  • packages/1-framework/3-tooling/language-server/src/completion-provider.ts
  • packages/1-framework/3-tooling/language-server/src/server.ts
  • packages/1-framework/3-tooling/language-server/test/completion-context.test.ts
  • packages/1-framework/3-tooling/language-server/test/server.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/1-framework/3-tooling/language-server/test/completion-context.test.ts
  • packages/1-framework/3-tooling/language-server/src/server.ts
  • apps/lsp-playground/src/cli.ts
  • packages/1-framework/3-tooling/language-server/test/server.test.ts
  • packages/1-framework/3-tooling/language-server/src/completion-provider.ts

Comment thread packages/1-framework/2-authoring/psl-parser/src/syntax/navigation.ts Outdated
SevInf added 13 commits June 29, 2026 08:45
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Make red tokens navigable (parent, prev/next token) and add SyntaxNode.tokenAtOffset (none/single/between with left/right bias), coveringElement, first/lastToken, plus trivia-skip helpers (skipTriviaToken, nonTriviaSibling, previousNonTriviaToken). Mirrors rust-analyzer syntax idioms; no fake-identifier marker.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…navigation

Anchor classification on tokenAtOffset(offset).leftBiased() and navigate from token.parent, with one source_range()-style replacement helper. Removes findCursorContext, findTokenContext, findDeepestNodeAtOffset, tokensBetween, lineStartOffsetFromTokens, containsOnlyWhitespaceTokens, the duplicated prefix builders, and the unused UnsupportedPslCompletionReason. Behavior unchanged; matches rust-analyzer classifier shape.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…layground URL parsing

Address PR review: rebuild completion artifacts from the current buffer before classifying so completions after an edit are not stale; parse playground request URLs against a fixed local base and return 400 on malformed/absent URLs instead of trusting the incoming Host header.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…d review dispatch

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…code comments

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
… AST

Replace the splitQualifiedPrefix source scan (which re-tokenized the qualified name by counting : and . in raw text) with QualifiedNameAst navigation: contract-space/namespace/name roles are resolved from colon()/dot() offsets and segment IdentifierAst offsets relative to the cursor. The only source touch left is slicing the cursor segments own identifier-token text. Deletes splitQualifiedPrefix, pathFromSegments, and segmentAt; behavior preserved, two new tests pin the colon-without-dot and cursor-mid-name cases.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Order the Position/Range/SemanticTokens type imports merged during the rebase against main (semantic tokens + completion landed in parallel).

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
SevInf added 8 commits June 29, 2026 14:45
With client-side filtering, the classifier no longer needs to carry the typed
prefix. Removes the prefix string from the declaration-keyword and generic-block
contexts and shrinks the model-field-type prefix to just the namespace it
selects (deleting TypeNamePrefix and the cursor-truncation helper). Completability
and namespace resolution are unchanged.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
… position

Replaces the single modelFieldType context with position-specific modelType /
spaceMember / namespaceMember, and splits genericBlockParameter into
genericBlockKey / genericBlockValue. Each position now maps to a focused
provider. spaceMember and genericBlockValue are seams for behaviour not yet
backed by data (foreign contract-space members need the multi-input symbol
table; parameter-value completion is future work) and return no items. The only
changed outcome: a :-qualified type with no . now classifies as spaceMember
rather than offering bare model-type completions.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Mirrors SyntaxNode.endOffset (offset + textLength) so consumers stop hand-rolling
offset + text.length.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
- simplify classifyTypePosition to read the separator-positional accessors
  directly (malformed leading-separator types now resolve to modelType)
- inline the position factories and sourceRangeStart; use token.endOffset
- relocate the attribute guard from the top-level classifier into the
  generic-block classifier (the only place it is load-bearing: an @@ attribute
  inside a generic block would otherwise misclassify as genericBlockKey)
- offer type completion for composite-type fields, not just model fields
- treat the next line after a field name as a valid blank-type position, since
  the parser skips newlines as trivia and accepts type-on-newline

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
nodeForContext only redirected when the anchor was a Document or ModelDeclaration,
and in both cases the classification is identical with or without the redirect
(a bare model body has nothing to complete; top-level resolves to a document-scope
keyword either way). Field positions already anchor on the FieldDeclaration since
the parser attaches a field trailing trivia to the field. Drop the dead
nodeForContext and the anchor/context distinction.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Model/composite/namespace/types/generic block AST nodes share lbrace()/rbrace();
a BracedBlock interface lets consumers write one brace-body helper instead of
duplicating it per node kind.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
- closestAst takes variadic casts and walks the ancestor path once (model-or-
  composite check no longer traverses twice)
- classifyTypePosition builds and returns the context directly, dropping the
  intermediate TypePosition type and the re-mapping switch
- declaration keywords may be completed after a closing brace regardless of
  newlines (model A {} model B {} is valid one-line PSL); keyed on RBrace, not
  newlineBetween
- merge declarationBodyContainsOffset/namespaceBodyContainsOffset into a single
  blockBodyContainsOffset over BracedBlock, used by the namespace-scope, non-
  declaration-body, and generic-block checks alike

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
… combinator

Reverts closestAst to its single-cast form and adds a small any() cast-combinator,
so the model-or-composite lookup reads closestAst(node, any(a, b)) instead of
overloading closestAst with variadic casts. Single ancestor walk, identical
behaviour.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>

@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: 3

🤖 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 `@packages/1-framework/3-tooling/language-server/src/completion-context.ts`:
- Around line 45-50: The namespace-member completion payload is dropping the
contract-space qualifier, so `classifyTypePosition()` and
`NamespaceMemberCompletionContext` currently treat foreign-space and local
namespace lookups the same. Update the context shape to retain the `space`
information (or split this into a distinct completion kind) and thread that
through `provideNamespaceMemberCompletionItems()` so it can detect external
contract-space prefixes and short-circuit instead of resolving them against
`symbolTable.topLevel.namespaces[namespace]`.

In `@packages/1-framework/3-tooling/language-server/src/completion-provider.ts`:
- Around line 265-274: The namespace completion path in
provideNamespaceMemberCompletionItems is incorrectly resolving contract-space
namespaces from source.symbolTable, so foreign references like supabase:auth.|
get treated as local auth members. Update this branch to check the namespace
context for a preserved space/contract-space marker and, when it is foreign,
return an empty completion list instead of calling namespaceCandidates on the
local symbol table; only local namespaces should continue using
modelTypeCompletionItems.
- Around line 381-389: The namespace qualifier completion is inserting only the
bare namespace name, so accepting it does not produce a qualifier flow. Update
namespaceQualifierCandidate in completion-provider.ts to insert the dotted
namespace form (for example, the namespace name plus a trailing dot) and keep
the displayed label consistent with that qualifier behavior so users can
continue completing the qualified member after insertion.
🪄 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.yml

Review profile: CHILL

Plan: Pro

Run ID: 35f1ffc5-9d1f-4960-b41c-b193f8cbaa75

📥 Commits

Reviewing files that changed from the base of the PR and between 77d6293 and 8281b0a.

📒 Files selected for processing (20)
  • apps/lsp-playground/README.md
  • apps/lsp-playground/src/cli.ts
  • apps/lsp-playground/src/client/main.ts
  • apps/lsp-playground/src/client/runtime.ts
  • packages/1-framework/2-authoring/psl-parser/src/exports/syntax.ts
  • packages/1-framework/2-authoring/psl-parser/src/syntax/ast-helpers.ts
  • packages/1-framework/2-authoring/psl-parser/src/syntax/ast/declarations.ts
  • packages/1-framework/2-authoring/psl-parser/src/syntax/ast/qualified-name.ts
  • packages/1-framework/2-authoring/psl-parser/src/syntax/navigation.ts
  • packages/1-framework/2-authoring/psl-parser/src/syntax/red.ts
  • packages/1-framework/2-authoring/psl-parser/test/syntax/ast.test.ts
  • packages/1-framework/2-authoring/psl-parser/test/syntax/navigation.test.ts
  • packages/1-framework/2-authoring/psl-parser/test/syntax/red.test.ts
  • packages/1-framework/3-tooling/language-server/README.md
  • packages/1-framework/3-tooling/language-server/src/completion-context.ts
  • packages/1-framework/3-tooling/language-server/src/completion-provider.ts
  • packages/1-framework/3-tooling/language-server/src/server.ts
  • packages/1-framework/3-tooling/language-server/test/completion-context.test.ts
  • packages/1-framework/3-tooling/language-server/test/completion-provider.test.ts
  • packages/1-framework/3-tooling/language-server/test/server.test.ts
💤 Files with no reviewable changes (1)
  • apps/lsp-playground/src/client/runtime.ts
✅ Files skipped from review due to trivial changes (2)
  • apps/lsp-playground/README.md
  • packages/1-framework/3-tooling/language-server/README.md
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/1-framework/2-authoring/psl-parser/src/exports/syntax.ts
  • packages/1-framework/2-authoring/psl-parser/test/syntax/red.test.ts
  • packages/1-framework/2-authoring/psl-parser/test/syntax/navigation.test.ts
  • packages/1-framework/2-authoring/psl-parser/src/syntax/navigation.ts
  • apps/lsp-playground/src/cli.ts
  • packages/1-framework/3-tooling/language-server/test/completion-provider.test.ts
  • packages/1-framework/3-tooling/language-server/src/server.ts
  • apps/lsp-playground/src/client/main.ts
  • packages/1-framework/3-tooling/language-server/test/server.test.ts
  • packages/1-framework/2-authoring/psl-parser/src/syntax/red.ts

Comment on lines +265 to +274
function provideNamespaceMemberCompletionItems(
context: NamespaceMemberCompletionContext,
sourceFile: SourceFile,
source: PslCompletionCandidateSource,
): readonly CompletionItem[] {
return modelTypeCompletionItems(
context,
sourceFile,
namespaceCandidates(source.symbolTable?.topLevel.namespaces[context.namespace]),
);

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Don't resolve foreign contract-space namespaces from the local symbol table.

Because NamespaceMemberCompletionContext has no space, this branch treats supabase:auth.| exactly like auth.| and returns members from the local auth namespace. That contradicts the PR objective that external contract-space discovery remains unsupported. Once the context preserves space, this path should return [] for foreign spaces until external symbols exist.

🤖 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 `@packages/1-framework/3-tooling/language-server/src/completion-provider.ts`
around lines 265 - 274, The namespace completion path in
provideNamespaceMemberCompletionItems is incorrectly resolving contract-space
namespaces from source.symbolTable, so foreign references like supabase:auth.|
get treated as local auth members. Update this branch to check the namespace
context for a preserved space/contract-space marker and, when it is foreign,
return an empty completion list instead of calling namespaceCandidates on the
local symbol table; only local namespaces should continue using
modelTypeCompletionItems.

Comment on lines +381 to +389
function namespaceQualifierCandidate(namespace: NamespaceSymbol): ModelTypeCompletionCandidate {
return {
category: 'namespace',
label: namespace.name,
insertText: namespace.name,
filterText: namespace.name,
detail: 'Namespace',
kind: CompletionItemKind.Module,
};

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Insert namespace qualifiers with the trailing dot.

These candidates currently insert auth, not auth.. Accepting the completion at a bare type position therefore produces a plain identifier instead of the advertised namespace qualifier flow. Make the qualifier candidate insert the dotted form (and ideally label it the same way).

🤖 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 `@packages/1-framework/3-tooling/language-server/src/completion-provider.ts`
around lines 381 - 389, The namespace qualifier completion is inserting only the
bare namespace name, so accepting it does not produce a qualifier flow. Update
namespaceQualifierCandidate in completion-provider.ts to insert the dotted
namespace form (for example, the namespace name plus a trailing dot) and keep
the displayed label consistent with that qualifier behavior so users can
continue completing the qualified member after insertion.

SevInf added 7 commits June 29, 2026 19:27
hasUnsupportedAncestor (3 walks) and isInsideNonDeclarationKeywordBody (4 walks)
each collapse to a single closestAst over an any() of their casts. The block
kinds never nest within one another, so the nearest ancestor of any kind equals
the OR of each kind. Explicit type args (BracedBlock / AstNode) pin inference to
the shared interfaces.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Replace the single-T any() signature with an overload pair: a precise
generic public signature that distributes CastTarget over the cast
tuple, plus a loose impl signature whose body type-checks without a
cast. Call sites drop their explicit any<BracedBlock> / any<AstNode>
type arguments and infer the union automatically.

A single-signature const-generic form does not work: const only
affects call-site tuple inference, not the body, where cast(node)
stays opaque and is unassignable to the generic return type.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Relocate two completion-context helpers into @prisma-next/psl-parser so
they sit with the AST navigation surface they belong to:

- closestAst becomes SyntaxNode.findClosestParent, a self-inclusive
  generic method (tests the node itself before walking ancestors),
  mirroring rust-analyzer ancestors().find_map.
- the any cast-combinator moves to ast-helpers next to findFirstChild /
  filterChildren and is exported from the syntax barrel.

completion-context drops both local definitions, imports any, and
rewires its eight call sites to node?.findClosestParent(...).

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…efore them

The comment guard checked both leftBiased and rightBiased tokens. The
rightBiased clause fires when the cursor sits in a real type position
that merely precedes a trailing comment (model Post { author |// note }),
wrongly returning unsupported instead of a model-type completion.

Drop the rightBiased clause: a cursor inside a comment is detected by the
leftBiased token alone. Add a regression test pinning that completion is
still offered in a type position before a trailing comment.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
The offset field on UnsupportedPslCompletionContext was never read: the
provider short-circuits the unsupported case to an empty list, and no
test asserts it. Remove the field, delete the unsupported(offset) helper
that threaded it, and return a single UNSUPPORTED sentinel from every
non-completable position.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Red nodes now carry their index within the parent, set once at
construction. Sibling navigation becomes childAt(parent, index +/- 1),
matching the rust-analyzer red-tree idiom, and the bespoke siblingAfter
/ siblingBefore green-layer walkers are deleted. Navigation no longer
touches the green layer or guesses node identity from (green, offset);
the index also disambiguates a zero-width child from an offset-colliding
neighbour, which an offset-only scheme cannot.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>

@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.

🧹 Nitpick comments (1)
packages/1-framework/2-authoring/psl-parser/src/syntax/red.ts (1)

49-57: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid routing sibling traversal through childAt.

nextSibling*, prevSibling*, and climbing traversal still call childAt, which rescans from child 0 to recompute offsets. Iterating siblings is therefore O(n²) on wide nodes despite the new index field. Use the current element’s offset plus adjacent green child lengths for true O(1) sibling wrapping.

♻️ Proposed localized refactor
   get nextSiblingOrToken(): SyntaxElement | undefined {
-    return childAt(this.parent, this.index + 1);
+    return siblingAfter(this);
   }
 
   /** The sibling element immediately before this token within its parent. */
   get prevSiblingOrToken(): SyntaxElement | undefined {
-    return childAt(this.parent, this.index - 1);
+    return siblingBefore(this);
   }
   get nextSibling(): SyntaxElement | undefined {
-    return this.parent === undefined ? undefined : childAt(this.parent, this.index + 1);
+    return siblingAfter(this);
   }
 
   get prevSibling(): SyntaxElement | undefined {
-    return this.parent === undefined ? undefined : childAt(this.parent, this.index - 1);
+    return siblingBefore(this);
   }
 function climbingNext(el: SyntaxElement): SyntaxElement | undefined {
   let current: SyntaxElement = el;
   for (;;) {
     const parent = current.parent;
     if (parent === undefined) return undefined;
-    const sibling = childAt(parent, current.index + 1);
+    const sibling = siblingAfter(current);
     if (sibling !== undefined) return sibling;
     current = parent;
   }
 }
 
 function climbingPrev(el: SyntaxElement): SyntaxElement | undefined {
   let current: SyntaxElement = el;
   for (;;) {
     const parent = current.parent;
     if (parent === undefined) return undefined;
-    const sibling = childAt(parent, current.index - 1);
+    const sibling = siblingBefore(current);
     if (sibling !== undefined) return sibling;
     current = parent;
   }
 }
+
+function siblingAfter(el: SyntaxElement): SyntaxElement | undefined {
+  const parent = el.parent;
+  if (parent === undefined) return undefined;
+  const index = el.index + 1;
+  const target = parent.green.children[index];
+  if (target === undefined) return undefined;
+  return wrapElement(target, el.offset + elementTextLength(el.green), parent, index);
+}
+
+function siblingBefore(el: SyntaxElement): SyntaxElement | undefined {
+  const parent = el.parent;
+  if (parent === undefined) return undefined;
+  const index = el.index - 1;
+  const target = parent.green.children[index];
+  if (target === undefined) return undefined;
+  return wrapElement(target, el.offset - elementTextLength(target), parent, index);
+}

Also applies to: 188-193, 388-405, 422-433

🤖 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 `@packages/1-framework/2-authoring/psl-parser/src/syntax/red.ts` around lines
49 - 57, The sibling/climbing traversal still routes through childAt, which
defeats the new index-based optimization and keeps sibling iteration O(n²) on
wide nodes. Update the traversal logic in red.ts for nextSiblingOrToken,
prevSiblingOrToken, and the related climbing paths mentioned in the diff to
compute adjacent elements directly from the current element’s offset and
neighboring green child lengths instead of rescanning from child 0. Keep the fix
localized around the existing SyntaxElement accessors and any helper used by the
climbing traversal so sibling wrapping becomes O(1).
🤖 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.

Nitpick comments:
In `@packages/1-framework/2-authoring/psl-parser/src/syntax/red.ts`:
- Around line 49-57: The sibling/climbing traversal still routes through
childAt, which defeats the new index-based optimization and keeps sibling
iteration O(n²) on wide nodes. Update the traversal logic in red.ts for
nextSiblingOrToken, prevSiblingOrToken, and the related climbing paths mentioned
in the diff to compute adjacent elements directly from the current element’s
offset and neighboring green child lengths instead of rescanning from child 0.
Keep the fix localized around the existing SyntaxElement accessors and any
helper used by the climbing traversal so sibling wrapping becomes O(1).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: bcccb435-c86b-416f-8267-9bdf735e071a

📥 Commits

Reviewing files that changed from the base of the PR and between 1f88b69 and d1ccb8f.

📒 Files selected for processing (7)
  • packages/1-framework/2-authoring/psl-parser/src/exports/syntax.ts
  • packages/1-framework/2-authoring/psl-parser/src/syntax/ast-helpers.ts
  • packages/1-framework/2-authoring/psl-parser/src/syntax/red.ts
  • packages/1-framework/2-authoring/psl-parser/test/syntax/ast.test.ts
  • packages/1-framework/2-authoring/psl-parser/test/syntax/red.test.ts
  • packages/1-framework/3-tooling/language-server/src/completion-context.ts
  • packages/1-framework/3-tooling/language-server/test/completion-context.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/1-framework/2-authoring/psl-parser/test/syntax/ast.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/1-framework/2-authoring/psl-parser/src/exports/syntax.ts
  • packages/1-framework/3-tooling/language-server/test/completion-context.test.ts

SevInf added 13 commits June 30, 2026 09:03
…t source text

The decorator highlighter walked the raw source string backwards over @
characters to extend the first segment range. The @ / @@ prefix is a real
token (At / DoubleAt), so read its offset from the attribute node via
findChildToken instead. Drops the character scan and the sourceText
threading through collectDecoratorName / rangeForDecoratorIdentifier.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…oken

previousNonTriviaToken hand-rolled the trivia-skipping loop that
skipTriviaToken already encapsulates. Step strictly backwards once, then
delegate to skipTriviaToken, matching the rust-analyzer composition
(prev_token then skip_trivia_token) instead of duplicating it.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…cturally

Replace the token-scan declaration-keyword logic with a structural rule:
a keyword is offered unless the cursor sits inside an established
declaration. The nearest declaration ancestor permits a keyword only
when it is still nascent (its sole significant child is the keyword),
when the cursor is past its closing brace, or when it is a namespace
body offering a fresh nested slot.

This fixes the cursor wedged right after a complete declaration
(model A {}|, previously misread as inside the model) while keeping a
mid-header position (model A|) correctly rejected. Deletes the
isInsideNonDeclarationKeywordBody and declarationKeywordAllowed helpers;
previousSignificantToken stays for the generic-block value position.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…sors

canBeginDeclaration counted non-trivia children to decide whether a
declaration was still nascent. Read it off the AST instead: a nascent
declaration has no lbrace and no name. TypesBlockAst is anonymous (no
name accessor), so discriminate it with instanceof, which also narrows
the union so name() resolves on the rest. The namespace case is now an
explicit branch (a namespace permits a keyword only in its body) rather
than an opaque combined flag.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
A field with no type emitted a zero-width TypeAnnotation node — a
deviation from the rust-analyzer rule that missing constructs are
absent, not materialized empty. parseTypeAnnotation now emits a node
only when type content is present.

Field-type completion relied on that placeholder to anchor the empty
type slot. Without it, a typeless field has no TypeAnnotation and its
trailing trivia attaches to the enclosing block, so the slot is found
positionally: fieldForTypeSlot resolves the field via the nearest
significant token to the left, and emptyTypeSlotEnd bounds the slot at
the first attribute or next sibling. The textLength === 0 branch is
deleted (no zero-width node can exist for it to match).

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…dth navigation handling

parseAttributeArg, like parseTypeAnnotation before it, emitted a
zero-width AttributeArg node when no named-arg or value-expression was
present (e.g. @default(,)). Guard on the expression FIRST-set so a node
is emitted only when the arg has content.

With both placeholder emits gone, no non-root node can be zero-width, so
the red-tree navigation no longer needs to special-case it: isInside /
containsOffset / containsRange collapse to the plain inclusive span, and
the zero-width child skip in tokenAtOffset is removed. A parametrized
test pins the precondition across malformed inputs; the obsolete
synthetic zero-width-seam test is removed.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ken primitive

The completion classifier recovered "cursor in a gap with no node to anchor
on" three different ways: a raw leftBiased().parent anchor, a trivia-branching
fallback inside fieldForTypeSlot, and a bespoke previousSignificantToken for
the generic-block value check. Collapse all three onto one cursor-relative
look-left primitive, contextToken, which skips the in-progress edit identifier
the way tsserver adjusts previousToken to contextToken.

The generic-block structural checks (which block, which pair, in-field) stay
anchored on the cursor own node, since whether the cursor sits in a key/value/
attribute slot is a node-structural question, not a look-left one; only the
value-gap check moves to contextToken. canBeginDeclaration generalizes its
past-rbrace clause to namespaces so a cursor past a closed namespace closing
brace can still begin a document-level declaration, consistent with contextToken
anchoring on the closing brace.

previousSignificantToken is deleted; previousNonTriviaToken is removed from
this file imports (its parser-side removal follows).

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…on helper

previousNonTriviaToken existed solely to back the language server
previousSignificantToken, which the contextToken unification deleted. Its only
production consumer gone, the wrapper is dead: contextToken reaches the same
token via skipTriviaToken directly. Remove the function, its re-export, and its
test block. skipTriviaToken, nonTriviaSibling, and isTrivia keep their other
live consumers and are untouched.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…once per request

contextToken folded the look-left over cursorIdentifier, so the classifier
recomputed the edit identifier three times: once for replacementStartOffset and
once inside each contextToken call. Compute the edit and the context token once
at the top of classifyPslCompletionContext and thread the results down -
contextToken now takes the precomputed edit, and the generic-block value check
reads the passed-in token instead of recomputing. Pure de-duplication, no
behaviour change.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…eclaration

Rename the declaration-keyword gate and its declaration argument to name what
they check: whether a declaration keyword can be completed given the preceding
declaration the cursor sits against.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
… token

The empty-type-slot check was the last completion site recovering a gap with
numeric offset bounds (emptyTypeSlotEnd + offset > fieldNameEnd && offset <=
slotEnd) instead of the unified look-left mechanism. Since a typeless field has
no node spanning its type slot, the slot is valid exactly when the context
token belongs to the field own name - nothing significant sits between the name
and the cursor; an attribute token (or anything else) means the cursor has left
the slot. Thread the context token into classifyModelFieldType and test it with
fieldName.syntax.isInside(context.offset); identity comparison is unreliable
because the red layer recreates wrapper objects on traversal. emptyTypeSlotEnd
is deleted along with its lone nonTriviaSibling consumer in this file.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
"context" said nothing about which token the helper returns. It is the
significant token preceding the cursor (skipping the in-progress edit
identifier and trivia) - the token the classifier anchors on. Rename the
helper to precedingToken and the derived locals/fields to match
(preceding, precedingNode, the precedingToken fields threaded into the
generic-block and model-field classifiers), harmonising with the
precedingDeclaration naming.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
… param names in the provider

existingParameterNames was candidate-filtering data precomputed on the
completion context, but the context should describe the cursor position, not
gather provider data. Replace GenericBlockKeyCompletionContext.existingParameterNames
with the enclosing block AST node and compute the existing-parameter set in the
provider from block.entries(), excluding the in-progress pair the cursor sits
inside. Behaviour is preserved: the old sameSpan-against-active-pair exclusion
and the new isOutside(cursorOffset) check select the same entry.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
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