feat: Phase 5.2 migrate embedded-resource APIs to InitializeEmbeddedResourcesFromAssembly#16
Conversation
…esourcesFromAssembly
Three new pattern ids cover the TestableIO embedded-resource surface:
- MockFileSystem.AddFileFromEmbeddedResource (single file by exact resource
name) — manual review only. Testably exposes only a bulk
InitializeEmbeddedResourcesFromAssembly with no single-file overload, and
bulk replacement would lose the user's right to rename the destination.
- MockFileSystem.AddFilesFromEmbeddedNamespace (bulk by literal StartsWith
prefix) — auto-fixed when the assembly arg resolves statically and the
third arg is a string literal that begins with that assembly's name.
- The previous two cover the IMockFileDataAccessor surface as well: when
the receiver is the interface (not concrete MockFileSystem) the
InitializeEmbeddedResourcesFromAssembly extension on IFileSystem cannot
bind, so those receivers fall through to manual review.
Auto-fix conditions for AddFilesFromEmbeddedNamespace:
- Receiver is concrete TestableIO MockFileSystem.
- Assembly arg is `typeof(SomeType).Assembly` (resolved via semantic model)
OR `Assembly.GetExecutingAssembly()` (resolved to Compilation.AssemblyName).
- Third arg is a string literal that starts with the resolved assembly name.
Rewrite shape:
fs.AddFilesFromEmbeddedNamespace("/data", typeof(C).Assembly, "MyAsm.TestData")
→ fs.InitializeEmbeddedResourcesFromAssembly("/data", typeof(C).Assembly,
relativePath: "TestData");
Dots in the stripped remainder become forward slashes (Testably normalizes
AltDirectorySeparatorChar). Literal == assembly name (with or without trailing
dot) drops the `relativePath:` argument entirely so the call materializes
every embedded resource (matching TestableIO's `StartsWith(<asm-name>)`).
The fix adds `using Testably.Abstractions.Testing;` ADDITIVELY without
removing the existing TestingHelpers using — the receiver type stays bound
to TestableIO so other call sites in the file keep compiling. New helper
`EnsureTestablyUsing` for additive insertion (vs the existing destructive
`SwapToTestablyUsing`).
Known semantic difference: TestableIO flattens nested-namespace resources
under the target dir; Testably preserves nested structure as subdirectories.
Common case (flat resource set) is equivalent; nested cases produce more
correct but different output, surfaced through manual inspection of the diff.
Tests cover: analyzer firing on each pattern (3); manual-review codefix for
AddFileFromEmbeddedResource; auto-fix happy path for typeof-Assembly,
nested namespace path preservation, literal == assembly name (relativePath
omitted), GetExecutingAssembly path; manual review for prefix mismatch,
non-literal third arg, non-resolvable assembly arg, interface-typed receiver.
Playground gains TestData/sample.txt as embedded resource and a parity
fixture per pattern that exercises the TestableIO call shape end-to-end.
There was a problem hiding this comment.
Pull request overview
This PR extends the System.IO.Abstractions → Testably migration analyzer/code-fix to cover TestableIO’s embedded-resource APIs by flagging embedded-resource calls and (when safe) rewriting bulk namespace loads to InitializeEmbeddedResourcesFromAssembly.
Changes:
- Added new pattern IDs + analyzer detection for
AddFileFromEmbeddedResourceandAddFilesFromEmbeddedNamespace. - Implemented a code fix for
AddFilesFromEmbeddedNamespacethat rewrites toInitializeEmbeddedResourcesFromAssemblywhen the assembly/prefix can be resolved statically, and addsusing Testably.Abstractions.Testing;additively. - Added/expanded tests and a playground embedded resource fixture to validate analyzer + code-fix behavior.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/Testably.Abstractions.Migration.Tests/SystemIOAbstractionsCodeFixProviderTests.AccessorMethodTests.cs | Adds code-fix tests for embedded-resource patterns (auto-fix + manual-review cases). |
| Tests/Testably.Abstractions.Migration.Tests/SystemIOAbstractionsAnalyzerTests.cs | Adds analyzer coverage for embedded-resource method diagnostics. |
| Tests/Testably.Abstractions.Migration.SystemIOAbstractionsPlayground/TestData/sample.txt | Adds a sample embedded resource payload for end-to-end playground parity. |
| Tests/Testably.Abstractions.Migration.SystemIOAbstractionsPlayground/Testably.Abstractions.Migration.SystemIOAbstractionsPlayground.csproj | Embeds the new sample resource in the playground project. |
| Tests/Testably.Abstractions.Migration.SystemIOAbstractionsPlayground/ManualReviewTests.cs | Adds playground tests exercising the TestableIO embedded-resource call shapes. |
| Source/Testably.Abstractions.Migration.Analyzers/SystemIOAbstractionsAnalyzer.cs | Flags embedded-resource API usage with new pattern IDs. |
| Source/Testably.Abstractions.Migration.Analyzers/Patterns.cs | Introduces pattern ID constants and documents their migration expectations. |
| Source/Testably.Abstractions.Migration.Analyzers.CodeFixers/SystemIOAbstractionsCodeFixProvider.cs | Registers/applies the embedded-namespace rewrite and adds EnsureTestablyUsing. |
Comments suppressed due to low confidence (2)
Source/Testably.Abstractions.Migration.Analyzers.CodeFixers/SystemIOAbstractionsCodeFixProvider.cs:1717
EnsureTestablyUsingtreats any using whoseNamestring matchesTestably.Abstractions.Testingas sufficient. If the file already has an alias/static using (e.g.using T = Testably.Abstractions.Testing;), extension methods from that namespace are NOT imported, so the rewrittenInitializeEmbeddedResourcesFromAssemblycall would fail to bind. Consider only treating non-aliased, non-static using directives as satisfying this check (or always adding a normal using when the existing one is aliased/static).
if (compilationUnit.Usings.Any(u => u.Name?.ToString() == TestablyTestingNamespace))
{
return compilationUnit;
}
Source/Testably.Abstractions.Migration.Analyzers.CodeFixers/SystemIOAbstractionsCodeFixProvider.cs:1575
- This rewrite assumes the three arguments are in source-order (
Arguments[0]= path,[1]= assembly,[2]= embeddedResourcePath). If the caller uses named arguments out-of-order, the code fix can produce an incorrect (or even non-compiling)InitializeEmbeddedResourcesFromAssemblycall. Consider either (a) mapping arguments to parameters viaIInvocationOperation.Arguments/Argument.Parameter, or (b) conservatively declining the fix when any of the arguments usesNameColon/NameEquals.
ArgumentSyntax pathArg = invocation.ArgumentList.Arguments[0];
ArgumentSyntax assemblyArg = invocation.ArgumentList.Arguments[1];
if (!TryComputeRelativePathFromAssemblyAndLiteral(
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|



Two new pattern ids cover the TestableIO embedded-resource surface:
Auto-fix conditions for AddFilesFromEmbeddedNamespace:
typeof(SomeType).Assembly(resolved via semantic model) ORAssembly.GetExecutingAssembly()(resolved to Compilation.AssemblyName).Rewrite shape:
fs.AddFilesFromEmbeddedNamespace("/data", typeof(C).Assembly, "MyAsm.TestData")
→ fs.InitializeEmbeddedResourcesFromAssembly("/data", typeof(C).Assembly,
relativePath: "TestData");
Dots in the stripped remainder become forward slashes (Testably normalizes AltDirectorySeparatorChar). Literal == assembly name (with or without trailing dot) drops the
relativePath:argument entirely so the call materializes every embedded resource (matching TestableIO'sStartsWith(<asm-name>)).The fix adds
using Testably.Abstractions.Testing;ADDITIVELY without removing the existing TestingHelpers using — the receiver type stays bound to TestableIO so other call sites in the file keep compiling. New helperEnsureTestablyUsingfor additive insertion (vs the existing destructiveSwapToTestablyUsing).Known semantic difference: TestableIO flattens nested-namespace resources under the target dir; Testably preserves nested structure as subdirectories. Common case (flat resource set) is equivalent; nested cases produce more correct but different output, surfaced through manual inspection of the diff.
Tests cover: analyzer firing on each pattern (3); manual-review codefix for AddFileFromEmbeddedResource; auto-fix happy path for typeof-Assembly, nested namespace path preservation, literal == assembly name (relativePath omitted), GetExecutingAssembly path; manual review for prefix mismatch, non-literal third arg, non-resolvable assembly arg, interface-typed receiver. Playground gains TestData/sample.txt as embedded resource and a parity fixture per pattern that exercises the TestableIO call shape end-to-end.