Skip to content

refactor(tsmorph): remove duplicated import builders and unreachable guards in generateFiles - #207

Merged
7nohe merged 3 commits into
mainfrom
claude/lucid-heisenberg-ed96ff
Aug 11, 2026
Merged

refactor(tsmorph): remove duplicated import builders and unreachable guards in generateFiles#207
7nohe merged 3 commits into
mainfrom
claude/lucid-heisenberg-ed96ff

Conversation

@7nohe

@7nohe 7nohe commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Follow-up to the buildKeys dead-code removal (#205), kept out of that PR deliberately. Rebased onto current main (includes #205 and #204).

src/tsmorph/generateFiles.mts was the worst-covered file in the repo at 68.75% branch coverage, and it was uncoverable by adding tests: every uncovered branch was either structurally unreachable or a duplicate of already-tested code. This removes those branches instead.

1. Duplicated import builders

generateFiles.mts held a verbatim private copy of buildCommonFileImports / buildHookFileImports, which are already exported and tested in projectFactory.mts (tests/tsmorph/projectFactory.test.ts). The duplicated code was also the untested code. It now imports them.

2. Unreachable null guards

Three if (hook) guards could never be false — each call site pre-filtered on isPaginatable, and the builders return null only when !op.isPaginatable:

Guard Builder Returns null iff
generateSuspenseFile buildUseSuspenseInfiniteQueryHookbuildInfiniteHook buildQueryHooks.mts:271
generateInfiniteQueriesFile buildUseInfiniteQueryHookbuildInfiniteHook buildQueryHooks.mts:271
generatePrefetchFile buildPrefetchInfiniteQueryFn buildQueryHooks.mts:381

3. Extracted buildQueryOptionsFileImports

With 1 and 2 done, one uncovered branch remained: the inline buildModelImport + null-check in generateQueryOptionsFile. Same defect class as 1 — an ad-hoc import list duplicating logic projectFactory.mts already owns, and the only part of the import wiring no test reached. Moved next to its siblings and covered both the with-models and no-models paths.

4. One addStatements helper for every nullable builder

The first pass left generateFiles.mts with four nullable-builder call sites written three different ways. All four now go through one helper:

function addStatements(
  sourceFile: SourceFile,
  operations: OperationInfo[],
  build: (op: OperationInfo) => VariableStatementStructure | null,
): void

The contract — a builder returns null for operations outside its scope — is stated once in the helper's JSDoc instead of being re-derived at each site, and three near-identical explanatory comments collapse to one.

This also fixes a build regression the earlier pass introduced: narrowing (T | null)[] with a bare .filter(x => x !== null) relies on inferred type predicates, a TypeScript 5.5 feature, while package.json declares a typescript: "5.x || 6.x" peer range. Verified against typescript@5.4.5: tsc --noEmit produced three TS2345 errors before this commit and exits 0 after. Consumers of the published dist were never affected — this only broke building from source on TS 5.0–5.4.

Note this brings generateQueryOptionsFile's if (infiniteOptions) guard along too. That guard is genuinely load-bearing (its call site does not pre-filter) and is not removed — the same conditional now lives inside the helper, exercised both ways.

Coverage

before after
generateFiles.mts branches 68.75% 100%
repo aggregate branches 91.73% 93.47%

Verification

  • npm test — 189 passed, exit 0, unmodified config
  • npm run build, npm run lint — clean (107 files, zero warnings)
  • tsc --noEmit under typescript@5.4.5 and @6.0.3 — both clean
  • npm run preview:react diffed against a baseline generated from main: byte-identical

Note on the perFile follow-up

The motivation was to enable coverage.thresholds.perFile: true in vitest.config.ts, since the global aggregate can be moved by adding or deleting any file regardless of whether behaviour got safer.

generateFiles.mts is no longer the blocker, but perFile: true still cannot be enabledsrc/parseOperations.mts fails it on functions, statements and branches:

ERROR: Coverage for functions (94.11%) does not meet global threshold (95%) for src/parseOperations.mts
ERROR: Coverage for statements (90.14%) does not meet global threshold (95%) for src/parseOperations.mts
ERROR: Coverage for branches (80%) does not meet global threshold (90%) for src/parseOperations.mts

So vitest.config.ts is deliberately untouched. Covering parseOperations.mts (uncovered lines 27, 43, 55) is the remaining prerequisite.

@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
openapi-react-query-codegen Ready Ready Preview Aug 11, 2026 11:42am

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 99.2% (🎯 95%) 498 / 502
🟢 Statements 98.47% (🎯 95%) 516 / 524
🟢 Functions 99.26% (🎯 95%) 135 / 136
🟢 Branches 93.47% (🎯 90%) 215 / 230
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/tsmorph/generateFiles.mts 100% 100% 100% 100%
src/tsmorph/projectFactory.mts 100% 100% 100% 100%
Generated in workflow #449 for commit 5f98c3c by the Vitest Coverage Report Action

7nohe added 3 commits August 11, 2026 20:41
… guards

`generateFiles.mts` kept a verbatim private copy of `buildCommonFileImports`
and `buildHookFileImports`, duplicating the exported versions in
`projectFactory.mts`. The exported ones are covered by
`tests/tsmorph/projectFactory.test.ts`; the copies were not, so the duplicated
code was also the untested code. Import them instead.

Three `if (hook)` null guards were unreachable: each call site pre-filtered on
`isPaginatable`, and `buildInfiniteHook` / `buildPrefetchInfiniteQueryFn`
return null only when `!op.isPaginatable`. Rather than assert non-null, map
over the GET operations and drop the nulls, which keeps the types honest and
leaves the builder as the single source of truth for pagination.

The `if (infiniteOptions)` guard in `generateQueryOptionsFile` is left alone:
that call site does not pre-filter and both paths are exercised.

Generated example output is byte-identical.

Branch coverage: generateFiles.mts 68.75% -> 75%, repo aggregate 90.07% -> 91.25%.
…actory

`generateQueryOptionsFile` assembled its own import list inline, repeating the
`buildModelImport` + null-check pattern that `buildCommonFileImports` already
owns. Like the copies removed in the previous commit, that inline branch was
the one part of the import wiring no test reached.

Move it next to its siblings in `projectFactory.mts` and cover both the
with-models and no-models paths, mirroring the existing
`buildCommonFileImports` tests.

Generated example output is byte-identical.

Branch coverage: generateFiles.mts 75% -> 100%, repo aggregate 91.25% -> 91.66%.
…helper

generateFiles.mts had four call sites for builders that return null when the
operation is out of scope, written three different ways: a plain `if` guard for
`buildInfiniteQueryOptionsFn`, and three near-identical
`.map(build).filter(x => x !== null)` blocks each carrying its own near-identical
three-line comment.

Collapse all four onto a single `addStatements(sourceFile, operations, build)`
helper. The contract ("a builder returns null for operations outside its scope")
is now stated once, in the helper's JSDoc, instead of being re-derived at every
site.

This also fixes a build regression: narrowing `(T | null)[]` via a bare
`.filter(x => x !== null)` relies on inferred type predicates, a TypeScript 5.5
feature, while package.json declares a `typescript: "5.x || 6.x"` peer range.
Verified with typescript@5.4.5 -- `tsc --noEmit` failed with three TS2345 errors
before this commit and passes after. Consumers of the published `dist` were
never affected; this only broke building from source on TS 5.0-5.4.

The queryOptions import test now asserts the exact module-specifier order rather
than mere membership, since that order is emitted verbatim.

Generated example output is byte-identical.
@7nohe
7nohe force-pushed the claude/lucid-heisenberg-ed96ff branch from f795136 to 5f98c3c Compare August 11, 2026 11:42
@7nohe
7nohe merged commit 3b1ef0f into main Aug 11, 2026
5 checks passed
@7nohe
7nohe deleted the claude/lucid-heisenberg-ed96ff branch August 11, 2026 12:01
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