diff --git a/Directory.Build.props b/Directory.Build.props index 1d97b3a..78e818a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,7 +8,7 @@ SPDX-License-Identifier: MIT 2.0.0 MIT - 2024-2026 TruePath contributors <https://github.com/ForNeVeR/TruePath>; .NET Foundation and Contributors + 2024-2026 TruePath contributors <https://github.com/ForNeVeR/TruePath>; .NET Foundation and Contributors README.md true true diff --git a/TruePath.PathIoGenerator.Tests/PathIoGeneratorTests.cs b/TruePath.PathIoGenerator.Tests/PathIoGeneratorTests.cs index c344d76..9007379 100644 --- a/TruePath.PathIoGenerator.Tests/PathIoGeneratorTests.cs +++ b/TruePath.PathIoGenerator.Tests/PathIoGeneratorTests.cs @@ -4,6 +4,8 @@ using System.Collections.Immutable; using System.Text; +using System.Xml; +using Basic.Reference.Assemblies; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; @@ -43,7 +45,7 @@ public static partial class PathIo } """; - public static string SourcesWithHandWrittenMembers(string members) => DefaultSources + public static string SourcesWithUserDefinedMembers(string members) => DefaultSources .Replace( """ [GenerateSystemIoExtensions] @@ -65,16 +67,16 @@ public static CSharpCompilation CreateCompilation(string sources) // reliable on CI, where the test host runtime and the SDK with the packs may live in different roots. return CSharpCompilation.Create( "TestAssembly", - new[] { CSharpSyntaxTree.ParseText(sources, new CSharpParseOptions(LanguageVersion.Latest)) }, - Basic.Reference.Assemblies.Net100.References.All, + [CSharpSyntaxTree.ParseText(sources, new CSharpParseOptions(LanguageVersion.Latest))], + Net100.References.All, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); } public static string Generate(CSharpCompilation compilation, params AdditionalText[] additionalTexts) { GeneratorDriver driver = CSharpGeneratorDriver.Create( - new[] { new PathIoSourceGenerator().AsSourceGenerator() }, - additionalTexts.ToImmutableArray()); + [new PathIoSourceGenerator().AsSourceGenerator()], + [.. additionalTexts]); driver = driver.RunGenerators(compilation); var runResult = driver.GetRunResult(); var tree = runResult.GeneratedTrees.FirstOrDefault(generated => generated.FilePath.Contains("PathIo.Generated")); @@ -117,21 +119,51 @@ public void PureForwardMethodIsGenerated() public void BclDocIndexFindsBclMembers() { var index = BclDocIndex.Build( - ImmutableArray.Create(new InMemoryAdditionalText("/ref/System.Runtime.xml", BclDocs))); + [new InMemoryAdditionalText("/ref/System.Runtime.xml", BclDocs)]); Assert.True(index.TryGetMethodMember("M:System.IO.File.ReadAllText(System.String)", out var member)); Assert.Contains("OPENS A TEXT FILE, READS ALL THE TEXT IN THE FILE.", member.ToString()); } + private const string BrokenBclDocs = """ + + + + OPENS A TEXT FILE, READS ALL THE TEXT IN THE FILE. + + + Broken + + + + """; + + [Fact] + public void BclDocIndexToleratesBrokenMembersInNetStandardDoc() + { + var index = BclDocIndex.Build( + [new InMemoryAdditionalText("/ref/netstandard.xml", BrokenBclDocs)]); + + Assert.True(index.TryGetMethodMember("M:System.IO.File.ReadAllText(System.String)", out _)); + Assert.False(index.TryGetMethodMember("M:System.IO.File.Delete(System.String)", out _)); + } + + [Fact] + public void BclDocIndexRejectsInvalidXmlInOtherDocs() + { + Assert.Throws(() => BclDocIndex.Build( + [new InMemoryAdditionalText("/ref/System.Runtime.xml", BrokenBclDocs)])); + } + [Fact] public void CoreUsesBclDocIndexDirectly() { var compilation = TestHelper.CreateCompilation(TestHelper.DefaultSources); var index = BclDocIndex.Build( - ImmutableArray.Create(new InMemoryAdditionalText("/ref/System.Runtime.xml", BclDocs))); + [new InMemoryAdditionalText("/ref/System.Runtime.xml", BclDocs)]); var result = PathIoGeneratorCore.Generate( - compilation, "TruePath.SystemIo.PathIo", markedTypeIsNested: false, HandWrittenInfo.Empty, + compilation, "TruePath.SystemIo.PathIo", markedTypeIsNested: false, UserDefinedMembersInfo.Empty, ImmutableHashSet.Empty, index); Assert.Contains("/// OPENS A TEXT FILE, READS ALL THE TEXT IN THE FILE.", result.Source); @@ -166,7 +198,7 @@ public static partial class PathIo { } """; var compilation = TestHelper.CreateCompilation(sources); GeneratorDriver driver = CSharpGeneratorDriver.Create( - new[] { new PathIoSourceGenerator().AsSourceGenerator() }, + [new PathIoSourceGenerator().AsSourceGenerator()], ImmutableArray.Empty); driver = driver.RunGenerators(compilation, TestContext.Current.CancellationToken); @@ -196,26 +228,26 @@ public void FallbackDocumentationIsUsedWhenNoXmlIsAvailable() } [Fact] - public void HandWrittenSignatureSuppressesGeneration() + public void UserDefinedSignatureSuppressesGeneration() { const string members = """ public static bool Exists(TruePath.AbsolutePath path) => true; """; var source = TestHelper.Generate( - TestHelper.CreateCompilation(TestHelper.SourcesWithHandWrittenMembers(members))); + TestHelper.CreateCompilation(TestHelper.SourcesWithUserDefinedMembers(members))); Assert.DoesNotContain("Exists(", source); } [Fact] - public void HandWrittenForwardingCallSuppressesItsBclTarget() + public void UserDefinedForwardingCallSuppressesItsBclTarget() { const string members = """ public static void DeleteEmptyDirectory(TruePath.AbsolutePath path) => System.IO.Directory.Delete(path.Value, recursive: false); public static void DeleteDirectoryRecursively(TruePath.AbsolutePath path) => System.IO.Directory.Delete(path.Value, recursive: true); """; var source = TestHelper.Generate( - TestHelper.CreateCompilation(TestHelper.SourcesWithHandWrittenMembers(members))); + TestHelper.CreateCompilation(TestHelper.SourcesWithUserDefinedMembers(members))); Assert.DoesNotContain("Delete(AbsolutePath path, bool", source); Assert.Contains( @@ -236,7 +268,7 @@ public static TruePath.AbsolutePath[] GetFiles(TruePath.AbsolutePath path, TrueP """; var source = TestHelper.Generate( TestHelper.CreateCompilation( - TestHelper.SourcesWithHandWrittenMembers(members) + TestHelper.SourcesWithUserDefinedMembers(members) .Replace( " public readonly struct AbsolutePath", " public record struct LocalPathPattern(string Value);\n public readonly struct AbsolutePath"))); @@ -249,13 +281,13 @@ public static TruePath.AbsolutePath[] GetFiles(TruePath.AbsolutePath path, TrueP [Fact] public void ForwardingCallWithPinnedTrailingArgumentSuppressesItsBclOverload() { - // The parameter list of the handwritten member is the leading part of Directory.Delete(string, bool), which + // The parameter list of the user-defined member is the leading part of Directory.Delete(string, bool), which // makes it a specialization of that overload, so the generated Delete(AbsolutePath, bool) must not appear. const string members = """ public static void DeleteEmptyDirectory(TruePath.AbsolutePath path) => System.IO.Directory.Delete(path.Value, recursive: false); """; var source = TestHelper.Generate( - TestHelper.CreateCompilation(TestHelper.SourcesWithHandWrittenMembers(members))); + TestHelper.CreateCompilation(TestHelper.SourcesWithUserDefinedMembers(members))); Assert.DoesNotContain("public static void Delete(this AbsolutePath path, bool", source); } @@ -332,7 +364,7 @@ public void GeneratedCodeCompilesWithoutErrors() { var compilation = TestHelper.CreateCompilation(TestHelper.DefaultSources); GeneratorDriver driver = CSharpGeneratorDriver.Create( - new[] { new PathIoSourceGenerator().AsSourceGenerator() }, + [new PathIoSourceGenerator().AsSourceGenerator()], ImmutableArray.Empty); driver = driver.RunGenerators(compilation, TestContext.Current.CancellationToken); var updated = compilation.AddSyntaxTrees(driver.GetRunResult().GeneratedTrees); diff --git a/TruePath.PathIoGenerator/BclDocIndex.cs b/TruePath.PathIoGenerator/BclDocIndex.cs index b097955..5b1a209 100644 --- a/TruePath.PathIoGenerator/BclDocIndex.cs +++ b/TruePath.PathIoGenerator/BclDocIndex.cs @@ -5,8 +5,10 @@ using System.Collections.Immutable; using System.Text; using System.Text.RegularExpressions; +using System.Xml; using System.Xml.Linq; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; namespace TruePath.PathIoGenerator; @@ -35,7 +37,7 @@ public bool TryGetMethodMember(string docId, out XElement member) public static BclDocIndex Build(ImmutableArray files) { // netstandard.xml is processed first, so that the framework-specific System.Runtime.xml wins on conflicts. - var orderedFiles = files.OrderBy(file => Path.GetFileName(file.Path) == "netstandard.xml" ? 0 : 1); + var orderedFiles = files.OrderBy(file => IsNetStandardDoc(file) ? 0 : 1); var builder = ImmutableDictionary.CreateBuilder(); foreach (var file in orderedFiles) @@ -43,52 +45,87 @@ public static BclDocIndex Build(ImmutableArray files) var text = file.GetText(); if (text == null) continue; - // Some of the BCL documentation files (netstandard.xml in particular) contain invalid XML, so member - // elements are extracted line by line and parsed one by one: a broken member is then skipped instead - // of failing the whole file. - var current = new StringBuilder(); - string? currentName = null; - foreach (var textLine in text.Lines) + if (IsNetStandardDoc(file)) + ExtractMembersLeniently(text, builder); + else + ExtractMembers(text, builder); + } + + return new BclDocIndex(files, builder.ToImmutable()); + } + + private const string NetStandardDocFileName = "netstandard.xml"; + + private static bool IsNetStandardDoc(AdditionalText file) + { + return Path.GetFileName(file.Path) == NetStandardDocFileName; + } + + private static bool IsIndexedMember(string name) + { + return name.StartsWith("M:System.IO.File.", StringComparison.Ordinal) + || name.StartsWith("M:System.IO.Directory.", StringComparison.Ordinal); + } + + private static void ExtractMembers(SourceText text, ImmutableDictionary.Builder builder) + { + var document = XDocument.Parse(text.ToString()); + foreach (var member in document.Descendants("member")) + { + var name = member.Attribute("name")?.Value; + if (name == null || !IsIndexedMember(name)) continue; + + builder[name] = member; + } + } + + private static void ExtractMembersLeniently( + SourceText text, + ImmutableDictionary.Builder builder) + { + // netstandard.xml contains invalid XML, so member elements are extracted line by line and parsed one by one: + // a broken member is then skipped instead of failing the whole file. + // + // See https://github.com/dotnet/standard/issues/1527 on the topic of the broken XML doc. + var current = new StringBuilder(); + string? currentName = null; + foreach (var textLine in text.Lines) + { + var line = textLine.ToString(); + var trimmed = line.TrimStart(); + if (currentName == null) { - var line = textLine.ToString(); - var trimmed = line.TrimStart(); - if (currentName == null) - { - if (!trimmed.StartsWith("", StringComparison.Ordinal)) + { + try { - current.Append('\n').Append(line); + builder[currentName] = XElement.Parse(current.ToString()); } - - if (trimmed.TrimEnd().EndsWith("", StringComparison.Ordinal)) + catch (XmlException) { - try - { - builder[currentName] = XElement.Parse(current.ToString()); - } - catch (System.Xml.XmlException) - { - // Skip a malformed member. - } - - currentName = null; + // Skip a malformed member. } + + currentName = null; } } - - return new BclDocIndex(files, builder.ToImmutable()); } public override bool Equals(object? obj) diff --git a/TruePath.PathIoGenerator/Exclusions.cs b/TruePath.PathIoGenerator/Exclusions.cs index fdb1bae..ee9de85 100644 --- a/TruePath.PathIoGenerator/Exclusions.cs +++ b/TruePath.PathIoGenerator/Exclusions.cs @@ -9,8 +9,8 @@ namespace TruePath.PathIoGenerator; /// /// The parsed contents of the path-io-exclusions.txt additional file: XML documentation IDs of the BCL -/// methods that must not be exposed as PathIo extensions. Methods covered by handwritten members (either by -/// rewritten signature or by the forwarding call inside the handwritten body) are excluded automatically and +/// methods that must not be exposed as PathIo extensions. Methods covered by user-defined members (either by +/// rewritten signature or by the forwarding call inside their body) are excluded automatically and /// must not be listed there. /// internal sealed class Exclusions diff --git a/TruePath.PathIoGenerator/MarkedType.cs b/TruePath.PathIoGenerator/MarkedType.cs index 150ebdc..de52940 100644 --- a/TruePath.PathIoGenerator/MarkedType.cs +++ b/TruePath.PathIoGenerator/MarkedType.cs @@ -6,21 +6,21 @@ namespace TruePath.PathIoGenerator; /// /// The equatable payload the pipeline computes for every marked class: its display name, whether it is nested -/// (rejected by with a diagnostic), and the hand-written member +/// (rejected by with a diagnostic), and the user-defined member /// analysis. Being equatable lets the incremental pipeline cache it across unrelated edits. /// -internal sealed class MarkedType(string typeName, bool isNested, HandWrittenInfo handWritten) +internal sealed class MarkedType(string typeName, bool isNested, UserDefinedMembersInfo userDefinedMembers) { public string TypeName { get; } = typeName; public bool IsNested { get; } = isNested; - public HandWrittenInfo HandWritten { get; } = handWritten; + public UserDefinedMembersInfo UserDefinedMembers { get; } = userDefinedMembers; public override bool Equals(object? obj) { return obj is MarkedType other && TypeName == other.TypeName && IsNested == other.IsNested - && HandWritten.Equals(other.HandWritten); + && UserDefinedMembers.Equals(other.UserDefinedMembers); } public override int GetHashCode() diff --git a/TruePath.PathIoGenerator/PathIoGeneratorCore.cs b/TruePath.PathIoGenerator/PathIoGeneratorCore.cs index f2b7051..b33e66b 100644 --- a/TruePath.PathIoGenerator/PathIoGeneratorCore.cs +++ b/TruePath.PathIoGenerator/PathIoGeneratorCore.cs @@ -27,8 +27,8 @@ public sealed class GenerationResult(string? source, ImmutableArray /// A BCL method is not generated when: /// /// its ID is listed in path-io-exclusions.txt; -/// a handwritten member of the marked class already occupies the same rewritten signature; -/// a handwritten member forwards to it (detected by the forwarding call in its body); +/// a user-defined member of the marked class already occupies the same rewritten signature; +/// a user-defined member forwards to it (detected by the forwarding call in its body); /// another BCL method (File wins over Directory) already produced the same signature. /// /// @@ -37,16 +37,16 @@ public static class PathIoGeneratorCore private const string GeneratorName = "TruePath.PathIoGenerator"; private const string GeneratorVersion = "1.0.0"; - private static readonly ImmutableHashSet ForwardedAttributes = ImmutableHashSet.Create( + private static readonly ImmutableHashSet ForwardedAttributes = [ "System.Runtime.Versioning.SupportedOSPlatformAttribute", "System.Runtime.Versioning.UnsupportedOSPlatformAttribute", - "System.ObsoleteAttribute"); + "System.ObsoleteAttribute"]; public static GenerationResult Generate( Compilation compilation, string markedTypeMetadataName, bool markedTypeIsNested, - HandWrittenInfo handWritten, + UserDefinedMembersInfo userDefinedMembers, ImmutableHashSet excludedDocIds, BclDocIndex docIndex) { @@ -87,7 +87,7 @@ public static GenerationResult Generate( var renderer = TypeRenderer.Create(compilation); var generatedMethods = new List(); var generatedKeys = new HashSet(); - var skippedWithoutPath = new SortedSet(); + var skippedWithoutPath = new SortedSet(StringComparer.Ordinal); foreach (var owner in new[] { fileType!, directoryType! }) // File first, so File wins on duplicate signatures foreach (var method in owner.GetMembers().OfType()) @@ -100,7 +100,7 @@ public static GenerationResult Generate( continue; var docId = DocId.ForMethod(method); - if (excludedDocIds.Contains(docId) || handWritten.ForwardedDocIds.Contains(docId)) continue; + if (excludedDocIds.Contains(docId) || userDefinedMembers.ForwardedDocIds.Contains(docId)) continue; var pathOrdinals = method.Parameters .Where(p => p.Type.SpecialType == SpecialType.System_String && PathIoRules.IsPathParameterName(p.Name)) @@ -113,7 +113,7 @@ public static GenerationResult Generate( } var key = SignatureKey(method.Name, method.Parameters.Select(p => SignatureTypeDisplay(p, pathOrdinals, absolutePathType!, renderer))); - if (handWritten.Signatures.Contains(key)) continue; + if (userDefinedMembers.Signatures.Contains(key)) continue; if (!generatedKeys.Add(key)) continue; generatedMethods.Add(BuildMethod(owner, method, pathOrdinals, absolutePathType!, docIndex, renderer)); @@ -132,16 +132,18 @@ private static bool IsDeclaredPartial(INamedTypeSymbol type) } /// - /// Collects the exclusion-relevant facts about the hand-written members of the marked type: their rewritten + /// Collects the exclusion-relevant facts about the user-defined members of the marked type: their rewritten /// signatures, and the IDs of the BCL overloads they forward to. Called from the syntax-anchored part of /// the incremental pipeline, so it only re-runs when the marked type's syntax changes. /// - public static HandWrittenInfo CollectHandWrittenInfo(Compilation compilation, INamedTypeSymbol markedType) + public static UserDefinedMembersInfo CollectUserDefinedMembersInfo( + Compilation compilation, + INamedTypeSymbol markedType) { var fileType = compilation.GetTypeByMetadataName("System.IO.File"); var directoryType = compilation.GetTypeByMetadataName("System.IO.Directory"); var absolutePathType = compilation.GetTypeByMetadataName(PathIoRules.AbsolutePathTypeName); - if (fileType == null || directoryType == null || absolutePathType == null) return HandWrittenInfo.Empty; + if (fileType == null || directoryType == null || absolutePathType == null) return UserDefinedMembersInfo.Empty; var renderer = TypeRenderer.Create(compilation); var signatures = new HashSet(); @@ -172,13 +174,13 @@ public static HandWrittenInfo CollectHandWrittenInfo(Compilation compilation, IN } } - return new HandWrittenInfo( + return new UserDefinedMembersInfo( signatures.OrderBy(signature => signature, StringComparer.Ordinal).ToImmutableArray(), forwardedDocIds.OrderBy(docId => docId, StringComparer.Ordinal).ToImmutableArray()); } /// - /// Decides whether a handwritten member covers the BCL overload it forwards to. A member covers the overload + /// Decides whether a user-defined member covers the BCL overload it forwards to. A member covers the overload /// when its own parameter list is the leading part of the overload's generated parameter list: such a member /// is that overload with its trailing arguments pinned by the forwarding call (e.g. /// DeleteEmptyDirectory pins recursive: false of Directory.Delete(string, bool)). @@ -221,7 +223,7 @@ private static ImmutableHashSet PathParameterOrdinals(IMethodSymbol method) } /// - /// Matches the forwarding pattern `SomeBclCall(path.Value, …)` where `path` is a parameter of the handwritten + /// Matches the forwarding pattern `SomeBclCall(path.Value, …)` where `path` is a parameter of the user-defined /// method typed as AbsolutePath. Anything less direct is deliberately not treated as a forwarding call, /// so that incidental BCL calls inside custom logic cannot silently suppress generation. /// @@ -394,13 +396,6 @@ private static string RenderFile(INamedTypeSymbol markedType, sb.AppendLine("// Generated by the TruePath.PathIoGenerator source generator. Manual changes will be lost."); sb.AppendLine("// "); sb.AppendLine(); - // REUSE-IgnoreStart: these are string literals for the generated file's header, not this file's tags. - sb.AppendLine("// SPDX-FileCopyrightText: 2025 .NET Foundation and Contributors"); - sb.AppendLine("// SPDX-FileCopyrightText: 2025-2026 TruePath contributors "); - sb.AppendLine("//"); - sb.AppendLine("// SPDX-License-Identifier: MIT"); - // REUSE-IgnoreEnd - sb.AppendLine(); sb.AppendLine("#nullable enable"); sb.AppendLine(); foreach (var importedNamespace in renderer.ImportedNamespaces.OrderBy(namespaceName => namespaceName, StringComparer.Ordinal)) @@ -442,18 +437,19 @@ private static string RenderFile(INamedTypeSymbol markedType, } } -public sealed class HandWrittenInfo( +public sealed class UserDefinedMembersInfo( ImmutableArray signatures, ImmutableArray forwardedDocIds) { - public static HandWrittenInfo Empty { get; } = new(ImmutableArray.Empty, ImmutableArray.Empty); + public static UserDefinedMembersInfo Empty { get; } = + new(ImmutableArray.Empty, ImmutableArray.Empty); public ImmutableArray Signatures { get; } = signatures; public ImmutableArray ForwardedDocIds { get; } = forwardedDocIds; public override bool Equals(object? obj) { - return obj is HandWrittenInfo other + return obj is UserDefinedMembersInfo other && Signatures.SequenceEqual(other.Signatures) && ForwardedDocIds.SequenceEqual(other.ForwardedDocIds); } diff --git a/TruePath.PathIoGenerator/PathIoRules.cs b/TruePath.PathIoGenerator/PathIoRules.cs index 7558d78..410455b 100644 --- a/TruePath.PathIoGenerator/PathIoRules.cs +++ b/TruePath.PathIoGenerator/PathIoRules.cs @@ -33,7 +33,7 @@ public static bool IsPathParameterName(string parameterName) /// /// The parameter name a path parameter gets in the generated API: the BCL `…FileName`/`…DirName` spellings /// lose their `Name` suffix, so that e.g. Copy keeps the sourceFile/destFile parameter - /// names the library used when they were still hand-written. + /// names the library used before these methods were generated via the source generator. /// public static string GeneratedParameterName(string bclParameterName) { diff --git a/TruePath.PathIoGenerator/PathIoSourceGenerator.cs b/TruePath.PathIoGenerator/PathIoSourceGenerator.cs index 421d0a2..b491268 100644 --- a/TruePath.PathIoGenerator/PathIoSourceGenerator.cs +++ b/TruePath.PathIoGenerator/PathIoSourceGenerator.cs @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: MIT -using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -28,7 +27,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) static (attributeContext, _) => new MarkedType( attributeContext.TargetSymbol.ToDisplayString(), attributeContext.TargetSymbol.ContainingType != null, - PathIoGeneratorCore.CollectHandWrittenInfo( + PathIoGeneratorCore.CollectUserDefinedMembersInfo( attributeContext.SemanticModel.Compilation, (INamedTypeSymbol)attributeContext.TargetSymbol))); @@ -57,7 +56,12 @@ public void Initialize(IncrementalGeneratorInitializationContext context) foreach (var typeInfo in typeInfos.Sort((left, right) => string.CompareOrdinal(left.TypeName, right.TypeName))) { var result = PathIoGeneratorCore.Generate( - compilation, typeInfo.TypeName, typeInfo.IsNested, typeInfo.HandWritten, exclusions.DocIds, docIndex); + compilation, + typeInfo.TypeName, + typeInfo.IsNested, + typeInfo.UserDefinedMembers, + exclusions.DocIds, + docIndex); foreach (var diagnostic in result.Diagnostics) productionContext.ReportDiagnostic(diagnostic); if (result.Source is { } source) diff --git a/TruePath.PathIoGenerator/TypeRenderer.cs b/TruePath.PathIoGenerator/TypeRenderer.cs index 317eada..2cc2b8a 100644 --- a/TruePath.PathIoGenerator/TypeRenderer.cs +++ b/TruePath.PathIoGenerator/TypeRenderer.cs @@ -47,9 +47,9 @@ public static TypeRenderer Create(Compilation compilation) public string Render(ITypeSymbol type) => RenderCore(type, includeNullability: true); /// - /// Renders a type for signature identity: overload keys and handwritten-member matching. Nullability is + /// Renders a type for signature identity: overload keys and user-defined-member matching. Nullability is /// omitted because it is not part of overload identity in C#, and because a reference assembly compiled - /// before nullable reference types existed reports every reference type as oblivious. A handwritten + /// before nullable reference types existed reports every reference type as oblivious. A user-defined /// string? and such an oblivious string are one and the same parameter type. /// public string RenderSignature(ITypeSymbol type) => RenderCore(type, includeNullability: false); diff --git a/TruePath.SystemIo/GenerateSystemIoExtensionsAttribute.cs b/TruePath.SystemIo/GenerateSystemIoExtensionsAttribute.cs index c04fc94..a5ae52d 100644 --- a/TruePath.SystemIo/GenerateSystemIoExtensionsAttribute.cs +++ b/TruePath.SystemIo/GenerateSystemIoExtensionsAttribute.cs @@ -8,7 +8,7 @@ namespace TruePath.SystemIo; /// Marks a class as a generation target: the /// TruePath.PathIoGenerator source generator will fill the other partial part with forwarding extension methods /// for every public static method of System.IO.File and System.IO.Directory available in the -/// current target framework. Hand-written members of the marked class win over generated ones. +/// current target framework. User-defined members of the marked class win over generated ones. /// [AttributeUsage(AttributeTargets.Class, Inherited = false)] internal sealed class GenerateSystemIoExtensionsAttribute : Attribute; diff --git a/TruePath.SystemIo/PathIo.cs b/TruePath.SystemIo/PathIo.cs index 9ace60f..4796ec6 100644 --- a/TruePath.SystemIo/PathIo.cs +++ b/TruePath.SystemIo/PathIo.cs @@ -166,7 +166,7 @@ public static IEnumerable EnumerateFiles(this AbsolutePath path, s public static AbsolutePath[] GetFiles(this AbsolutePath path, LocalPathPattern searchPattern) => Directory.GetFiles(path.Value, searchPattern.Value).Select(x => new AbsolutePath(x)).ToArray(); - // These members are handwritten for their nullability contract: the netstandard2.0 reference assembly was compiled + // These members are defined here for their nullability contract: the netstandard2.0 reference assembly was compiled // before nullable reference types existed, so the generator sees every reference type as oblivious and renders it // as non-nullable. The annotations here have to come from source rather than from the generator. diff --git a/TruePath.SystemIo/path-io-exclusions.txt b/TruePath.SystemIo/path-io-exclusions.txt index 9ec03a1..dc82ab6 100644 --- a/TruePath.SystemIo/path-io-exclusions.txt +++ b/TruePath.SystemIo/path-io-exclusions.txt @@ -3,9 +3,9 @@ # One XML documentation ID per line, e.g.: # M:System.IO.Directory.Delete(System.String,System.Boolean) # -# Methods covered by handwritten PathIo members are excluded automatically and -# must not be listed here. This holds both for handwritten members occupying -# the same rewritten signature (e.g. CreateDirectory), and for handwritten +# Methods covered by user-defined PathIo members are excluded automatically and +# must not be listed here. This holds both for user-defined members occupying +# the same rewritten signature (e.g. CreateDirectory), and for user-defined # members whose body forwards to the BCL method with `path.Value` as the first # argument (e.g. DeleteEmptyDirectory/DeleteDirectoryRecursively forwarding to # Directory.Delete(string, bool)). diff --git a/TruePath.Tests/PathIoCompletenessTests.cs b/TruePath.Tests/PathIoCompletenessTests.cs index 210a20b..8c95697 100644 --- a/TruePath.Tests/PathIoCompletenessTests.cs +++ b/TruePath.Tests/PathIoCompletenessTests.cs @@ -13,7 +13,7 @@ namespace TruePath.Tests; /// /// Verifies that every path-taking public static API of and is /// exposed by : either generated by the TruePath.PathIoGenerator source generator, -/// provided by a hand-written member, or deliberately excluded via path-io-exclusions.txt. This mirrors the +/// provided by a user-defined member, or deliberately excluded via path-io-exclusions.txt. This mirrors the /// generator rules, so a drift between them shows up as a failing assertion. /// public class PathIoCompletenessTests @@ -52,9 +52,9 @@ public void EveryPathTakingBclApiIsExposedByPathIo() var docId = DocId(bclMethod); if (exclusions.Contains(docId)) continue; - // Hand-written members that forward to a BCL overload with `path.Value` as the first argument + // User-defined members that forward to a BCL overload with `path.Value` as the first argument // suppress its generation. The generator detects this semantically; here it is approximated on the - // hand-written source text (see PathIoGeneratorCore.CollectHandWrittenInfo). + // source text of the user-defined members (see PathIoGeneratorCore.CollectUserDefinedMembersInfo). if (forwardedCalls.Contains((bclMethod.DeclaringType!.Name, bclMethod.Name, parameters.Length))) continue; var expectedParameterTypes = parameters @@ -71,7 +71,7 @@ public void EveryPathTakingBclApiIsExposedByPathIo() Assert.True( match != null, - $"No PathIo counterpart for {docId}: add a hand-written or generated member, " + $"No PathIo counterpart for {docId}: add a user-defined or generated member, " + "or exclude the method in TruePath.SystemIo/path-io-exclusions.txt."); } } @@ -90,7 +90,7 @@ private static HashSet LoadExclusions() } /// - /// (type, method, argument count) triples of the BCL calls the hand-written PathIo members forward to with + /// (type, method, argument count) triples of the BCL calls the user-defined PathIo members forward to with /// `path.Value` as the first argument. /// private static HashSet<(string TypeName, string MethodName, int ArgumentCount)> LoadForwardedCalls()