feat(ACT-4873): add require-zod-import-in-contracts ESLint rule#696
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughThis PR introduces a new ESLint rule ChangesRequire Zod import in contract files
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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
packages/eslint-config/src/index.jsESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. packages/eslint-plugin/src/index.tsESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. packages/eslint-plugin/src/lib/rules/require-zod-import-in-contracts/index.test.tsESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.
Comment |
|
View your CI Pipeline Execution ↗ for commit 8e3127e
☁️ Nx Cloud last updated this comment at |
New ESLint rule that requires every `*.contract.ts` file to reference
"zod" in its own module scope -- either via an import (named, default,
namespace, or side-effect) or via a re-export.
Prevents the silent type-collapse regression documented in ACT-4870:
when a contract file infers zod-typed schemas from sibling files but
doesn't reference zod in its own scope, TypeScript's declaration emit
synthesizes inline imports as `import("node_modules/zod/index.cjs")`.
That malformed specifier fails to resolve for downstream consumers,
so every type that references it silently collapses to `any` --
without any `tsc` error. The fix is to give TS a clean "zod"
specifier in the source-level scope of the file being emitted.
Auto-applies to all `*.contract.ts` files via `@clipboard-health/eslint-config`,
mirroring how `require-http-module-factory` is auto-applied to `*.module.ts`.
47e692a to
8e3127e
Compare
Summary
New ESLint rule
@clipboard-health/require-zod-import-in-contractsthat requires every*.contract.tsfile to reference"zod"in its own module scope — either via animport(named, default, namespace, or side-effect) or via a re-export (export { z } from "zod",export * from "zod").Auto-applies to all
*.contract.tsfiles via@clipboard-health/eslint-config, mirroring howrequire-http-module-factoryis applied to*.module.ts.Why
Prevents the silent type-safety regression documented in ACT-4870 (clipboard-health#25570).
When a contract file infers zod-typed schemas from sibling files but doesn't reference zod in its own scope, TypeScript's declaration emit can't reuse a clean
"zod"specifier and falls back to synthesizing inline imports asimport("node_modules/zod/index.cjs").ZodObject<...>. That malformed module specifier fails to resolve for downstream consumers, so every type that references it silently collapses toany— andtscdoes not error.It happened once already (
@clipboard-health/contract-backend-main'sshiftV3.contract.tsshipped 441 leaked paths in v60.15.6, silently breakingshiftClient.list()'s response type forworker-app-bff). This rule prevents the next occurrence.What's in the PR
packages/eslint-plugin/src/lib/rules/require-zod-import-in-contracts/index.ts— rule implementation. VisitsProgramonce, checks for anyImportDeclaration/ExportAllDeclaration/ExportNamedDeclarationwithsource.value === "zod". Reports on the Program node if none found.packages/eslint-plugin/src/lib/rules/require-zod-import-in-contracts/index.test.ts— vitest tests covering 6 valid forms (named import, default import, namespace import,export { z } from "zod",export * from "zod", side-effect import) and 3 invalid forms (the shiftV3 case, no zod at all, similarly-named-but-not-zod package).packages/eslint-plugin/src/lib/rules/require-zod-import-in-contracts/README.md— explains the underlying TypeScript declaration-emit pitfall, why the rule exists, and shows both the regular import form and theexport { z } from "zod"workaround for files that compose schemas from sibling files only.packages/eslint-plugin/src/index.ts— registers the rule alongside existing rules.packages/eslint-config/src/index.js— auto-applies the rule to**/*.contract.ts.Verification
nx test eslint-plugin: all 42 tests pass (33 existing + 9 new).nx lint eslint-plugin: clean.shiftV3.contract.tssource fromclipboard-healthchecked out at../clipboard-health. With the currentexport { z } from "zod"line present → no error. With that line stripped (the pre-fix state that shipped v60.15.6) → firesmissingZodReference. (Temporary verification test was deleted before commit.)Test plan
@clipboard-health/eslint-plugin+@clipboard-health/eslint-configare republished, follow up by bumping both inclipboard-health(and any other consumer) and verifying the rule is active. The rule should produce no errors against current*.contract.tsfiles inclipboard-health/packages/contract-backend-main/since they all already have a zod import —shiftV3.contract.tsis the only file that previously didn't, and ACT-4870 fixed it.Linear: ACT-4873
Posted by Claude Code