Skip to content

feat(mock): emit strict Mock return types when required and nonNullable are both true#3529

Merged
melloware merged 11 commits into
orval-labs:masterfrom
Hypenate:feat/mock-strict-types-3525
Jun 4, 2026
Merged

feat(mock): emit strict Mock return types when required and nonNullable are both true#3529
melloware merged 11 commits into
orval-labs:masterfrom
Hypenate:feat/mock-strict-types-3525

Conversation

@Hypenate
Copy link
Copy Markdown
Contributor

@Hypenate Hypenate commented Jun 2, 2026

Summary

  • When override.mock.required and override.mock.nonNullable are both enabled, faker schema factories, operation response mocks, and MSW get*ResponseMock helpers emit a strict {Schema}Mock base type (Required keys + NonNullable property types).
  • Overridable factories are generic so return types narrow when overrides include null:
    • getPetMock() → complete PetMock (tag: string)
    • getPetMock({ tag: null })tag: string | null
    • getPetMock({ tag: 'abc' })tag: string
  • overrideResponse stays typed as Partial<T> (OpenAPI model) so all legitimate API-shaped overrides remain valid.
  • getMockWithoutFunc now forwards nonNullable so operation/response mocks honor the flag at runtime.

Generated signature (strict mode)

export type KeysWithNull<O> = { /* ... */ };
export type MockWithNullableOverrides<T, O extends Partial<T>, M> = { /* ... */ };
export type PetMock = { [K in keyof Required<Pet>]: NonNullable<Required<Pet>[K]>; };

export const getPetMock = <O extends Partial<Pet> = {}>(
  overrideResponse?: O,
): MockWithNullableOverrides<Pet, O, PetMock> => ({ ... }) as MockWithNullableOverrides<Pet, O, PetMock>;

Test plan

  • Unit tests for mock-types helpers and strict mock generation in faker/index.test.ts and msw/index.test.ts
  • Snapshot fixtures for OpenAPI 3.0 and 3.1 nullable optional schemas (issue-3525, issue-3525-oas31)
  • bun test in packages/mock passes (209 tests)

Fixes #3525

Summary by CodeRabbit

  • New Features
    • Strict mock type generation for mock outputs (faker + MSW): non-nullable required fields in returned mock types while still supporting typed partial/nullable overrides; new nullable-override helper types and strict mock aliases are emitted in generated code.
  • Tests
    • Added suites validating strict-mock behavior, factory signatures, and generated return types under strict flags.
  • Chores
    • Added generator configs, specs and updated snapshots to cover strict-mock scenarios.

…lable are both true

When override.mock.required and override.mock.nonNullable are both enabled,
schema and operation mock factories now return a strict Mock alias while
keeping Partial<T> overrides (including null). Also forwards nonNullable
through getMockWithoutFunc for operation response mocks.

Fixes orval-labs#3525

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

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 strict-mock detection and formatting helpers, uses them to compute and emit typed factory signatures for faker and MSW generators (including array-item and oneOf helpers), forwards nonNullable for operation mocks, and adds tests, configs, specs, and generated snapshots for OpenAPI 3.0 and 3.1.

Changes

Strict Mock Type System and Integration

Layer / File(s) Summary
Strict mock type utilities and tests
packages/mock/src/mock-types.ts, packages/mock/src/mock-types.test.ts
New utilities detect strict mock mode, derive XMock type names, generate helper type declarations (KeysWithNull, MockWithNullableOverrides, XMock), compute factory signature parts (param, returnType, returnCast), extract schema names from responses, and rewrite return-type strings. Unit tests exercise detection, formatting, and edge cases.
Faker generator integration and array-item factories
packages/mock/src/faker/index.ts, packages/mock/src/faker/getters/array-item-factory.ts, packages/mock/src/faker/resolvers/value.ts, packages/mock/src/faker/index.test.ts
Faker schema and response factory generation now imports signature helpers and formats exported factory declarations using computed param, returnType, and returnCast. Array-item factory and oneOf response helpers use the shared signature logic; tests validate generated PetMock and factory signatures.
MSW generation integration and forwarding nonNullable
packages/mock/src/msw/index.ts, packages/mock/src/msw/mocks.ts, packages/mock/src/msw/index.test.ts
MSW generateDefinition derives schema names from responses, generates strict helper/type declaration blocks, rewrites response mock factory return types when strict flags are set, prefixes generated output with helper declarations, and forwards nonNullable into operation mock options. Tests assert strict vs. non-strict emitted types.
OpenAPI test specs and Orval configs
tests/specifications/issue-3525.yaml, tests/specifications/issue-3525-oas31.yaml, tests/configs/mock.config.ts
Adds OAS 3.0 and 3.1 specs and two Orval mock generator configs that enable required: true and nonNullable: true for snapshot generation.
Generated snapshots (OpenAPI 3.0 & 3.1)
tests/__snapshots__/mock/issue-3525/..., tests/__snapshots__/mock/issue-3525-oas31/...
Generated faker and MSW snapshot outputs demonstrating helper type declarations, PetMock strict alias, typed getPetMock/getGetPetResponseMock factories, MSW handlers, and convenience exports for both OAS versions.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as Orval Generator
  participant MockTypes as mock-types
  participant Formatter as formatMockFactoryDeclaration
  CLI->>MockTypes: getMockFactorySignatureParts(typeName, mockOptions, options)
  MockTypes->>Formatter: param, returnType, returnCast
  Formatter->>CLI: formatted export const ... declaration
  CLI->>FileSystem: emit factory + strict helper blocks
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

msw

Suggested reviewers

  • melloware

Poem

🐰 I hopped through types and tests tonight,
PetMock tidy, defaults set right.
Generators hum, helpers align,
Overrides still let null cross the line.
A small carrot of strict typing — bright.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% 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 reflects the main feature: emitting strict Mock return types when both required and nonNullable flags are enabled, which is the primary change across all modified files.
Linked Issues check ✅ Passed The PR addresses all acceptance criteria from #3525: getMockWithoutFunc forwards nonNullable, strict PetMock types generated, overrideResponse remains Partial, signatures unchanged when flags unset, MSW factories updated, and comprehensive tests/snapshots added.
Out of Scope Changes check ✅ Passed All changes are scoped to the strict mock feature: new mock-types module, updates to faker/MSW generators, helper types (KeysWithNull, MockWithNullableOverrides), test additions, and snapshot fixtures for issue #3525. No unrelated modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Hypenate and others added 2 commits June 2, 2026 19:30
Use generic MockWithNullableOverrides so getPetMock() returns a complete
PetMock, while getPetMock({ tag: null }) correctly types tag as string | null.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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/mock/src/faker/index.test.ts`:
- Line 200: The inline literal assigned to `context` is being cast directly to
`GeneratorOptions['context']` which triggers TS2352; change the cast to go
through `unknown` like the `baseOptions` pattern (i.e., cast `context` first to
`unknown` and then to `GeneratorOptions['context']`) so the type assertion is
accepted; update the cast at the `context` assignment where
`GeneratorOptions['context']` is used to mirror the `baseOptions` double-cast
approach.

In `@packages/mock/src/mock-types.test.ts`:
- Around line 13-87: Add a unit test for getStrictMockTypeDeclarations that
verifies it deduplicates and bulk-generates mock type aliases: call
getStrictMockTypeDeclarations with an array containing duplicate schema names
(e.g., 'Pet', 'Pet', 'Error[]') and assert the returned string contains exactly
one declaration per unique base name (e.g., the same text produced by
getStrictMockTypeDeclaration('Pet') and getStrictMockTypeDeclaration('Error')
and not duplicated) and that declarations are joined in the expected output
ordering/format.
- Around line 62-80: Add tests for getSchemaTypeNamesFromResponses that cover
the two defensive paths: one test where a response import uses an alias (set
import.alias to a different string and ensure the alias is returned instead of
import.name) and another test where a response has a falsy value (value:
undefined or empty string) to confirm it is skipped; reference the
ResReqTypesValue shape and the getSchemaTypeNamesFromResponses function when
adding these cases to mock-types.test.ts.

In `@packages/mock/src/msw/index.ts`:
- Around line 20-26: Remove the unused import getMockFactoryReturnType from the
import list in the MSW mock module; update the import statement that currently
includes getMockFactoryReturnType alongside applyStrictMockReturnType,
getSchemaTypeNamesFromResponses, getStrictMockTypeDeclarations, and isStrictMock
so it only imports the actually used symbols (e.g., keep
applyStrictMockReturnType and the others) to eliminate the unused reference.
🪄 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: a912e04b-2070-4510-a082-d8546940a90b

📥 Commits

Reviewing files that changed from the base of the PR and between e179894 and ecfa5c8.

📒 Files selected for processing (20)
  • packages/mock/src/faker/getters/array-item-factory.ts
  • packages/mock/src/faker/index.test.ts
  • packages/mock/src/faker/index.ts
  • packages/mock/src/faker/resolvers/value.ts
  • packages/mock/src/mock-types.test.ts
  • packages/mock/src/mock-types.ts
  • packages/mock/src/msw/index.test.ts
  • packages/mock/src/msw/index.ts
  • packages/mock/src/msw/mocks.ts
  • tests/__snapshots__/mock/issue-3525-oas31/endpoints.ts
  • tests/__snapshots__/mock/issue-3525-oas31/model/index.faker.ts
  • tests/__snapshots__/mock/issue-3525-oas31/model/index.ts
  • tests/__snapshots__/mock/issue-3525-oas31/model/pet.ts
  • tests/__snapshots__/mock/issue-3525/endpoints.ts
  • tests/__snapshots__/mock/issue-3525/model/index.faker.ts
  • tests/__snapshots__/mock/issue-3525/model/index.ts
  • tests/__snapshots__/mock/issue-3525/model/pet.ts
  • tests/configs/mock.config.ts
  • tests/specifications/issue-3525-oas31.yaml
  • tests/specifications/issue-3525.yaml

Comment thread packages/mock/src/faker/index.test.ts Outdated
Comment thread packages/mock/src/mock-types.test.ts
Comment thread packages/mock/src/mock-types.test.ts
Comment thread packages/mock/src/msw/index.ts
Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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/mock/src/faker/getters/array-item-factory.ts`:
- Around line 16-22: The import order violates simple-import-sort: place imports
from ../../mock-types before ../../types; specifically, reorder the import
statements so formatMockFactoryDeclaration and getMockFactorySignatureParts
(from '../../mock-types') come before the MockSchema type import (from
'../../types'), keeping the local imports overrideVarName and extractItemsRef
unchanged to satisfy the linter.
🪄 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: a098dcc4-7971-4130-9614-9867486f5bed

📥 Commits

Reviewing files that changed from the base of the PR and between ecfa5c8 and 30f1915.

📒 Files selected for processing (12)
  • packages/mock/src/faker/getters/array-item-factory.ts
  • packages/mock/src/faker/index.test.ts
  • packages/mock/src/faker/index.ts
  • packages/mock/src/faker/resolvers/value.ts
  • packages/mock/src/mock-types.test.ts
  • packages/mock/src/mock-types.ts
  • packages/mock/src/msw/index.test.ts
  • packages/mock/src/msw/index.ts
  • tests/__snapshots__/mock/issue-3525-oas31/endpoints.ts
  • tests/__snapshots__/mock/issue-3525-oas31/model/index.faker.ts
  • tests/__snapshots__/mock/issue-3525/endpoints.ts
  • tests/__snapshots__/mock/issue-3525/model/index.faker.ts
✅ Files skipped from review due to trivial changes (3)
  • tests/snapshots/mock/issue-3525-oas31/model/index.faker.ts
  • tests/snapshots/mock/issue-3525-oas31/endpoints.ts
  • tests/snapshots/mock/issue-3525/endpoints.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/mock/src/msw/index.test.ts
  • packages/mock/src/faker/index.test.ts
  • tests/snapshots/mock/issue-3525/model/index.faker.ts

Comment thread packages/mock/src/faker/getters/array-item-factory.ts Outdated
Preserve semicolon and blank-line conventions for MSW response mocks and schema faker exports.

Co-authored-by: Cursor <cursoragent@cursor.com>
@melloware melloware added the mock Related to mock generation label Jun 2, 2026
Add M extends Record<keyof T, unknown> so generated strict mock helpers typecheck under TS2536.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Hypenate
Copy link
Copy Markdown
Contributor Author

Hypenate commented Jun 2, 2026

@wadakatu I'll have to ask for your review skills :)
Should be last PR from our side (famous last words).

@wadakatu
Copy link
Copy Markdown
Contributor

wadakatu commented Jun 3, 2026

Thanks for the ping @Hypenate, and nice work extracting the strict-mock logic into a testable mock-types.ts module. I pulled the branch (eab1c2922) and ran the generator end-to-end. The single-operation snapshots look right, but I hit two compile-breaking issues as soon as the spec has more than one operation or an array-of-$ref response — both are currently uncovered by the fixtures (which only exercise one object operation).

Repro: OAS 3.0, override.mock: { required: true, nonNullable: true }, mock.generators: [{ type: 'msw' }, { type: 'faker', schemas: true, operationResponses: true }], three ops — GET /petPet, POST /petPet, GET /petsPet[].

1. Duplicate type declarations across operations → TS2300

generateMSW prepends the helper block (KeysWithNull + MockWithNullableOverrides) once per operation, and each generateDefinition prepends the per-schema PetMock alias. The writer concatenates the per-operation strings verbatim (core/src/writers/target.ts:73, and target-tags.ts:61 for tags mode) with no dedup, so a file with N operations emits N copies:

$ grep -c 'export type KeysWithNull'             out/endpoints.ts   # 3
$ grep -c 'export type MockWithNullableOverrides' out/endpoints.ts  # 3
$ grep -c 'export type PetMock'                  out/endpoints.ts   # 3
out/endpoints.ts(164,13): error TS2300: Duplicate identifier 'KeysWithNull'.
out/endpoints.ts(168,13): error TS2300: Duplicate identifier 'MockWithNullableOverrides'.
out/endpoints.ts(176,13): error TS2300: Duplicate identifier 'PetMock'.
... (9 total)

Any spec where ≥2 operations share a file (single mode, or one tag file) trips this — e.g. GET /pet + POST /pet both returning Pet. The faker schemas file is fine because generateFakerForSchemas dedups via a Set; the bug is limited to the per-operation MSW and faker operationResponses paths.

Suggestion: emit the helpers and each {Schema}Mock alias once per file (the pattern generateFakerForSchemas already uses) — hoist them into the file header after aggregation, or dedup by identifier. A multi-operation + shared-schema snapshot would lock this down.

2. Schema-factory imports treated as type names → TS2749

For the array endpoint the response delegates to the faker factory getPetMock(). getSchemaTypeNamesFromResponses adds every response import name (imp.alias ?? imp.name), including value imports like the getPetMock factory, so getStrictMockTypeDeclarations emits:

export type getPetMockMock = {
  [K in keyof Required<getPetMock>]: NonNullable<Required<getPetMock>[K]>;
};

getPetMock is a value, not a type:

out/endpoints.ts(199,24): error TS2749: 'getPetMock' refers to a value, but is being used as a type here.

(The alias is also dead — the array factory's return type already resolves to PetMock[].) Reproduces with msw + faker schemas:true + an array-of-$ref response. Filtering getSchemaTypeNamesFromResponses to type-only imports (drop values/schemaFactory imports), and/or only emitting {Name}Mock for actual schema types, would fix it.

Things that look good

  • getMockWithoutFunc forwarding nonNullable — correct, matches the acceptance criteria.
  • Single-object happy path checks out: getPetMock()PetMock, getPetMock({ tag: null })tag: string | null, getPetMock({ tag: 'x' })tag: string.
  • I briefly suspected the generic <O extends Partial<T>> param would stop catching typo'd override keys, but TS 5.9 still flags excess properties against the constraint (getPetMock({ taag: null }) → TS2561), so no regression there.

One design question

The issue listed per-call generic narrowing as out-of-scope ("TS will not narrow the return type when you pass null… that is acceptable"). The MockWithNullableOverrides machinery is exactly that narrowing, and it's also the part being duplicated in (1). If the simpler getPetMock(overrideResponse?: Partial<Pet>): PetMock from the issue is enough, it'd drop KeysWithNull/MockWithNullableOverrides entirely and shrink the dedup surface to just the {Schema}Mock aliases. Might be worth a quick word with @melloware on whether the narrowing is wanted — happy either way, just flagging the trade-off.

Repro spec available if useful. Thanks again!

Hypenate and others added 3 commits June 3, 2026 11:18
…orts

Dedupe strict mock helper types per endpoints file and ignore faker factory value imports.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@Hypenate
Copy link
Copy Markdown
Contributor Author

Hypenate commented Jun 3, 2026

@wadakatu I gave it another go.

@melloware
Copy link
Copy Markdown
Collaborator

in my opinion "shrinking the dedupe" surface is always ideal so I am ok with the narrowing if necessary!

Copy link
Copy Markdown
Contributor

@wadakatu wadakatu left a comment

Choose a reason for hiding this comment

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

Re-pulled 6b817246c — both original issues are fixed (multi-op + array specs generate and tsc clean, and the issue-3525-multi fixture is a great addition 👍).

Following up on the dedup approach before it lands. After this change the strict-mock types are emitted only by dedupeStrictMockTypeDeclarations re-deriving schema names via regex from the rendered output, and it runs on every mock file regardless of the strict flags. That string-scraping regresses today for any schema whose name ends in Mock — including users who don't touch the strict flags at all. I left inline notes on the exact spots; full repro below.

Repro — plain non-strict mock (no required/nonNullable), one schema named WidgetMock:

components:
  schemas:
    WidgetMock:
      type: object
      required: [id]
      properties: { id: { type: integer }, label: { type: string, nullable: true } }
endpoints.ts(8,3):   error TS2440: Import declaration conflicts with local declaration of 'WidgetMock'.
endpoints.ts(75,24): error TS2304: Cannot find name 'Widget'.

The dedup injects strict helpers + export type WidgetMock = { [K in keyof Required<Widget>] … } into the non-strict file — it collides with the imported model (TS2440) and references a non-existent Widget captured from WidgetMock (TS2304). The same breakage happens in strict mode for a *Mock-named schema. Normal non-strict specs without a *Mock schema are unaffected — the dedup is a clean no-op there, so the blast radius is "any schema whose name ends in Mock".

Two changes would make it robust:

  1. Only run the hoist when strict mode is actually on (isStrictMock(override.mock)), so non-strict output is never rewritten.
  2. Build the header from the real strict type names you already compute (getStrictMockTypeName / getSchemaTypeNamesFromResponses) instead of regex-scanning the rendered string — that also removes the whitespace coupling in STRICT_MOCK_SCHEMA_DECL_PATTERN / replaceAll(helperBlock, …).

A fixture with a schema named WidgetMock (both strict and non-strict) would lock the regression down. Happy to help if useful — thanks again for iterating on this! 🙏

Comment thread packages/mock/src/mock-types.ts Outdated
return (
implementation.includes('export type KeysWithNull') ||
implementation.includes('MockWithNullableOverrides<') ||
/\b[A-Z]\w*Mock\b/.test(implementation)
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.

This /\b[A-Z]\w*Mock\b/ gate matches any PascalCase identifier ending in Mock, including a user schema literally named WidgetMock. Because dedupeStrictMockTypeDeclarations runs for every mock file (see finalizeMockImplementation wiring), this flips to true for non-strict output too and triggers the rewrite. Gating on the actual strict flag (isStrictMock(override.mock)) rather than a name heuristic would avoid the false positive.

Comment thread packages/mock/src/mock-types.ts Outdated
names.add(match[1]);
}

for (const match of implementation.matchAll(/\b([A-Z]\w*)Mock\b/g)) {
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.

This captures Widget from the model type WidgetMock, so buildStrictMockTypeFileHeader then emits:

export type WidgetMock = {
  [K in keyof Required<Widget>]: NonNullable<Required<Widget>[K]>;
};

Widget doesn't exist (→ TS2304) and the alias name WidgetMock collides with the imported model (→ TS2440). Re-deriving schema names from the rendered string is the fragile root cause here — threading the real type names through as structured data (you already compute them via getStrictMockTypeName / getSchemaTypeNamesFromResponses) would remove this class of bug.

Comment thread packages/mock/src/mock-types.ts Outdated
export function dedupeStrictMockTypeDeclarations(
implementation: string,
): string {
let body = implementation.replaceAll(INVALID_STRICT_MOCK_DECL_PATTERN, '');
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.

dedupeStrictMockTypeDeclarations runs unconditionally on every mock file (it's wired into finalizeMockImplementation plus both *Imports hooks), so non-strict generation gets rewritten too. An early isStrictMock(override.mock) guard — here or at the call sites — would scope this to the feature and remove most of the regression surface for non-strict users.

Comment thread packages/orval/src/api.ts
footer: generateClientFooter,
imports: generateClientImports,
importsMock: generateMockImports,
finalizeMockImplementation: dedupeStrictMockTypeDeclarations,
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.

This wires the hoist for all mock generation, regardless of the strict flags. As a result a non-strict user whose schema name ends in Mock gets strict helper types + a broken …Mock alias injected into their output (TS2440 / TS2304). Passing the strict flag (and ideally the structured strict type-name set) through here would let it stay a no-op for non-strict output.

Hypenate and others added 2 commits June 3, 2026 16:09
Only hoist strict-mock helpers when required+nonNullable are set, pass
schema names from generators instead of regex-scraping rendered output,
and add WidgetMock fixtures to lock the non-strict regression.

Co-authored-by: Cursor <cursoragent@cursor.com>
Explicitly annotate the normalized mockOutputs array so eslint no-unsafe-* passes in CI.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Hypenate
Copy link
Copy Markdown
Contributor Author

Hypenate commented Jun 3, 2026

@wadakatu I gave it another try, but maybe it's becoming too complex?
What do you think?

@wadakatu
Copy link
Copy Markdown
Contributor

wadakatu commented Jun 4, 2026

First, the good news: I re-pulled b2c64c5d3 and the regression is gone — both issue-3525-widget-mock (non-strict) and …-strict generate and tsc clean, non-strict output is untouched, and a *Mock-named schema now emits a correct WidgetMockMock with no collision. The flag gating + structured strictMockSchemaTypeNames are exactly the right move. 👍

On "is it too complex?" — honestly a little, but I don't think it's the amount of functionality; it's that the new structured-names path was added alongside the old regex-scraping path instead of replacing it. There are two parallel strategies right now:

  • MSW / operation mocks: factories only use the types, declarations aren't emitted per-op, and strictMockSchemaTypeNames flows to finalizeMockImplementation, which emits them once. 👍 clean.
  • faker schemas file: generateFakerForSchemas still emits the helper block + each {Schema}Mock inline, so dedupeStrictMockTypeDeclarations has to strip + rebuild them with regex (INVALID_STRICT_MOCK_DECL_PATTERN, STRICT_MOCK_SCHEMA_DECL_PATTERN, collectStrictMockSchemaTypeNames, collectStrictMockSchemaNamesFromUsage, replaceAll(helperBlock, …)).

So the complexity is basically the regex half, which only exists to clean up what the faker-schemas path emits inline.

You can collapse it to one strategy: have generateFakerForSchemas stop emitting the helpers/aliases inline and instead expose strictMockSchemaTypeNames the way the MSW builder does, then emit them once at the file level (like the operation path already does). At that point nothing needs de-duplicating and the whole regex layer can go — INVALID_/STRICT_MOCK_DECL_PATTERN, collectStrictMockSchemaTypeNames, collectStrictMockSchemaNamesFromUsage, and the strip/replaceAll logic. "Dedupe" becomes "emit once from the structured set," which also removes the whitespace coupling those patterns rely on.

That said, this simplification is an internal refactor — the PR as it stands fixes the regression and generates correct, compiling output (I verified locally). So there's no need to cram it all into this PR; I'd be totally fine doing the regex-layer removal / unification as a follow-up PR rather than piling more onto this one. Might be the easier path — what do you prefer?

@Hypenate
Copy link
Copy Markdown
Contributor Author

Hypenate commented Jun 4, 2026

@wadakatu
Thanks again for your excellent review and testing!
Indeed, the PR is getting quite big.

IF it's okay for @melloware to merge this, and then I'll do a follow-up PR to do the simplification, but atleast this PR stays under control.

@melloware melloware merged commit 7144707 into orval-labs:master Jun 4, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mock Related to mock generation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(mock): .faker types should reflect complete mocks when required and nonNullable are both true

3 participants