Skip to content

refactor: remove duplicated code and scaffolding across the repository - #97

Merged
OmarAlJarrah merged 10 commits into
mainfrom
refactor/simplification-sweep
Jul 27, 2026
Merged

refactor: remove duplicated code and scaffolding across the repository#97
OmarAlJarrah merged 10 commits into
mainfrom
refactor/simplification-sweep

Conversation

@OmarAlJarrah

Copy link
Copy Markdown
Member

Summary

A maintenance sweep across the whole repository, removing duplicated code and
scaffolding that had accumulated as helpers landed after the call sites that
should have used them. Net −965 lines (1,788 added, 2,753 removed) across 73
files, with one real bug fixed along the way.

No IR field name, struct shape, JSON tag, diagnostic code, or golden file
changes. The pipeline's observable behavior is unchanged apart from the
diagnostic fix below.

The one behavior change

lowerSecurityScheme and lowerMeta appended their extension diagnostics inside
the same if len(ext) > 0 guard that gated the assignment. That guard is false
in exactly the case the diagnostic describes: when every x-* extension on a
node fails to serialize, ext comes back empty and the warning was dropped
silently, so a security scheme carrying a single unserializable extension
compiled clean. Both sites now route through a lowerer.extensions helper that
records the diagnostic unconditionally. Regression tests cover both.

What drove the duplication

Four patterns accounted for most of it:

  • Helpers that landed after their call sites and were never retrofitted.
    componentSpec was added in an earlier change; 36 tests still hand-wrote the
    byte-identical preamble it emits. assertHasCode had three callers while ~25
    sites hand-rolled the same scan loop.
  • One function per assertion.ir/ carried 138 test functions whose bodies
    were a single helper call, the fixture being the only content.
  • Per-type functions over what is already one generic type. The eight
    resolveX unwrappers were identical because all eight argument types are
    aliases of one Reference[T,V,C]. Common() was written eleven times.
    irverify spelled its four-registry list four separate times.
  • Surface built ahead of its callers.NewWithRegistry and
    Registry.Formats had no production callers; eight nolint:unused directives
    deferred to "later compiler files" that had since arrived.

Deliberately not done

Several tempting consolidations were costed and rejected because they make the
code worse or do not actually save anything:

  • Merging pass/validate.go's typed walker into irverify's reflection
    walk.
    They are separate channels by design — ir.Diagnostic reports problems
    in the source spec, irverify.Violation reports our own compiler bugs.
    Retiring the typed walker also forces graphqlReachableTypes onto a collector
    that widens reachability to every TypeID, and since Property.Args[].Type is
    itself a TypeRef, the field checkArgsOutsideGraphQL polices becomes an edge
    that legalizes what it reaches. The line count inverts to positive.
  • A generic replacement for the eleven MarshalJSON adapters. They are
    four-line adapters over one marshalWithKind, and the recursion-avoiding type
    alias they depend on is not expressible inside a generic function.
  • Collapsing the determinism tests into a cross-file table. Measured at 13
    lines, and it only fits the function-size cap by dropping the per-case
    rationale comments.
  • cycles.go. 452 code lines across 36 functions; the remaining ~290 are the
    rationale for the cycle-detection work and are load-bearing. The pre-parse gate
    is permanent — it guards against stack exhaustion, which recover cannot
    catch, so it must run before unmarshalling rather than around it.

Repository hygiene

git add -A previously staged seven cloned reference repositories under
.claude/ as gitlinks pointing at commits that exist in no clone of this repo,
which breaks checkout for anyone else, plus a 91 MB scratch file. Those are now
ignored; nothing was deleted from disk. CLAUDE.md also still declared that the
repository contained no Go code and no build tooling.

Test plan

  • gofmt -l . — clean
  • go vet ./... — clean
  • golangci-lint run — 0 issues
  • go test ./... — all 12 packages pass
  • scripts/check-coverage.sh — total 100.0%, every package at 100.0%
  • Goldens unchanged: no -update run, git status clean under testdata/
  • The extension-diagnostic fix was mutation-tested — reverting meta.go to the
    guarded form fails the new test, restoring it passes.

The working tree accumulated several untracked directories that 'git add -A'
would have staged: .claude/ holds seven cloned reference repositories, each of
which git stages as a 160000 gitlink pointing at a commit that exists in no
clone of this repo, breaking checkout for everyone else. .crush/ and .idea/ are
similarly local-only, and out.json, logs.txt and pets.json are ad hoc run
artifacts that land in the repo root.
Ignore all of them. The .idea/ and .vscode/ entries were already present but
commented out. Nothing is deleted from disk - out.json and pets.json were built
from inputs that are not in the repo.
CLAUDE.md still declared that the repository 'contains only design documents
under docs/ - there is no Go code, no go.mod, and no build/test tooling yet',
and that the standard Go commands 'None of these work today'. Both have been
false for several milestones: the ir package, the OpenAPI compiler, the
validation pass, the engine and two CLIs all ship and are gated in CI. Point at
README.md for the milestone table rather than maintaining a second copy.
The architecture.md §3 package tree was also missing three packages that exist:
ir/irverify, internal/harness and cmd/morphic-harness.
The gate ran 'go test ./...' as its own step and then ran check-coverage.sh,
which runs the same full suite again with coverage instrumentation. The
standalone step passed no flag the coverage run lacks, so it was pure duplicated
wall-clock.
Also pin golangci-lint-action to an exact release instead of the floating major
tag, so an upstream release cannot turn the gate red with no change in this
repo.
NewWithRegistry had no production callers. Its only purpose was to let tests
reach newEngine's register-error branch, which required a whole internal test
file to exist alongside the external one. NewWith(fronts ...compilers.Compiler)
covers the same ground without handing the caller a mutable registry to retain,
and it can be exercised from the external test package, so engine_internal_test.go
goes away entirely.
Registry.Formats likewise had no callers outside its own two tests, and was the
only reason the compilers package imported sort. The engine's lookup-miss error
does not enumerate formats and the CLI has one subcommand, so nothing needed it.
…ce table
Three separate one-per-kind repetitions collapse:
- Common() was defined eleven times, once per concrete TypeDef kind, each body
identical. Promote it from the embedded *TypeCommon instead. The unexported
typeDef() marker stays per-kind - moving that to the exported TypeCommon would
let any embedder join the sum and break the seal.
- 'does any diagnostic carry error severity' was written five times across three
packages. Add ir.HasError and ir.FirstError; both are needed because several
call sites use the predicate in expression position and cannot take a
two-value return.
- irverify spelled its four-registry list four times over (the ID-type vars,
registryFor, resolves, and singular). One keyed table replaces them, so a
reference class can no longer be dropped from the oracle by someone updating
three of the four lists. The fields are keyed rather than positional because
two adjacent string fields would let a future row silently emit
'ir/dangling-types-ref'. The resolves accessor keeps its nil guard: Verify is
documented as a report-only oracle that never crashes on a malformed document.
irverify's corpus test is deleted - internal/harness already applies the
identical oracle to a strict superset of the same specs. irtest now spells the
golden encoding rule once, so the writer and the comparer cannot disagree.
lowerSecurityScheme and lowerMeta both appended their extension diagnostics
inside the same 'if len(ext) > 0' guard that gated the assignment. That guard is
false in exactly the case the diagnostic exists to describe: when every x-*
extension on the node fails to serialize, ext comes back empty and the warning
was dropped silently. A scheme carrying a single unserializable extension
compiled clean.
Route both through a lowerer.extensions helper that records the diagnostic
unconditionally, and fold the four other copies of the same idiom onto it.
The rest of this change is mechanical deduplication of the lowering path with no
behavior change:
- Eight resolveX reference unwrappers had byte-identical bodies because all
eight argument types are aliases of one Reference[T,V,C]. One generic
resolveRef replaces them.
- The 'adopt if unset' blocks in mergeConstraints and reconcileProperty become
cmp.Or, except the Min/Max pairs that co-adopt an exclusivity flag and
Examples, whose emptiness test is len() on a non-comparable slice. Note that
this folds thirty tracked coverage obligations out of existence, which leaves
the two TestMergeConstraints tests as the only thing pinning per-keyword
adoption.
- The four single-caller conflict predicates fold into their detail-returning
twins; lowerDiscriminator and modelDiscriminator merge; internNode becomes the
single hoisting entry point; variantRef is deleted as lowerTyped with a
TypeRef wrapper around it.
- cycles.go's unescapePointer was a byte-identical copy of ids.go's
unescapeSegment, in a file that already owns pointer construction and its
inverse. loaded.Format was assigned and never read. Eight nolint:unused
directives deferring to 'later compiler files' outlived those files.
Three spec fixtures were defined twice each across six test files under
identical content but different names (brokenSpec/badSpec, tinySpec/tinySpec,
minimalSpec/okSpec). Hoist the three constants into internal/testspec.
The package holds constants only, deliberately: a statement-bearing package with
no tests reports 0.0% and fails the per-package coverage gate, while a
const-only package registers as having no test files and is skipped. The four
writeSpec helpers are left where they are - hoisting those made fifteen call
sites more verbose to save five lines.
pass's four count-then-assert blocks share one countCode helper.
componentSpec landed in an earlier change but the tests written before it kept
hand-writing the five-line YAML preamble it emits - thirty-six of them, byte
for byte. assertHasCode sat in cycles_test.go with three callers while roughly
twenty-five other sites hand-rolled the same scan loop. The helpers were never
retrofitted to the call sites that predated them.
Move the diagnostic assertions into helpers_test.go alongside a pathsSpec
companion to componentSpec, add firstOp, indexBy and propsByWire, and migrate
the call sites.
Only the purely existential and purely counting assertions are converted.
Thirteen sites assert a property of every match, or AND a message substring with
a code, and an existential helper would silently weaken them. fuzz_test.go's
predicate is a fuzz-target skip gate rather than an assertion and keeps its
call-site semantics.
The petstore spec existed both as a 104-line Go constant and as the on-disk
golden input, byte-identical, with nothing forcing them to stay in step: editing
the fixture and regenerating left the end-to-end test asserting against a stale
copy. It now reads the file the golden already uses.
TestMappingPairs_Cases becomes a table like its five siblings, keeping the three
subtests whose assertions the shared one would erase and moving their rationale
into the doc comment.
The package carried 138 test functions of the form TestX_ZeroValueShape and
TestX_PopulatedRoundTrip whose bodies were a single helper call each - the
fixture was the only content and the wrapper cost four to six lines around it.
One assertJSONContract runs both halves as subtests, so the failure isolation
that assertRoundTrip's require.NoError provides today is preserved per half.
Fifty-seven of the sixty-three paired types merge. Six do not: Idempotency,
StatusRange, TemplateArg, VersionedBool and Visibility have table-driven
round-trips over several fixtures that a single populated value would quietly
drop - Visibility's three-state table is what pins optionality against
nullability - and Operation merges to sixty-three lines against a seventy-line
cap. The eleven TypeDef kinds round-trip through a pointer and stay as they are.
Also: the eight constant-spelling tests share one generic helper, threading the
empty-state name as a parameter rather than assuming 'unspecified', which
contradicts IdempotencyKind's own documented spelling. The eleven closures in
the Common aliasing test become a loop over the kind list, reading the mutation
back through json.Marshal - asserting through Common() instead would pass
against a shared-singleton implementation, since it reads back through the
method it wrote through. Four tests that hand-rolled the round-trip and
determinism helpers now use them.
The map-determinism tests gain the five cases that were missing: Message.Bindings,
Content.ItemEncoding, and Document's Channels, Messages and Auth registries.
…emptyConstraints drift
Three gaps from the simplification audit's unverified leads:
- emptyConstraints was missing Precision, Scale, and PatternMessage from its
field check. These are scalar constraints that constraintsFromSchema could
populate but weren't guarded against, so adding any of them would silently
leak a non-nil *Constraints when it should be nil.
- pickBool plus three nil-safe accessors (schemaReadOnly, schemaWriteOnly,
schemaDeprecated) followed an identical shape across exactly three call
sites. Replaced with one pickFlag(ref, tgt, accessor) that captures the
nil-safe "use-site overrides referent" pattern once.
- 17 of 19 provenance-stamping sites hand-wrote the same
append+diagf+Provenance{Source: l.srcIndex} triple. Extracted into an
l.diag method so the stamping rule is stated once. The two remaining raw
sites are correct exceptions: operations.go uses op.Provenance (not
l.srcIndex), and preserveRaw now delegates through l.diag itself.
💘 Generated with Crush
Assisted-by: Crush:qwen3.7-max
Sign up for freeto 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

@OmarAlJarrah