Skip to content

feat(plugin-kysely): validate type annotations on embedded sql fragments#503

Merged
Newbie012 merged 1 commit into
mainfrom
feat/kysely-embedded-fragment-types
Jun 29, 2026
Merged

feat(plugin-kysely): validate type annotations on embedded sql fragments#503
Newbie012 merged 1 commit into
mainfrom
feat/kysely-embedded-fragment-types

Conversation

@Newbie012

@Newbie012 Newbie012 commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

Validates the <T> annotation on raw sql fragments embedded in Kysely builder chains, with an autofix on a mismatch.

  • Selections (sql<T>....as("alias")) check against the column type.
  • where-like conditions check against boolean (both SqlBool and boolean accepted).

Adds a deferred typeCheck hook to ResolvedQuery in plugin-utils, rendered through the rule's standard incorrect-annotation report. Playground demo changes are left out of this PR.

Summary by CodeRabbit

  • New Features
    • Expanded SafeQL validation for typed Kysely sql<T> fragments inside builder chains, including selection aliases and where-style boolean conditions, with support for wrapped/parenthesized forms.
    • Added plugin-provided per-query typeCheck support to power deferred typed fragment validation.
  • Bug Fixes
    • Improved mismatch detection and reporting for boolean-like annotations (SqlBool/boolean), with more reliable expected vs. actual type comparisons and corrections.
  • Documentation
    • Updated SafeQL documentation with clarified sql<T> validation behavior and examples.
  • Tests
    • Added more integration coverage for valid and invalid typed fragments, including multi-issue reporting.
  • Chores
    • Refreshed package versions and adjusted lint task execution order.

@changeset-bot

changeset-bot Bot commented Jun 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d1e9766

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 22 packages
Name Type
@ts-safeql/plugin-kysely Minor
@ts-safeql/plugin-utils Minor
@ts-safeql/eslint-plugin Patch
@ts-safeql-demos/plugin-kysely Patch
@ts-safeql/connection-manager Patch
@ts-safeql/zod-annotator Patch
@ts-safeql/plugin-auth-aws Patch
@ts-safeql-demos/basic-flat-config Patch
@ts-safeql-demos/basic-migrations-raw Patch
@ts-safeql-demos/basic-transform-type Patch
@ts-safeql-demos/basic Patch
@ts-safeql-demos/big-project Patch
@ts-safeql-demos/config-file-flat-config Patch
@ts-safeql-demos/from-config-file Patch
@ts-safeql-demos/multi-connections Patch
@ts-safeql-demos/playground Patch
@ts-safeql-demos/plugin-aws-iam Patch
@ts-safeql-demos/plugin-slonik Patch
@ts-safeql-demos/postgresjs-custom-types Patch
@ts-safeql-demos/postgresjs-demo Patch
@ts-safeql-demos/vercel-postgres Patch
@ts-safeql/generate Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jun 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
safeql Ignored Ignored Preview Jun 28, 2026 11:12am

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c07ebf55-5a74-4503-9022-1d5590747d65

📥 Commits

Reviewing files that changed from the base of the PR and between 68950b7 and d1e9766.

📒 Files selected for processing (9)
  • .changeset/kysely-embedded-fragment-types.md
  • demos/plugin-kysely/src/builder.ts
  • packages/eslint-plugin/src/rules/check-sql.rule.ts
  • packages/plugin-utils/src/index.contract.test.ts
  • packages/plugin-utils/src/index.ts
  • packages/plugins/kysely/src/builder-type-check.ts
  • packages/plugins/kysely/src/plugin.integration.test.ts
  • packages/plugins/kysely/src/plugin.ts
  • turbo.json
✅ Files skipped from review due to trivial changes (1)
  • .changeset/kysely-embedded-fragment-types.md
🚧 Files skipped from review as they are similar to previous changes (8)
  • demos/plugin-kysely/src/builder.ts
  • turbo.json
  • packages/plugin-utils/src/index.contract.test.ts
  • packages/plugins/kysely/src/plugin.integration.test.ts
  • packages/plugin-utils/src/index.ts
  • packages/plugins/kysely/src/builder-type-check.ts
  • packages/eslint-plugin/src/rules/check-sql.rule.ts
  • packages/plugins/kysely/src/plugin.ts

📝 Walkthrough

Walkthrough

The PR adds validation for typed raw sql<T> fragments inside Kysely builder chains. plugin-utils gains shared type-check contracts and an optional typeCheck hook on ResolvedQuery. The Kysely plugin classifies typed fragments during rendering and attaches a callback to resolved queries. The check-sql rule forwards and invokes the hook, then reports structured mismatch results. Integration tests, a demo annotation, a changeset, and lint task config were updated.

Possibly related PRs

  • ts-safeql/safeql#452: Extends the same plugin-driven type-check pipeline with ResolvedQueryTypeCheckContext and typeCheck handling in check-sql.
  • ts-safeql/safeql#491: Introduces the Kysely plugin path that this PR extends with typed fragment classification and deferred type checking.
  • ts-safeql/safeql#490: Shares the rule-level resolveQueryFromPlugins flow that now forwards per-query typeCheck callbacks.

Poem

🐇 I hopped through sql<T> with careful feet,
And sniffed each fragment until types matched sweet.
A boolean here, a string there too—
Now every alias knows what to do.
Boing! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.35% 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 clearly matches the main change: validating type annotations on embedded Kysely sql fragments.
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 feat/kysely-embedded-fragment-types

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

demos/plugin-kysely/src/builder.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.


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.

@github-actions

github-actions Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

⚪️ generate() performance

metric base PR Δ
msPerQueryDistinct (gate) 0.4351 0.4271 -1.8%
msPerQueryRepeated 0.0055 0.0052 -5.5%

msPerQueryDistinct -1.8% vs base — within noise.

⚪️ check-sql lint-rule overhead

metric base PR Δ
msTotal 306 300 -2.0%
msRuleOverhead (gate) 179 172 -3.9%

msRuleOverhead -3.9% vs base — within noise.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 @.changeset/kysely-embedded-fragment-types.md:
- Line 9: The changeset text uses inline code with nested backticks, which
triggers markdownlint MD038. Update the affected examples in the prose around
the SafeQL description to avoid nested single-backtick formatting by using
double backticks or equivalent rewritten wording, and keep the content readable
while preserving the same examples.

In `@packages/plugins/kysely/src/plugin.ts`:
- Around line 821-833: The direct-argument check in the where-like method
handling is too broad and treats binary predicate operands as full boolean
conditions. Update the logic around the current parent.arguments / callee name
check so it only classifies actual condition expressions, not operands in forms
like where(column, operator, value). Use the existing whereLikeMethods and the
surrounding AST unwrap logic to distinguish single-argument boolean predicates
from multi-argument binary where calls, and avoid autofixing non-boolean sql
fragments such as sql<number> to sql<boolean>.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c882f882-14d7-4d52-8963-acc957b89610

📥 Commits

Reviewing files that changed from the base of the PR and between 6b79f06 and 7f87598.

📒 Files selected for processing (7)
  • .changeset/kysely-embedded-fragment-types.md
  • packages/eslint-plugin/src/rules/check-sql.rule.ts
  • packages/plugin-utils/src/index.contract.test.ts
  • packages/plugin-utils/src/index.ts
  • packages/plugins/kysely/src/builder-type-check.ts
  • packages/plugins/kysely/src/plugin.integration.test.ts
  • packages/plugins/kysely/src/plugin.ts

Comment thread .changeset/kysely-embedded-fragment-types.md Outdated
Comment thread packages/plugins/kysely/src/plugin.ts Outdated
@greptile-apps

greptile-apps Bot commented Jun 28, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds deferred typeCheck support to ResolvedQuery in plugin-utils, which the Kysely plugin uses to validate user-written sql<T> annotations on embedded fragments against the types the database actually returns — with an autofix on mismatch.

  • builder-type-check.ts (new): classifies fragments as "column" (.as("alias") selections) or "condition" (single-arg .where()-like calls), looks up the expected DB type from the resolved output, uses TypeScript type flags to collapse boolean-ish annotations (boolean, SqlBool, branded intersections) to a shared BOOLEAN target, and reports IncorrectTypeAnnotationReport for every mismatch in one pass.
  • plugin.ts: adds classifyTypedFragment / climbTransparentWrappers / isConditionFragment / getSelectionAlias to collect typed fragments while rendering the builder chain, and attaches createBuilderTypeCheck to the resolved query when any are found.
  • check-sql.rule.ts: introduces reportResolvedQueryTypeCheck to invoke the deferred typeCheck on the resolved output and dispatch each IncorrectTypeAnnotationReport through the existing reportIncorrectTypeAnnotations path.

Confidence Score: 5/5

Safe to merge; the new deferred typeCheck path is well-isolated and all three changed packages have integration tests covering valid/invalid fragment cases including multi-error reporting.

The feature is additive: the typeCheck hook is only invoked when the plugin explicitly provides it, the core rule falls back to its existing behavior otherwise, and the boolean-unification logic uses stable TypeScript type flags rather than string matching. Integration tests cover parenthesized fragments, binary-where operands, SqlBool/boolean, and multi-error queries. The only finding is a stale return-type annotation on resolveBuilderQuery that does not affect correctness.

packages/plugins/kysely/src/plugin.ts — the resolveBuilderQuery return type annotation no longer includes typeCheck? and should be updated to ResolvedQuery | "skip".

Important Files Changed

Filename Overview
packages/plugins/kysely/src/builder-type-check.ts New file implementing deferred type checking for embedded sql fragments; correctly handles column and condition kinds, with a sound boolean-flag check; reports all mismatches in one pass.
packages/plugins/kysely/src/plugin.ts Adds classifyTypedFragment / climbTransparentWrappers / isConditionFragment / getSelectionAlias helpers; stores typed fragments in RenderState and attaches createBuilderTypeCheck to the resolved query; return type annotation of resolveBuilderQuery is stale (does not declare typeCheck?).
packages/eslint-plugin/src/rules/check-sql.rule.ts Adds reportResolvedQueryTypeCheck and reportTypeCheckResult dispatchers; correctly threads overrideQuery.typeCheck into the deferred check path; strips typeCheck from the query binding to avoid double-invocation.
packages/plugin-utils/src/index.ts Exports ResolvedQueryTypeCheckContext, IncorrectTypeAnnotationReport, and ResolvedQueryTypeCheckResult; adds optional typeCheck to ResolvedQuery interface.
packages/plugins/kysely/src/plugin.integration.test.ts Adds valid cases (SqlBool, boolean, binary-where operand) and invalid cases (wrong column type, wrong where type, parenthesized fragments, multi-error reporting); good coverage of the new feature paths.
packages/plugin-utils/src/index.contract.test.ts Extends the contract test to include ResolvedQueryTypeCheckContext; ensures the public interface shape compiles correctly.
demos/plugin-kysely/src/builder.ts Updates count(*) annotation from number to string, reflecting that SafeQL maps PostgreSQL bigint to string; validates the autofix the new feature produces.
turbo.json Adds dependsOn: ["^build"] to the lint task so linked packages are fully built before lint runs; correct fix for lint-time type errors on local package imports.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Rule as check-sql.rule
    participant Plugin as plugin-kysely
    participant BTCheck as builder-type-check
    participant DB as SafeQL DB

    Rule->>Plugin: resolveQuery(context)
    Plugin->>Plugin: buildBuilderCompileText() — classifyTypedFragment()
    Plugin->>Plugin: state.typedFragments.push(fragment)
    Plugin->>Rule: "ResolvedQuery { text, sourcemaps, typeCheck? }"

    Rule->>DB: generateSyncE(query)
    DB->>Rule: result.output (resolved column types)

    alt tag is CallExpression without type arg
        Rule->>Rule: reportResolvedQueryTypeCheck()
        Rule->>BTCheck: typeCheck(ctx) with output + checker
        BTCheck->>BTCheck: checkFragment() per fragment
        BTCheck->>BTCheck: actualTypeOf() — look up alias in output
        BTCheck->>BTCheck: expectedTypeOf() — resolveExpectedType or BOOLEAN
        BTCheck->>BTCheck: isComparablyEqual()
        BTCheck->>Rule: IncorrectTypeAnnotationReport[]
        Rule->>Rule: reportTypeCheckResult() → reportIncorrectTypeAnnotations()
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Rule as check-sql.rule
    participant Plugin as plugin-kysely
    participant BTCheck as builder-type-check
    participant DB as SafeQL DB

    Rule->>Plugin: resolveQuery(context)
    Plugin->>Plugin: buildBuilderCompileText() — classifyTypedFragment()
    Plugin->>Plugin: state.typedFragments.push(fragment)
    Plugin->>Rule: "ResolvedQuery { text, sourcemaps, typeCheck? }"

    Rule->>DB: generateSyncE(query)
    DB->>Rule: result.output (resolved column types)

    alt tag is CallExpression without type arg
        Rule->>Rule: reportResolvedQueryTypeCheck()
        Rule->>BTCheck: typeCheck(ctx) with output + checker
        BTCheck->>BTCheck: checkFragment() per fragment
        BTCheck->>BTCheck: actualTypeOf() — look up alias in output
        BTCheck->>BTCheck: expectedTypeOf() — resolveExpectedType or BOOLEAN
        BTCheck->>BTCheck: isComparablyEqual()
        BTCheck->>Rule: IncorrectTypeAnnotationReport[]
        Rule->>Rule: reportTypeCheckResult() → reportIncorrectTypeAnnotations()
    end
Loading

Reviews (3): Last reviewed commit: "feat(plugin-kysely): validate type annot..." | Re-trigger Greptile

Comment thread packages/plugins/kysely/src/builder-type-check.ts Outdated
Comment thread packages/plugins/kysely/src/builder-type-check.ts Outdated
Comment thread packages/plugins/kysely/src/builder-type-check.ts
Comment thread packages/eslint-plugin/src/rules/check-sql.rule.ts Outdated
@Newbie012 Newbie012 force-pushed the feat/kysely-embedded-fragment-types branch 3 times, most recently from 485dacd to 26256a0 Compare June 28, 2026 08:16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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/plugins/kysely/src/builder-type-check.ts`:
- Around line 95-100: The boolean type check in isBooleanType currently relies
on checker.typeToString(type) === "SqlBool", which misses aliased forms like
type MyBool = SqlBool. Update isBooleanType in builder-type-check.ts to inspect
the underlying symbol/type via the TypeChecker instead of matching the printed
name, and keep the existing boolean/union/intersection handling intact so
aliased SqlBool annotations are recognized correctly.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 04797fd0-7fc6-4a78-82ea-2cff1beb7596

📥 Commits

Reviewing files that changed from the base of the PR and between 485dacd and 26256a0.

📒 Files selected for processing (8)
  • .changeset/kysely-embedded-fragment-types.md
  • demos/plugin-kysely/src/builder.ts
  • packages/eslint-plugin/src/rules/check-sql.rule.ts
  • packages/plugin-utils/src/index.contract.test.ts
  • packages/plugin-utils/src/index.ts
  • packages/plugins/kysely/src/builder-type-check.ts
  • packages/plugins/kysely/src/plugin.integration.test.ts
  • packages/plugins/kysely/src/plugin.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/kysely-embedded-fragment-types.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • demos/plugin-kysely/src/builder.ts
  • packages/plugin-utils/src/index.contract.test.ts
  • packages/plugins/kysely/src/plugin.integration.test.ts
  • packages/plugin-utils/src/index.ts
  • packages/eslint-plugin/src/rules/check-sql.rule.ts
  • packages/plugins/kysely/src/plugin.ts

Comment thread packages/plugins/kysely/src/builder-type-check.ts Outdated
@Newbie012 Newbie012 force-pushed the feat/kysely-embedded-fragment-types branch 2 times, most recently from 5c35ef8 to 68950b7 Compare June 28, 2026 10:14
Check the `<T>` a user writes on a raw `sql` fragment embedded in a Kysely
builder chain against the type the database returns, and autofix it on a
mismatch. Selections (`sql<T>`...`.as("alias")`) compare against the column
type; `where`-like conditions compare against `boolean` (accepting both
`SqlBool` and `boolean`). A query reports one result per bad fragment.

Adds a deferred `typeCheck` hook to `ResolvedQuery` in plugin-utils so a
plugin can validate annotations once the query's row type is known, and
wires the eslint rule to dispatch it through the standard
incorrect-annotation report and autofix.

Also makes the turbo `lint` task depend on `^build` so a change to the
eslint-plugin rule busts the cache of every package that lints against it
(the demos), instead of replaying a stale pass.
@Newbie012 Newbie012 force-pushed the feat/kysely-embedded-fragment-types branch from 68950b7 to d1e9766 Compare June 28, 2026 11:12
@Newbie012 Newbie012 merged commit 09fcf97 into main Jun 29, 2026
7 checks passed
@Newbie012 Newbie012 deleted the feat/kysely-embedded-fragment-types branch June 29, 2026 07:43
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