Skip to main content

migrate-static-to-wrapper

Mechanically replace static dependency call sites with wrapper or built-in abstraction calls across a bounded scope (file, project, or namespace). Performs codemod-style bulk replacement of DateTime.UtcNow to TimeProvider.GetUtcNow(), File.ReadAllText to IFileSystem, and similar transformations. Adds constructor injection parameters and updates DI registration. USE FOR: replace DateTime.Now/UtcNow with TimeProvider, migrate static calls to wrapper, bulk replace File.* with IFileSystem, codemod static to injectable, add constructor injection for a dependency, mechanical or scoped migration of statics, convert static calls to use an abstraction, update call sites. DO NOT USE FOR: detecting statics (use detect-static-dependencies), generating wrappers (use generate-testability-wrappers), migrating between test frameworks.

Source facts

Repository
managedcode/LlmTck
Last source activity
July 9, 2026 at 11:53
Detected SKILL.md language
English
Stars
1
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
migrate-static-to-wrapper
description
Mechanically replace static dependency call sites with wrapper or built-in abstraction calls across a bounded scope (file, project, or namespace). Performs codemod-style bulk replacement of DateTime.UtcNow to TimeProvider.GetUtcNow(), File.ReadAllText to IFileSystem, and similar transformations. Adds constructor injection parameters and updates DI registration. USE FOR: replace DateTime.Now/UtcNow with TimeProvider, migrate static calls to wrapper, bulk replace File.* with IFileSystem, codemod static to injectable, add constructor injection for a dependency, mechanical or scoped migration of statics, convert static calls to use an abstraction, update call sites. DO NOT USE FOR: detecting statics (use detect-static-dependencies), generating wrappers (use generate-testability-wrappers), migrating between test frameworks.
license
MIT
# Migrate Static to Wrapper Perform mechanical, codemod-style replacement of static dependency call sites with calls to injected wrapper interfaces or built-in abstractions. Operates on a bounded scope (single file, project, or namespace) so migrations can be done incrementally. ## When to Use - After wrappers have been generated (via `generate-testability-wrappers`) or built-in abstractions identified - Migrating `DateTime.UtcNow` → `TimeProvider.GetUtcNow()` across a project - Migrating `File.*` → `IFileSystem.File.*` across a namespace - Adding constructor injection for the new abstraction to affected classes - Incremental migration: one project or namespace at a time ## When Not to Use - No wrapper or abstraction exists yet (use `generate-testability-wrappers` first) - The user wants to detect statics, not migrate them (use `detect-static-dependencies`) - The code does not use dependency injection and the user hasn't chosen ambient context - Migrating between test frameworks (use the appropriate migration skill) ## Inputs | Input | Required | Description | |-------|----------|-------------| | Static pattern | Yes | What to replace (e.g., `DateTime.UtcNow`, `File.ReadAllText`) | | Replacement abstraction | Yes | What to use instead (e.g., `TimeProvider`, `IFileSystem`) | | Scope | Yes | File path, project (.csproj), namespace, or directory to migrate | | Injection strategy | No | `constructor` (default), `primary-constructor`, or `ambient` | ## Workflow ### Step 1: Verify prerequisites Before modifying any code: 1. **Confirm the wrapper/abstraction exists**: Check that the interface or built-in abstraction is available in the project. For `TimeProvider`, verify the target framework is .NET 8+ or `Microsoft.Bcl.TimeProvider` is referenced. For `System.IO.Abstractions`, verify the NuGet package is referenced. 2. **Confirm DI registration exists**: Check `Program.cs` or `Startup.cs` for the service registration. If missing, add it before proceeding. 3. **Identify all files in scope**: List the `.cs` files that will be modified. Exclude test projects, `obj/`, `bin/`, and generated code. ### Step 2: Plan the migration for each file For each file containing the static pattern, determine: 1. **Which class(es) contain the call sites** — identify the class declarations 2. **Whether the class already has the dependency injected** — check constructors for existing `TimeProvider`, `IFileSystem`, etc. parameters 3. **The replacement expression** for each call site #### Replacement mapping | Category | Original | DI replacement | |----------|----------|----------------| | Time | `DateTime.Now` | `_timeProvider.GetLocalNow().DateTime` | | Time | `DateTime.UtcNow` | `_timeProvider.GetUtcNow().DateTime` | | Time | `DateTime.Today` | `_timeProvider.GetLocalNow().Date` | | Time | `DateTimeOffset.UtcNow` | `_timeProvider.GetUtcNow()` | | File | `File.ReadAllText(path)` | `_fileSystem.File.ReadAllText(path)` | | File | `File.WriteAllText(path, text)` | `_fileSystem.File.WriteAllText(path, text)` | | File | `File.Exists(path)` | `_fileSystem.File.Exists(path)` | | File | `Directory.Exists(path)` | `_fileSystem.Directory.Exists(path)` | | Env | `Environment.GetEnvironmentVariable(name)` | `_env.GetEnvironmentVariable(name)` | | Console | `Console.WriteLine(msg)` | `_console.WriteLine(msg)` | | Process | `Process.Start(info)` | `_processRunner.Start(info)` | Apply the same pattern for other members in each category. ### Step 3: Add constructor injection Add the new dependency following the class's existing pattern: - **Primary constructor** (C# 12+): Add parameter to primary constructor: `public class OrderProcessor(ILogger<OrderProcessor> logger, TimeProvider timeProvider)` - **Traditional constructor**: Add `private readonly` field + constructor parameter, matching the existing field naming convention (`_camelCase` or `m_camelCase`) ### Step 4: Replace call sites Perform each replacement mechanically. For each call site: 1. Replace the static call with the wrapper call 2. Preserve the surrounding code structure (whitespace, comments, chaining) 3. Add required `using` directives if not already present #### Adding using directives | Abstraction | Using directive | |------------|-----------------| | `TimeProvider` | None (in `System` namespace) | | `IFileSystem` | `using System.IO.Abstractions;` | | `IHttpClientFactory` | `using System.Net.Http;` (usually already present) | | Custom wrappers | `using <wrapper namespace>;` | ### Step 5: Update affected test files If test files exist for the migrated classes: 1. **Update constructor calls** — add the new parameter to test class instantiation 2. **Use test doubles**: - `TimeProvider` → `new FakeTimeProvider()` from `Microsoft.Extensions.TimeProvider.Testing` - `IFileSystem` → `new MockFileSystem()` from `System.IO.Abstractions.TestingHelpers` - Custom wrappers → `new Mock<IWrapperName>()` or hand-rolled fake ### Step 6: Build verification After all changes in the current scope: ```bash dotnet build <project.csproj> ``` If the build fails: - **Missing using**: Add the required `using` directive - **Missing NuGet package**: Run `dotnet add package <name>` - **Constructor mismatch in tests**: Update test instantiation (Step 5) - **Ambiguous call**: Fully qualify the wrapper call ### Step 7: Report changes Summarize what was done: ``` ## Migration Summary **Pattern**: DateTime.UtcNow → TimeProvider.GetUtcNow() **Scope**: MyProject/Services/ ### Files Modified (production) | File | Call Sites Replaced | Injection Added | |------|--------------------:|:----------------| | OrderProcessor.cs | 3 | Yes (constructor) | | NotificationService.cs | 1 | Yes (primary ctor) | ### Files Modified (tests) | File | Change | |------|--------| | OrderProcessorTests.cs | Added FakeTimeProvider parameter | ### Remaining (out of scope) - MyProject/Older/ — 8 call sites not migrated (different namespace) ``` ## Validation - [ ] All call sites in scope were replaced (none missed) - [ ] Constructor injection added to all affected classes - [ ] Field naming follows existing class conventions - [ ] Required `using` directives added - [ ] Required NuGet packages referenced - [ ] Build succeeds after migration - [ ] Test files updated with appropriate test doubles - [ ] No behavioral changes introduced (wrapper delegates directly to the static) ## Common Pitfalls | Pitfall | Solution | |---------|----------| | Replacing statics in test code | Only replace in production code; tests should use fakes/mocks | | Breaking static classes | Static classes can't have constructors — use ambient context for these | | Missing `FakeTimeProvider` NuGet | Add `Microsoft.Extensions.TimeProvider.Testing` to test project | | Replacing in expression-bodied members without updating return type | `DateTime` → `DateTimeOffset` when using `TimeProvider.GetUtcNow()` — verify type compatibility | | Migrating too much at once | Stick to the defined scope — one project or namespace per run | | Forgetting DI registration | Always verify `Program.cs`/`Startup.cs` has the registration before replacing call sites |
View on GitHub