Conversation
…tions When `mockOnly: true`, a type provides a hand-written mock method without requiring `init`/`instantiate()` or `Instantiable` conformance. The mock generator uses these mocks as defaults — forwarded deps get `= Type.mock()` defaults, and instantiated deps become optional tree children with `Type.mock()` as the default builder. This eliminates the need for callers to manually provide mocks for forwarded types and types whose `@Instantiable` is in another module. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #248 +/- ##
==========================================
Coverage 100.00% 100.00%
==========================================
Files 41 41
Lines 5799 5986 +187
==========================================
+ Hits 5799 5986 +187
🚀 New features to boost your workflow:
|
… tests Switch mockOnly error diagnostics from message-only to error+changes pattern so IDEs show actionable fix-it suggestions. Add 6 unit tests for the new FixableInstantiableError cases (description + fixIt for each). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers the (true, false) merge path in resolveSafeDIFulfilledTypes where a mockOnly entry exists and a non-mockOnly entry with its own mock method arrives — ensuring the duplicate mock provider error is thrown. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ment Covers the extension-branch paths for mockOnlyWithGenerateMock, mockOnlyWithIsRoot, and the mockOnlyArgumentInvalid throw — these were uncovered since prior tests only exercised the concrete-declaration branch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ion test When a mockOnly declaration merges with a non-mockOnly production declaration, the merged entry has mockOnly=false but carries the mockInitializer from the mockOnly provider. The mockOnlyTypes computation was filtering on instantiable.mockOnly, so merged entries were excluded and forwarded parameters didn't get Type.mock() defaults. Fix by deriving mockOnlyTypes from the presence of a mockInitializer on non-generateMock entries instead of checking the mockOnly flag. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dering Add test for the (true, false) merge path where a mockOnly entry is already in the map and a non-mockOnly with generateMock arrives second. Raise codecov project target from 99.9% to 100%. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…, add merge docs - Update customMockName error message/fix-it to mention mockOnly as valid alternative - Rename customMockNameWithoutGenerateMock to customMockNameWithoutMockGeneration - Rename mockOnlyTypes to handWrittenMockTypes (reflects actual semantics post-merge fix) - Document merge precedence rules in Manual.md mockOnly section Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Module info files are regenerated each build — no cross-version deserialization needed. The synthesized Codable conformance is sufficient. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The merge logic copied mockInitializer and mockReturnType but not customMockName, so a mockOnly declaration with customMockName: "preview" would emit Type.mock() instead of Type.preview() after merging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add two tests verifying production code output is identical with and without mockOnly declarations (including customMockName and mockAttributes) - Extract mergedWithMockProvider(_:) on Instantiable to centralize mock field copying — prevents future omissions like the customMockName bug Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Test @Instantiated child with merged customMockName exercises the tree generation path (SafeDIOverrides + builder), not just forwarded defaults - Test mockOnly + customMockName: "preview" without a preview() method verifies the diagnostic names the custom method, not just "mock" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Clarify that undecorated extensions are not detected, and point users to mockOnly as the way to provide mocks from separate extensions - Update customMockName docs (both Manual and Instantiable.swift) to describe both generateMock and mockOnly usage - Update @forwarded section to list all three sources of defaults: init defaults, mockOnly providers, and merged declarations Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use second person ("you must") instead of "the user must"
- Describe user-facing behavior instead of internals ("tree child",
"default builder", "initializer and dependencies")
- Simplify customMockName description
- Consolidate three @forwarded default cases to two (merged declaration
is an implementation detail of mockOnly, not a user-facing concept)
- Use "combines" instead of "merges" for the dual-declaration explanation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Lead with example, then behavior, then rules (matching existing manual pattern of introduce-capability → show-example → add-caveats) - Move use cases list after the behavioral explanation - "redundant but allowed" → "has no effect when mockOnly is true" - Extract "Splitting production and mock declarations" as a subheading - Reword merge explanation behaviorally instead of implementation-centric - Fix Instantiable.swift doc comments: "the user must" → "you must", "the mock generator references" → "SafeDI uses" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
694312b to
8a3c099
Compare
- mockOnlyWithGenerateMock: removes `generateMock: true` from the attribute - mockOnlyWithIsRoot: removes `isRoot: true` from the attribute - mockOnlyMissingMockMethod: generates a stub mock method with correct parameters and a compilable body Add removeArgument(labeled:from:on:) helper that handles comma cleanup for all argument positions (first, middle, last, only). Tests verify fix-its produce compilable code via fixedSource assertions, including edge cases: removing first/last of multiple args, generating stubs with multiple dependencies. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The guard branch returning [] was structurally unreachable — we only call removeArgument when the labeled argument is known to exist. Per CLAUDE.md: dead branches for unreachable paths should not exist. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3e4f932 to
c6f90d2
Compare
Flatten three-deep if nesting into single comma-separated conditions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mockInitializer, mockReturnType, and customMockName doc comments were framed only around generateMock. Updated to reflect mockOnly usage too. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add InstantiableVisitor.mockMethodName alongside the existing macroName and instantiateMethodName constants. Replace 8 hardcoded "mock" string literals across ScopeGenerator, DependencyTreeGenerator, InstantiableVisitor, and InstantiableMacro. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…and test fixture naming Add four sections to CLAUDE.md based on lessons from the mockOnly implementation: - Mock generation flow: end-to-end pipeline from generateMockCode through ScopeGenerator, with note about production/mock path isolation - Validation boundaries: macro (local context, fix-its) vs plugin (full context, cross-type errors) - Serialization: Codable models are hermetic per build, no backward compatibility needed - Test fixture naming: executeSafeDIToolTest names files from type names, which affects processing order Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6aa69b84ce
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } else if let mockMethodName = context.handWrittenMockTypes[dependency.property.typeDescription] { | ||
| let mockTypeName = dependency.property.typeDescription.asSource | ||
| mockParameters.append("\(bodyIndent)\(dependency.property.label): \(typeSource) = \(mockTypeName).\(mockMethodName)()") |
There was a problem hiding this comment.
Skip mock defaults when provider needs arguments
This branch always defaults forwarded parameters to Type.mock() (or customMockName) whenever a hand-written mock exists, but it never checks whether that mock method has required parameters. If a forwarded dependency’s provider is, for example, static func mock(token: String) -> Child, generated code becomes child: Child = Child.mock() and fails to compile. Please gate this defaulting on the provider having no required arguments (or only defaulted ones), otherwise keep the forwarded parameter required.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch — this is a real edge case. When a hand-written mock has required dependency-matching parameters, the generated Type.mock() default won't compile. Fixed in #249 by tightening the handWrittenMockTypes filter to only include types whose mock can be called with zero arguments.
| changes: Self.generateCustomMockStub( | ||
| named: expectedMethodName, | ||
| typeName: extensionDeclaration.extendedType.typeDescription.asSource, | ||
| dependencies: [], | ||
| isExtension: true, |
There was a problem hiding this comment.
Generate a valid stub for missing mockOnly extension method
For @Instantiable(mockOnly: true) extensions with no mock provider, this diagnostic path asks generateCustomMockStub to treat the declaration as extension-based instantiation, which produces a stub that calls .instantiate(). mockOnly extensions are explicitly allowed for external/stdlib types that do not implement instantiate(), so applying the offered fix-it can immediately introduce uncompilable code. The mockOnly fix-it should emit a body that does not depend on instantiate().
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not a real concern. The diagnostic at this line is the enforcement that a mock method must exist — it fires when the method is missing. The fix-it is just a convenience scaffold; the user has to fill in the body with real construction logic anyway since SafeDI can't know how to build an arbitrary third-party type. The stub body being imperfect is fine.
Summary
mockOnly: Bool = falseto@Instantiable, allowing types to provide a hand-writtenmock()method without requiringinit/instantiate()orInstantiableconformancemockOnlydeclaration, the mock generator usesType.mock()as the default — forwarded deps become defaulted parameters, and instantiated deps become optional tree childrencustomMockNamefor naming the hand-written method@Instantiabledeclarations for the same type viamergedWithMockProvider(_:)(e.g., production init from one, mock from the other)@Instantiable(mockOnly: true) extension String)Test plan
🤖 Generated with Claude Code