Skip to content

TML-2893: resolve pass + ResolvedDocument semantic layer over the CST#818

Closed
SevInf wants to merge 3 commits into
mainfrom
psl-parser-replace
Closed

TML-2893: resolve pass + ResolvedDocument semantic layer over the CST#818
SevInf wants to merge 3 commits into
mainfrom
psl-parser-replace

Conversation

@SevInf

@SevInf SevInf commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Linked issue

Refs TML-2893. First (stack-root) slice of the PSL parser replacement — introduces the resolved semantic layer the SQL and Mongo authoring paths migrate onto in follow-up slices. Builds on the fault-tolerant CST parser landed in #795.

At a glance

import { parse, resolve } from '@prisma-next/psl-parser'

const { document } = parse(source) // CST — lossless, position-bearing
const resolved = resolve(document) // target-agnostic, name-resolved view

const user = resolved.namespaces.get('auth')!.models.get('User')!
user.fields.get('posts')!.type.target
// → { kind: 'ref', coord: { kind: 'model', namespaceId: 'blog', name: 'Post' } }

resolved.diagnostics // syntactic (from parse) + semantic (from resolve), one list

Today the only PSL semantic layer is PslDocumentAst from the legacy regex parser, which half-resolves type references (it leaves a name string each interpreter re-resolves by hand via allPslModels.find(...)). resolve resolves every reference to a kind-ful coordinate, once.

Decision

This PR ships two things in @prisma-next/psl-parser:

  1. A resolve(document, options?): ResolvedDocument pass over the CST — a target-agnostic, name-resolved view: per-namespace name-keyed ReadonlyMap stores (models / enums / compositeTypes / extensionBlocks) plus document-level namedTypes, insertion-ordered (source order preserved), first-declaration-wins on duplicates. Every field / named-type reference resolves to a TypeTargetscalar / ref (a kind-ful DeclCoord) / crossSpace (coordinates, no kind — bound later against composed contracts) / constructor / unresolved. Attribute arg values are the CST ExpressionAst nodes (no normalized value union); ResolvedAttribute accessors (positionalArg, stringArg) subsume the old getPositionalArgument / parseQuotedStringLiteral helpers. Every resolved entity keeps a syntax back-pointer to its CST node for spans.

  2. Relocation of the two semantic validators into resolve, at verbatim message/code parity — extension-block validation (the PSL_EXTENSION_* / PSL_INVALID_EXTENSION_BLOCK_* family, descriptor- and codec-driven) and named-type collision. These were misfiled inside the legacy parser even though they are semantic, not syntactic. parse stays purely syntactic.

resolve has no production consumer yet — the SQL and Mongo interpreters migrate onto it in later slices, and only then is the legacy parser deleted. Nothing is removed here.

How it fits together

The target pipeline is PSL text ─parse─▶ CST ─resolve─▶ ResolvedDocument ─▶ per-target interpreters. This slice builds the middle stage:

  1. Resolve the shape (commit 43e948349). Walk the CST once: bucket declarations into per-namespace name-keyed maps; resolve every type reference to a TypeTarget; follow named-type aliases (types { Email = String } → a namedType ref); record the two non-semantic diagnostics this stage owns (unresolved reference, duplicate-declaration collision). Non-throwing on malformed input — diagnostics accumulate, resolve always returns a document.
  2. Pin the resolution policy (commit 561f23c89). Bare-name resolution is current-namespace-first, then document-wide first-match; qualified ns.Type resolves to that namespace exactly. A regression test pins the current-namespace-first branch as distinct from the document-wide path (a same name declared in two namespaces, referenced from one).
  3. Relocate the semantic validators (commit a10a46c4b). Populate the extensionBlocks map; run descriptor-driven extension-block validation and named-type collision inside resolve, surfaced in ResolvedDocument.diagnostics. parse performs neither; the legacy validateExtensionBlock / parser.ts are left untouched (their deletion is a later slice). Reuses the new parser's CST directly — no CST→PslExtensionBlock adapter shim.

Behavior changes & evidence

  • New resolve + ResolvedDocument surface in src/resolve.ts, exported from src/exports/syntax.ts alongside the existing CST exports. parse and resolve are separate entry points. Whole-shape + duplicate-collision coverage in test/resolve.test.ts.
  • Semantic diagnostics now emit from resolve at parity — the five validateExtensionBlock failure modes (unknown/missing block, option-out-of-set, invalid-value incl. unknown-codec / non-JSON-literal / codec-rejection variants, unresolved-ref with same namespace / any namespace in the schema scope labels), PSL_EXTENSION_DUPLICATE_PARAMETER, and named-type collision (scalar/model/enum) — verified character-for-character (toBe) against the legacy validator + parser in test/resolve-validation.test.ts.
  • Two additive diagnostic codes (PSL_UNRESOLVED_TYPE_REFERENCE, PSL_DUPLICATE_DECLARATION) in the shared PslDiagnosticCode union (framework-components/src/shared/psl-extension-block.ts) — appended only, no existing code repurposed.

Reviewer notes

  • Largest diff is a10a46c4b (resolve.ts +358, validators + classification moved in). The new parser's CST is purely syntactic, so the param-kind classification (option/value/ref/list) that the old parser did at parse time now happens inside resolve against the descriptor — that is why resolve gained the optional ResolveOptions { pslBlockDescriptors?, codecLookup? } second argument. The signature stays backward-compatible (resolve(document) still works; codecLookup defaults to emptyCodecLookup, matching the legacy parser's own fallback).
  • Named-type collision is scalar/model/enum, not composite — deliberately. The legacy parser.ts builds compositeTypeNames but never reads it in the collision loop, so it emits no composite collision. Matching that exactly is the parity bar; adding a composite collision would invent a diagnostic the old parser never emitted.
  • Known divergence to reconcile at migration (not a bug here). resolve uses current-namespace-first bare-name resolution (the design-pinned policy); the live SQL interpreter's bare path is flat document-wide last-wins. They differ only when the same bare name is declared in two namespaces and referenced from one. With no consumer yet this is inert; it becomes the decision point under the migration slices' byte-identity check. Tracked in the project plan's open items.
  • Pre-existing: an info-level no-bare-cast at src/tokenizer.ts:69 (from feat(psl-parser): fault-tolerant recursive-descent parser (parse + SourceFile) under ./syntax #795, untouched) — lint exits 0.

Compatibility / migration / risk

Additive only. No existing export changed or removed; parse behavior unchanged; the legacy parser, validateExtensionBlock, and the printer/infer path are untouched. No consumer depends on resolve yet, so the divergence note above carries zero runtime risk in this PR.

Testing performed

  • pnpm --filter @prisma-next/psl-parser typecheck — pass
  • pnpm --filter @prisma-next/psl-parser test — pass (15 files, 455 tests; 22 in resolve.test.ts, 25 in resolve-validation.test.ts)
  • pnpm --filter @prisma-next/psl-parser lint — pass (one pre-existing info, exit 0)
  • pnpm lint:deps — pass (no cross-layer violation; ResolvedDocument stays in psl-parser)

Follow-ups

  • SQL + Mongo authoring interpreters migrate onto ResolvedDocument (separate slices); the bare-name divergence above is reconciled there under byte-identity.
  • Legacy parser.ts / parsePslDocument / ./parser export / validateExtensionBlock deleted once both interpreters are off them (the delete-legacy-parser slice).

Alternatives considered

  • Collapse parse + resolve into one call. Rejected — callers that only need syntax (round-trip, formatting) skip resolution; keeping them separate keeps parse purely syntactic and the stages independently callable.
  • A normalized ResolvedValue union for attribute args. Rejected — consumers already read raw args descriptor-driven, and the CST expression node carries the structured form; a normalized union would be unconsumed and, without the descriptor, often ambiguous.
  • Store direct object pointers instead of DeclCoord coordinates. Rejected — coordinates avoid a cyclic object graph (so nodes can be frozen) and mirror the existing entries[kind][name] / Contract Record<string, …> keying.
  • A CST→PslExtensionBlock adapter so the old validator runs unchanged. Rejected (brief constraint) — relocating the logic over the CST directly avoids a shim that would outlive its purpose.

Checklist

  • DCO sign-off on every commit
  • Tests added/updated (whole-shape, duplicate-collision, resolution-policy, full parity corpus)
  • Title in the TML-NNNN: convention
  • pnpm lint:deps clean

Summary by CodeRabbit

  • New Features

    • Enhanced validation system with detection for unresolved type references and duplicate declarations within scopes
    • Extended diagnostic capabilities for improved error reporting
  • Tests

    • Added comprehensive test coverage for type resolution and validation scenarios
    • Included tests for cross-namespace references and attribute resolution

SevInf and others added 3 commits June 12, 2026 16:02
Introduce a target-agnostic resolved view over the fault-tolerant CST: a
`resolve(document): ResolvedDocument` pass that walks the tree once and
returns per-namespace, name-keyed `ReadonlyMap` stores (models, enums,
composite types, extension blocks), a document-level `namedTypes` map, and a
diagnostics list. Every field/named-type reference resolves to a `TypeTarget`
(scalar / ref with a kind-ful `DeclCoord` / crossSpace carrying coordinates
but no DeclKind / constructor / unresolved). Declaration stores are
first-declaration-wins on duplicate, with a collision diagnostic; a dangling
reference yields an unresolved target plus a diagnostic. Cross-space
references are never flagged unresolved.

Attributes are exposed structurally as `ResolvedAttribute`, with arg values as
CST `ExpressionAst` nodes and positional/string accessors that subsume the old
`getPositionalArgument` / `parseQuotedStringLiteral` helpers. Each resolved
entity keeps a CST back-pointer (`syntax`).

`parse` and `resolve` stay separate exported entries; the pass is non-throwing
on malformed input. Adds two non-semantic diagnostic codes
(`PSL_UNRESOLVED_TYPE_REFERENCE`, `PSL_DUPLICATE_DECLARATION`).

Bare references resolve current-namespace-then-document; qualified `ns.Type`
references resolve against that namespace exactly, matching the live old
parser + interpreter resolution policy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Add a fixture where a same-named declaration exists in two namespaces and a
bare reference resolves to the one in its own namespace, distinguishing
current-namespace-first from document-wide first-match. Deleting the
current-namespace preference branch in resolve now fails this test.

Reword the NameTable JSDoc to state the chosen bare-name policy
(current-namespace-first, then document-wide first-match) without the
overstated live-interpreter-parity claim, and note that the live SQL
interpreter bare path is document-wide last-wins, to be reconciled at
interpreter migration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Move the two semantic PSL validators into the resolve pass at message/code
parity with the legacy parser/validator, computed directly over the resolved
CST shape (no PslExtensionBlock adapter shim):

- Extension-block validation (PSL_EXTENSION_* family): descriptor-driven over
  GenericBlockDeclaration / KeyValuePair entries, with codec lookup. Covers
  unknown parameter, missing-required, option-out-of-set, invalid-value
  (unknown codec / non-JSON literal / codec rejection), unresolved-ref, and
  first-occurrence-wins duplicate parameter. Populates the per-namespace
  extensionBlocks map left empty in the resolved-model dispatch.
- Named-type cross-kind collision (PSL_INVALID_TYPES_MEMBER): scalar / model /
  enum conflicts, preserving the legacy scalar->model->enum precedence and
  verbatim messages.

resolve now accepts ResolveOptions { pslBlockDescriptors, codecLookup }. The
legacy validateExtensionBlock / parser.ts stay untouched; parse remains purely
syntactic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
@SevInf SevInf requested a review from a team as a code owner June 12, 2026 16:33
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements a complete PSL authoring resolve pass that constructs resolved document models from parse trees, validates extension blocks and type references, detects duplicate declarations and collisions, and resolves type annotations into structured targets across namespaces and spaces.

Changes

PSL Resolve Pass Implementation

Layer / File(s) Summary
Diagnostic Codes and Public API
packages/1-framework/1-core/framework-components/src/shared/psl-extension-block.ts, packages/1-framework/2-authoring/psl-parser/src/exports/syntax.ts
Adds PSL_UNRESOLVED_TYPE_REFERENCE and PSL_DUPLICATE_DECLARATION diagnostic codes, and exports the complete resolved-type API contract including ResolvedDocument, ResolvedNamespace, ResolvedModel, TypeTarget, and the resolve function.
Core Resolver and Type Resolution
packages/1-framework/2-authoring/psl-parser/src/resolve.ts (imports, types, Resolver class)
Defines resolved structure types for fields/models/enums/named types/namespaces, implements the Resolver class for type-target resolution with diagnostic tracking, and provides helpers for normalizing field attributes and arguments into structured ResolvedAttribute objects with positional and string-literal accessors.
Document Orchestration and Name Registration
packages/1-framework/2-authoring/psl-parser/src/resolve.ts (resolve, registerNames, buildNamespace, buildFields)
Implements the main resolve() function that orchestrates source reconstruction, namespace/type building, and name registration with duplicate detection; populates name-kind maps to support bare-name resolution while emitting PSL_DUPLICATE_DECLARATION diagnostics; constructs models, enums, and composite types with validated extension blocks.
Validation: Named-Type Collisions and Extension Blocks
packages/1-framework/2-authoring/psl-parser/src/resolve.ts (named-type validation, extension validation, text helpers)
Validates named-type cross-kind collisions against scalars/models/enums emitting PSL_INVALID_TYPES_MEMBER; implements descriptor-driven extension block validation with parameter kind-specific checks (option sets, JSON/codec decode, ref scope rules, list recursion), duplicate-parameter first-occurrence wins, and required-parameter enforcement.
Validation Test Suite
packages/1-framework/2-authoring/psl-parser/test/resolve-validation.test.ts
Tests extension block validation diagnostics with stub codecs covering unknown/duplicate parameters, missing required parameters, option validation, JSON parsing, codec failures, ref scope resolution, and named-type collision detection across multiple conflict scenarios.
Resolve Pipeline Tests
packages/1-framework/2-authoring/psl-parser/test/resolve.test.ts
Comprehensive tests for the full resolve pipeline: namespace/model/enum/type insertion order, field-to-target resolution for scalars/aliases/cross-namespace/cross-space/constructor types, bare-name precedence rules, attribute accessor APIs, duplicate declaration collision behavior with correct reference resolution to surviving declarations, and resilience to malformed/empty input.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A resolver hops through schemas bright,
Linking types and refs in structured flight,
With attributes named and targets clear,
Duplicates caught and collisions dear!
Extension blocks pass their rightful tests—
The resolve pass gives the parser rest.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% 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 summarizes the main objective of the PR: implementing a resolution pass and ResolvedDocument semantic layer over the CST, which are the core contributions evident in resolve.ts and the associated changes.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch psl-parser-replace

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 and usage tips.

@pkg-pr-new

pkg-pr-new Bot commented Jun 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

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

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/extension-supabase

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/emitter

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: a10a46c

@github-actions

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 155.12 KB (0%)
postgres / emit 122.41 KB (0%)
mongo / no-emit 76.92 KB (0%)
mongo / emit 71.01 KB (0%)
cf-worker / no-emit 183.82 KB (0%)
cf-worker / emit 147.7 KB (0%)

@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 (2)
packages/1-framework/2-authoring/psl-parser/test/resolve.test.ts (1)

175-180: ⚡ Quick win

Make the cross-space non-unresolved assertion precise.

The current check (message not containing "User") is indirect and brittle. It can fail on unrelated diagnostics and doesn’t directly verify unresolved-type behavior.

Suggested tightening
     it('does not flag the cross-space reference as unresolved', () => {
       const doc = resolveSource(source);
-      for (const diagnostic of doc.diagnostics) {
-        expect(diagnostic.message).not.toContain('User');
-      }
+      const unresolvedMessages = doc.diagnostics
+        .filter((d) => d.code === 'PSL_UNRESOLVED_TYPE_REFERENCE')
+        .map((d) => d.message);
+      expect(unresolvedMessages).toEqual(['Type "Mystery" does not resolve to a known declaration']);
     });
🤖 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/test/resolve.test.ts` around
lines 175 - 180, The test currently checks doc.diagnostics for messages not
containing "User", which is indirect and brittle; replace it with a precise
assertion that no diagnostic exists that indicates an unresolved cross-space
reference to "User" by using resolveSource(...) and asserting that
doc.diagnostics does not contain any entry where diagnostic.code (or
diagnostic.message if code is unavailable) matches the unresolved-reference
identifier (e.g., 'UNRESOLVED_REFERENCE' or message.includes('unresolved') ) and
the diagnostic.message includes "User"; update the it block named 'does not flag
the cross-space reference as unresolved' to explicitly check
doc.diagnostics.some(...) === false for that specific unresolved diagnostic
instead of the broad not-toContain string check.
packages/1-framework/2-authoring/psl-parser/test/resolve-validation.test.ts (1)

454-473: ⚡ Quick win

Assert the “first occurrence wins” behavior explicitly.

Line 454’s test title claims first-occurrence semantics, but the assertions currently only verify the duplicate-parameter diagnostic. Both using values are valid strings, so a regression to “last wins” would still pass this test.

Suggested tightening
 policy_select ReadPosts {
   target = Post
   roles = []
   using = "first"
-  using = "second"
+  using = 42
 }
 `;
       const diagnostic = diagnosticsFor(source).find(
         (d) => d.code === 'PSL_EXTENSION_DUPLICATE_PARAMETER',
       );
       expect(diagnostic?.message).toBe(
         'Duplicate parameter "using" in "policy_select" block "ReadPosts"; first occurrence wins',
       );
+      expect(diagnosticsFor(source).some((d) => d.code === 'PSL_EXTENSION_INVALID_VALUE')).toBe(
+        false,
+      );
🤖 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/test/resolve-validation.test.ts`
around lines 454 - 473, The test currently only checks for the
PSL_EXTENSION_DUPLICATE_PARAMETER diagnostic but doesn't assert which value is
retained; update the test (which already uses diagnosticsFor) to also
parse/resolve the source and locate the policy_select block "ReadPosts" and
assert that its resolved using parameter is "first" (i.e., first occurrence
wins), e.g., retrieve the parsed/validated policy_select node for "ReadPosts"
and expect its using value to equal "first".
🤖 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/test/resolve-validation.test.ts`:
- Around line 454-473: The test currently only checks for the
PSL_EXTENSION_DUPLICATE_PARAMETER diagnostic but doesn't assert which value is
retained; update the test (which already uses diagnosticsFor) to also
parse/resolve the source and locate the policy_select block "ReadPosts" and
assert that its resolved using parameter is "first" (i.e., first occurrence
wins), e.g., retrieve the parsed/validated policy_select node for "ReadPosts"
and expect its using value to equal "first".

In `@packages/1-framework/2-authoring/psl-parser/test/resolve.test.ts`:
- Around line 175-180: The test currently checks doc.diagnostics for messages
not containing "User", which is indirect and brittle; replace it with a precise
assertion that no diagnostic exists that indicates an unresolved cross-space
reference to "User" by using resolveSource(...) and asserting that
doc.diagnostics does not contain any entry where diagnostic.code (or
diagnostic.message if code is unavailable) matches the unresolved-reference
identifier (e.g., 'UNRESOLVED_REFERENCE' or message.includes('unresolved') ) and
the diagnostic.message includes "User"; update the it block named 'does not flag
the cross-space reference as unresolved' to explicitly check
doc.diagnostics.some(...) === false for that specific unresolved diagnostic
instead of the broad not-toContain string check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 6d4ff9a7-672f-489a-b524-2135741fa9c4

📥 Commits

Reviewing files that changed from the base of the PR and between 3c96875 and a10a46c.

📒 Files selected for processing (5)
  • packages/1-framework/1-core/framework-components/src/shared/psl-extension-block.ts
  • packages/1-framework/2-authoring/psl-parser/src/exports/syntax.ts
  • packages/1-framework/2-authoring/psl-parser/src/resolve.ts
  • packages/1-framework/2-authoring/psl-parser/test/resolve-validation.test.ts
  • packages/1-framework/2-authoring/psl-parser/test/resolve.test.ts

@SevInf SevInf closed this Jun 12, 2026
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