diff --git a/Source/Testably.Abstractions.Migration.Analyzers/Patterns.cs b/Source/Testably.Abstractions.Migration.Analyzers/Patterns.cs
index 1db5a3e..49d16c8 100644
--- a/Source/Testably.Abstractions.Migration.Analyzers/Patterns.cs
+++ b/Source/Testably.Abstractions.Migration.Analyzers/Patterns.cs
@@ -57,6 +57,19 @@ public static class Patterns
///
public const string MockFileSystemAddDrive = "MockFileSystem.AddDrive";
+ ///
+ /// fs.MockTime(Func<DateTime>). Manual review (Phase 5.3): TestableIO
+ /// calls the supplied delegate on every timestamp request, while Testably installs
+ /// a fixed-then-mutable MockTimeSystem at construction. The two have no
+ /// observably-equivalent automatic rewrite for arbitrary delegates, and the
+ /// equivalent surface (o => o.UseTimeSystem(...)) lives in the
+ /// MockFileSystemOptions lambda — a cross-statement fold that conflicts
+ /// with the parameterless / options-ctor fixes when both touch the construction.
+ /// A future sub-phase may add an opt-in fix for the narrow constant-DateTime
+ /// lambda shape with a custom FixAllProvider.
+ ///
+ public const string MockFileSystemMockTime = "MockFileSystem.MockTime";
+
// ── Enumeration properties (Phase 5.1) ────────────────────────────────
// These IMockFileDataAccessor properties enumerate the whole mocked file
// system. Testably has no direct equivalent — the natural replacements
diff --git a/Source/Testably.Abstractions.Migration.Analyzers/SystemIOAbstractionsAnalyzer.cs b/Source/Testably.Abstractions.Migration.Analyzers/SystemIOAbstractionsAnalyzer.cs
index 63932a5..3b7c129 100644
--- a/Source/Testably.Abstractions.Migration.Analyzers/SystemIOAbstractionsAnalyzer.cs
+++ b/Source/Testably.Abstractions.Migration.Analyzers/SystemIOAbstractionsAnalyzer.cs
@@ -314,6 +314,7 @@ private static bool IsMockFileSystemOptions(IParameterSymbol parameter)
"MoveDirectory" => Patterns.AccessorMoveDirectory,
"FileExists" => Patterns.AccessorFileExists,
"AddDrive" => Patterns.MockFileSystemAddDrive,
+ "MockTime" => Patterns.MockFileSystemMockTime,
_ => null,
};
diff --git a/Tests/Testably.Abstractions.Migration.SystemIOAbstractionsPlayground/ManualReviewTests.cs b/Tests/Testably.Abstractions.Migration.SystemIOAbstractionsPlayground/ManualReviewTests.cs
index 2d268f3..a7f9479 100644
--- a/Tests/Testably.Abstractions.Migration.SystemIOAbstractionsPlayground/ManualReviewTests.cs
+++ b/Tests/Testably.Abstractions.Migration.SystemIOAbstractionsPlayground/ManualReviewTests.cs
@@ -84,6 +84,21 @@ public async Task MockFileData_CopyConstructor_ClonesTextContents()
await That(clone.TextContents).IsEqualTo("hello");
}
+ [Fact]
+ public async Task MockFileSystem_MockTime_ReturnsSelfForFluentChaining()
+ {
+ // TestableIO calls the supplied delegate every time it needs a timestamp.
+ // Testably installs a fixed-then-mutable MockTimeSystem at construction with
+ // no equivalent post-construction fluent API, so this site is reported with
+ // pattern id `MockFileSystem.MockTime` and left for manual migration. The
+ // playground only needs to keep the call shape compiling; the timestamp
+ // semantics of MockTime are out of scope for the parity baseline.
+ MockFileSystem fs = new();
+ MockFileSystem chained = fs.MockTime(() => DateTime.UnixEpoch);
+
+ await That(chained).IsSameAs(fs);
+ }
+
private sealed class MyMockFs : MockFileSystem
{
}
diff --git a/Tests/Testably.Abstractions.Migration.Tests/SystemIOAbstractionsAnalyzerTests.cs b/Tests/Testably.Abstractions.Migration.Tests/SystemIOAbstractionsAnalyzerTests.cs
index 64d965d..48dddb1 100644
--- a/Tests/Testably.Abstractions.Migration.Tests/SystemIOAbstractionsAnalyzerTests.cs
+++ b/Tests/Testably.Abstractions.Migration.Tests/SystemIOAbstractionsAnalyzerTests.cs
@@ -142,6 +142,25 @@ await Verifier.VerifyAnalyzerAsync(
Verifier.Diagnostic(Rules.SystemIOAbstractionsRule).WithLocation(0));
}
+ [Fact]
+ public async Task MockTime_ShouldBeFlagged()
+ {
+ const string source = """
+ using System;
+ using System.IO.Abstractions.TestingHelpers;
+
+ public class C
+ {
+ public void Run(MockFileSystem fs)
+ => {|#0:fs.MockTime(() => DateTime.UnixEpoch)|};
+ }
+ """;
+
+ await Verifier.VerifyAnalyzerAsync(
+ source,
+ Verifier.Diagnostic(Rules.SystemIOAbstractionsRule).WithLocation(0));
+ }
+
[Theory]
[InlineData("AllPaths")]
[InlineData("AllFiles")]
diff --git a/Tests/Testably.Abstractions.Migration.Tests/SystemIOAbstractionsCodeFixProviderTests.AccessorMethodTests.cs b/Tests/Testably.Abstractions.Migration.Tests/SystemIOAbstractionsCodeFixProviderTests.AccessorMethodTests.cs
index c1d8540..23ec82c 100644
--- a/Tests/Testably.Abstractions.Migration.Tests/SystemIOAbstractionsCodeFixProviderTests.AccessorMethodTests.cs
+++ b/Tests/Testably.Abstractions.Migration.Tests/SystemIOAbstractionsCodeFixProviderTests.AccessorMethodTests.cs
@@ -705,5 +705,38 @@ await Verifier.VerifyCodeFixAsync(
Verifier.Diagnostic(Rules.SystemIOAbstractionsRule).WithLocation(0),
source);
}
+
+ [Theory]
+ [InlineData("() => System.DateTime.UnixEpoch")]
+ [InlineData("() => System.DateTime.Now")]
+ [InlineData("() => System.DateTime.UtcNow")]
+ [InlineData("dateTimeProvider")]
+ public async Task MockTime_HasNoFix(string argument)
+ {
+ // Phase 5.3 ships MockTime as manual review only. TestableIO calls the
+ // supplied delegate every time it needs a timestamp; Testably installs a
+ // fixed-then-mutable MockTimeSystem at construction. The two have no
+ // observably-equivalent automatic rewrite for arbitrary delegates, and the
+ // equivalent surface (`o => o.UseTimeSystem(...)`) lives inside the
+ // MockFileSystemOptions lambda — a cross-statement fold that conflicts
+ // with the parameterless / options-ctor fixes when both touch the same
+ // construction. A future sub-phase may opt-in fix the narrow constant-
+ // DateTime lambda shape with a custom FixAllProvider.
+ string source = $$"""
+ using System;
+ using System.IO.Abstractions.TestingHelpers;
+
+ public class C
+ {
+ public void Run(MockFileSystem fs, Func dateTimeProvider)
+ => {|#0:fs.MockTime({{argument}})|};
+ }
+ """;
+
+ await Verifier.VerifyCodeFixAsync(
+ source,
+ Verifier.Diagnostic(Rules.SystemIOAbstractionsRule).WithLocation(0),
+ source);
+ }
}
}