diff --git a/Directory.Packages.props b/Directory.Packages.props
index 37f12c935a34..cde36c909357 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -28,6 +28,7 @@
+
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index a968beb23d97..9cf15a52ea6e 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -131,6 +131,10 @@
https://github.com/dotnet/roslyn
d7b0a8c4b320a592e6b81dc5a40bc724cd8b71ba
+
+ https://github.com/dotnet/roslyn
+ d7b0a8c4b320a592e6b81dc5a40bc724cd8b71ba
+
https://github.com/dotnet/roslyn
d7b0a8c4b320a592e6b81dc5a40bc724cd8b71ba
diff --git a/eng/Versions.props b/eng/Versions.props
index b8c6971ef6bc..1f0ab5410739 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -215,6 +215,7 @@
4.13.0-3.25056.21
4.13.0-3.25056.21
4.13.0-3.25056.21
+ 4.13.0-3.25056.21
4.13.0-3.25056.21
4.13.0-3.25056.21
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ApiCompatServiceProvider.cs b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ApiCompatServiceProvider.cs
index b5d95dd8debd..f351aa68e114 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ApiCompatServiceProvider.cs
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ApiCompatServiceProvider.cs
@@ -35,7 +35,7 @@ public ApiCompatServiceProvider(Func logFa
CompositeSymbolFilter attributeDataSymbolFilter = new(accessibilitySymbolFilter);
if (excludeAttributesFiles is not null)
{
- attributeDataSymbolFilter.Add(new DocIdSymbolFilter(excludeAttributesFiles));
+ attributeDataSymbolFilter.Add(DocIdSymbolFilter.CreateFromFiles(excludeAttributesFiles));
}
ApiComparerSettings apiComparerSettings = new(
@@ -49,7 +49,7 @@ public ApiCompatServiceProvider(Func logFa
return new ApiCompatRunner(SuppressibleLog,
SuppressionEngine,
new ApiComparerFactory(ruleFactory(SuppressibleLog), apiComparerSettings),
- new AssemblySymbolLoaderFactory(respectInternals));
+ new AssemblySymbolLoaderFactory(SuppressibleLog, respectInternals));
});
}
diff --git a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI.Task/GenAPITask.cs b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI.Task/GenAPITask.cs
index 6348a3fb8d4d..38fed527c774 100644
--- a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI.Task/GenAPITask.cs
+++ b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI.Task/GenAPITask.cs
@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Build.Framework;
+using Microsoft.CodeAnalysis;
+using Microsoft.DotNet.ApiSymbolExtensions;
using Microsoft.DotNet.ApiSymbolExtensions.Logging;
using Microsoft.NET.Build.Tasks;
@@ -62,17 +64,23 @@ public class GenAPITask : TaskBase
///
protected override void ExecuteCore()
{
- GenAPIApp.Run(new MSBuildLog(Log),
- Assemblies!,
- AssemblyReferences,
- OutputPath,
- HeaderFile,
- ExceptionMessage,
- ExcludeApiFiles,
- ExcludeAttributesFiles,
- RespectInternals,
- IncludeAssemblyAttributes
- );
+ ILog logger = new MSBuildLog(Log);
+ (IAssemblySymbolLoader loader, Dictionary assemblySymbols) = AssemblySymbolLoader.CreateFromFiles(
+ logger,
+ assembliesPaths: Assemblies ?? throw new NullReferenceException("Assemblies cannot be null."),
+ assemblyReferencesPaths: AssemblyReferences,
+ RespectInternals);
+
+ GenAPIApp.Run(logger,
+ loader,
+ assemblySymbols,
+ OutputPath,
+ HeaderFile,
+ ExceptionMessage,
+ ExcludeApiFiles,
+ ExcludeAttributesFiles,
+ RespectInternals,
+ IncludeAssemblyAttributes);
}
}
}
diff --git a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI.Tool/Program.cs b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI.Tool/Program.cs
index dd2edbcbbd94..a401b52166b2 100644
--- a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI.Tool/Program.cs
+++ b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI.Tool/Program.cs
@@ -5,6 +5,8 @@
using System.CommandLine.Parsing;
using System.Diagnostics;
using System.Reflection;
+using Microsoft.CodeAnalysis;
+using Microsoft.DotNet.ApiSymbolExtensions;
using Microsoft.DotNet.ApiSymbolExtensions.Logging;
namespace Microsoft.DotNet.GenAPI.Tool
@@ -97,15 +99,25 @@ static int Main(string[] args)
rootCommand.SetAction((ParseResult parseResult) =>
{
- GenAPIApp.Run(new ConsoleLog(MessageImportance.Normal),
- parseResult.GetValue(assembliesOption)!,
- parseResult.GetValue(assemblyReferencesOption),
+ bool respectInternals = parseResult.GetValue(respectInternalsOption);
+
+ ILog logger = new ConsoleLog(MessageImportance.Normal);
+
+ (IAssemblySymbolLoader loader, Dictionary assemblySymbols) = AssemblySymbolLoader.CreateFromFiles(
+ logger,
+ assembliesPaths: parseResult.GetValue(assembliesOption) ?? throw new NullReferenceException("No assemblies provided."),
+ assemblyReferencesPaths: parseResult.GetValue(assemblyReferencesOption),
+ respectInternals);
+
+ GenAPIApp.Run(logger,
+ loader,
+ assemblySymbols,
parseResult.GetValue(outputPathOption),
parseResult.GetValue(headerFileOption),
parseResult.GetValue(exceptionMessageOption),
parseResult.GetValue(excludeApiFilesOption),
parseResult.GetValue(excludeAttributesFilesOption),
- parseResult.GetValue(respectInternalsOption),
+ respectInternals,
parseResult.GetValue(includeAssemblyAttributesOption)
);
});
diff --git a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/CSharpAssemblyDocumentGenerator.cs b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/CSharpAssemblyDocumentGenerator.cs
new file mode 100644
index 000000000000..7a5b105297c5
--- /dev/null
+++ b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/CSharpAssemblyDocumentGenerator.cs
@@ -0,0 +1,370 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Formatting;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Editing;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.Options;
+using Microsoft.CodeAnalysis.Simplification;
+using Microsoft.DotNet.ApiSymbolExtensions;
+using Microsoft.DotNet.ApiSymbolExtensions.Filtering;
+using Microsoft.DotNet.ApiSymbolExtensions.Logging;
+using Microsoft.DotNet.GenAPI.SyntaxRewriter;
+
+namespace Microsoft.DotNet.GenAPI;
+
+///
+/// A class that generates the C# document and syntax trees of a specified collection of assemblies.
+///
+public sealed class CSharpAssemblyDocumentGenerator
+{
+ private readonly ILog _logger;
+ private readonly IAssemblySymbolLoader _loader;
+ private readonly ISymbolFilter _symbolFilter;
+ private readonly ISymbolFilter _attributeDataSymbolFilter;
+ private readonly string? _exceptionMessage;
+ private readonly bool _includeAssemblyAttributes;
+ private readonly AdhocWorkspace _adhocWorkspace;
+ private readonly SyntaxGenerator _syntaxGenerator;
+ private readonly IEnumerable? _metadataReferences;
+ private readonly bool _addPartialModifier;
+ private readonly bool _hideImplicitDefaultConstructors;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger to use.
+ /// The assembly symbol loader to use.
+ /// The symbol filter to use.
+ /// The attribute data symbol filter to use.
+ /// The optional exception message to use.
+ /// Whether to include assembly attributes or not.
+ /// The metadata references to use. The default value is .
+ /// Whether to add the partial modifier or not. The default value is .
+ /// Whether to hide implicit default constructors or not. The default value is .
+ public CSharpAssemblyDocumentGenerator(ILog logger,
+ IAssemblySymbolLoader loader,
+ ISymbolFilter symbolFilter,
+ ISymbolFilter attributeDataSymbolFilter,
+ string? exceptionMessage,
+ bool includeAssemblyAttributes,
+ IEnumerable? metadataReferences = null,
+ bool addPartialModifier = true,
+ bool hideImplicitDefaultConstructors = true)
+ {
+ _logger = logger;
+ _loader = loader;
+ _symbolFilter = symbolFilter;
+ _attributeDataSymbolFilter = attributeDataSymbolFilter;
+ _exceptionMessage = exceptionMessage;
+ _includeAssemblyAttributes = includeAssemblyAttributes;
+ _adhocWorkspace = new AdhocWorkspace();
+ _syntaxGenerator = SyntaxGenerator.GetGenerator(_adhocWorkspace, LanguageNames.CSharp);
+ _metadataReferences = metadataReferences;
+ _addPartialModifier = addPartialModifier;
+ _hideImplicitDefaultConstructors = hideImplicitDefaultConstructors;
+ }
+
+ ///
+ /// Returns the configured source code document for the specified assembly symbol.
+ ///
+ /// The assembly symbol that represents the loaded assembly.
+ /// The source code document instance of the specified assembly symbol.
+ public Document GetDocumentForAssembly(IAssemblySymbol assemblySymbol)
+ {
+ CSharpCompilationOptions compilationOptions = new(OutputKind.DynamicallyLinkedLibrary,
+ nullableContextOptions: NullableContextOptions.Enable);
+ Project project = _adhocWorkspace.AddProject(ProjectInfo.Create(
+ ProjectId.CreateNewId(), VersionStamp.Create(), assemblySymbol.Name, assemblySymbol.Name, LanguageNames.CSharp,
+ compilationOptions: compilationOptions));
+ project = project.AddMetadataReferences(_metadataReferences ?? _loader.MetadataReferences);
+
+ IEnumerable namespaceSymbols = EnumerateNamespaces(assemblySymbol).Where(_symbolFilter.Include);
+ List namespaceSyntaxNodes = [];
+
+ foreach (INamespaceSymbol namespaceSymbol in namespaceSymbols.Order())
+ {
+ SyntaxNode? syntaxNode = Visit(namespaceSymbol);
+
+ if (syntaxNode is not null)
+ {
+ namespaceSyntaxNodes.Add(syntaxNode);
+ }
+ }
+
+ SyntaxNode compilationUnit = _syntaxGenerator.CompilationUnit(namespaceSyntaxNodes)
+ .WithAdditionalAnnotations(Formatter.Annotation, Simplifier.Annotation)
+ .Rewrite(new TypeDeclarationCSharpSyntaxRewriter(_addPartialModifier))
+ .Rewrite(new BodyBlockCSharpSyntaxRewriter(_exceptionMessage));
+
+ if (_includeAssemblyAttributes)
+ {
+ compilationUnit = GenerateAssemblyAttributes(assemblySymbol, compilationUnit);
+ }
+
+ compilationUnit = GenerateForwardedTypeAssemblyAttributes(assemblySymbol, compilationUnit);
+ compilationUnit = compilationUnit.NormalizeWhitespace(eol: Environment.NewLine);
+
+ Document document = project.AddDocument(assemblySymbol.Name, compilationUnit);
+ document = Simplifier.ReduceAsync(document).Result;
+ document = Formatter.FormatAsync(document, DefineFormattingOptions()).Result;
+
+ return document;
+ }
+
+ ///
+ /// Returns the formatted root syntax node for the specified document.
+ ///
+ /// A source code document instance.
+ /// The root syntax node of the specified document.
+ public SyntaxNode GetFormattedRootNodeForDocument(Document document) => document.GetSyntaxRootAsync().Result!.Rewrite(new SingleLineStatementCSharpSyntaxRewriter());
+
+ private SyntaxNode? Visit(INamespaceSymbol namespaceSymbol)
+ {
+ SyntaxNode namespaceNode = _syntaxGenerator.NamespaceDeclaration(namespaceSymbol.ToDisplayString());
+
+ IEnumerable typeMembers = namespaceSymbol.GetTypeMembers().Where(_symbolFilter.Include);
+ if (!typeMembers.Any())
+ {
+ return null;
+ }
+
+ foreach (INamedTypeSymbol typeMember in typeMembers.Order())
+ {
+ SyntaxNode typeDeclaration = _syntaxGenerator
+ .DeclarationExt(typeMember, _symbolFilter)
+ .AddMemberAttributes(_syntaxGenerator, typeMember, _attributeDataSymbolFilter);
+
+ typeDeclaration = Visit(typeDeclaration, typeMember);
+
+ namespaceNode = _syntaxGenerator.AddMembers(namespaceNode, typeDeclaration);
+ }
+
+ return namespaceNode;
+ }
+
+ // Name hiding through inheritance occurs when classes or structs redeclare names that were inherited from base classes.This type of name hiding takes one of the following forms:
+ // - A constant, field, property, event, or type introduced in a class or struct hides all base class members with the same name.
+ // - A method introduced in a class or struct hides all non-method base class members with the same name, and all base class methods with the same signature(§7.6).
+ // - An indexer introduced in a class or struct hides all base class indexers with the same signature(§7.6) .
+ private bool HidesBaseMember(ISymbol member)
+ {
+ if (member.IsOverride)
+ {
+ return false;
+ }
+
+ if (member.ContainingType.BaseType is not INamedTypeSymbol baseType)
+ {
+ return false;
+ }
+
+ if (member is IMethodSymbol method)
+ {
+ if (method.MethodKind == MethodKind.ExplicitInterfaceImplementation)
+ {
+ return false;
+ }
+
+ // If they're methods, compare their names and signatures.
+ return baseType.GetMembers(member.Name)
+ .Any(baseMember => _symbolFilter.Include(baseMember) &&
+ (baseMember.Kind != SymbolKind.Method ||
+ method.SignatureEquals((IMethodSymbol)baseMember)));
+ }
+ else if (member is IPropertySymbol prop && prop.IsIndexer)
+ {
+ // If they're indexers, compare their signatures.
+ return baseType.GetMembers(member.Name)
+ .Any(baseMember => baseMember is IPropertySymbol baseProperty &&
+ _symbolFilter.Include(baseMember) &&
+ (prop.GetMethod.SignatureEquals(baseProperty.GetMethod) ||
+ prop.SetMethod.SignatureEquals(baseProperty.SetMethod)));
+ }
+ else
+ {
+ // For all other kinds of members, compare their names.
+ return baseType.GetMembers(member.Name)
+ .Any(_symbolFilter.Include);
+ }
+ }
+
+ private SyntaxNode Visit(SyntaxNode namedTypeNode, INamedTypeSymbol namedType)
+ {
+ IEnumerable members = namedType.GetMembers().Where(_symbolFilter.Include);
+
+ // If it's a value type
+ if (namedType.TypeKind == TypeKind.Struct)
+ {
+ namedTypeNode = _syntaxGenerator.AddMembers(namedTypeNode, namedType.SynthesizeDummyFields(_symbolFilter, _attributeDataSymbolFilter));
+ }
+
+ namedTypeNode = _syntaxGenerator.AddMembers(namedTypeNode, namedType.TryGetInternalDefaultConstructor(_symbolFilter));
+
+ foreach (ISymbol member in members.Order())
+ {
+ if (member is IMethodSymbol method)
+ {
+ // If the method is ExplicitInterfaceImplementation and is derived from an interface that was filtered out, we must filter it out as well.
+ if (method.MethodKind == MethodKind.ExplicitInterfaceImplementation &&
+ method.ExplicitInterfaceImplementations.Any(m => !_symbolFilter.Include(m.ContainingSymbol) ||
+ // if explicit interface implementation method has inaccessible type argument
+ m.ContainingType.HasInaccessibleTypeArgument(_symbolFilter)))
+ {
+ continue;
+ }
+
+ // Filter out default constructors since these will be added automatically
+ if (_hideImplicitDefaultConstructors && method.IsImplicitDefaultConstructor(_symbolFilter))
+ {
+ continue;
+ }
+ }
+
+ // If the property is derived from an interface that was filtered out, we must not filter it out either.
+ if (member is IPropertySymbol property && !property.ExplicitInterfaceImplementations.IsEmpty &&
+ property.ExplicitInterfaceImplementations.Any(m => !_symbolFilter.Include(m.ContainingSymbol)))
+ {
+ continue;
+ }
+
+ SyntaxNode memberDeclaration = _syntaxGenerator
+ .DeclarationExt(member, _symbolFilter)
+ .AddMemberAttributes(_syntaxGenerator, member, _attributeDataSymbolFilter);
+
+ if (member is INamedTypeSymbol nestedTypeSymbol)
+ {
+ memberDeclaration = Visit(memberDeclaration, nestedTypeSymbol);
+ }
+
+ if (HidesBaseMember(member))
+ {
+ DeclarationModifiers mods = _syntaxGenerator.GetModifiers(memberDeclaration);
+ memberDeclaration = _syntaxGenerator.WithModifiers(memberDeclaration, mods.WithIsNew(isNew: true));
+ }
+
+ try
+ {
+ namedTypeNode = _syntaxGenerator.AddMembers(namedTypeNode, memberDeclaration);
+ }
+ catch (InvalidOperationException e)
+ {
+ // re-throw the InvalidOperationException with the symbol that caused it.
+ throw new InvalidOperationException(string.Format(Resources.AddMemberThrowsException,
+ member.ToDisplayString(),
+ namedTypeNode,
+ e.Message));
+ }
+ }
+
+ return namedTypeNode;
+ }
+
+ private SyntaxNode GenerateAssemblyAttributes(IAssemblySymbol assembly, SyntaxNode compilationUnit)
+ {
+ // When assembly references aren't available, assembly attributes with foreign types won't be resolved.
+ ImmutableArray attributes = assembly.GetAttributes().ExcludeNonVisibleOutsideOfAssembly(_attributeDataSymbolFilter);
+
+ // Emit assembly attributes from the IAssemblySymbol
+ List attributeSyntaxNodes = attributes
+ .Where(attribute => !attribute.IsReserved())
+ .Select(attribute => _syntaxGenerator.Attribute(attribute)
+ .WithTrailingTrivia(SyntaxFactory.LineFeed))
+ .ToList();
+
+ // [assembly: System.Reflection.AssemblyVersion("x.x.x.x")]
+ if (attributes.All(attribute => attribute.AttributeClass?.ToDisplayString() != typeof(AssemblyVersionAttribute).FullName))
+ {
+ attributeSyntaxNodes.Add(_syntaxGenerator.Attribute(typeof(AssemblyVersionAttribute).FullName!,
+ SyntaxFactory.AttributeArgument(SyntaxFactory.IdentifierName($"\"{assembly.Identity.Version}\"")))
+ .WithTrailingTrivia(SyntaxFactory.LineFeed));
+ }
+
+ // [assembly: System.Runtime.CompilerServices.ReferenceAssembly]
+ if (attributes.All(attribute => attribute.AttributeClass?.ToDisplayString() != typeof(ReferenceAssemblyAttribute).FullName))
+ {
+ attributeSyntaxNodes.Add(_syntaxGenerator.Attribute(typeof(ReferenceAssemblyAttribute).FullName!)
+ .WithTrailingTrivia(SyntaxFactory.LineFeed));
+ }
+
+ // [assembly: System.Reflection.AssemblyFlags((System.Reflection.AssemblyNameFlags)0x70)]
+ if (attributes.All(attribute => attribute.AttributeClass?.ToDisplayString() != typeof(AssemblyFlagsAttribute).FullName))
+ {
+ attributeSyntaxNodes.Add(_syntaxGenerator.Attribute(typeof(AssemblyFlagsAttribute).FullName!,
+ SyntaxFactory.AttributeArgument(SyntaxFactory.IdentifierName("(System.Reflection.AssemblyNameFlags)0x70")))
+ .WithTrailingTrivia(SyntaxFactory.LineFeed));
+ }
+
+ return _syntaxGenerator.AddAttributes(compilationUnit, attributeSyntaxNodes);
+ }
+
+ private SyntaxNode GenerateForwardedTypeAssemblyAttributes(IAssemblySymbol assembly, SyntaxNode compilationUnit)
+ {
+ foreach (INamedTypeSymbol symbol in assembly.GetForwardedTypes().Where(_symbolFilter.Include))
+ {
+ if (symbol.TypeKind != TypeKind.Error)
+ {
+ // see https://github.com/dotnet/roslyn/issues/67341
+ // GetForwardedTypes returns bound generics, but `typeof` requires unbound
+ TypeSyntax typeSyntaxNode = (TypeSyntax)_syntaxGenerator.TypeExpression(symbol.MakeUnboundIfGeneric());
+ compilationUnit = _syntaxGenerator.AddAttributes(compilationUnit,
+ _syntaxGenerator.Attribute(typeof(TypeForwardedToAttribute).FullName!,
+ SyntaxFactory.TypeOfExpression(typeSyntaxNode)).WithTrailingTrivia(SyntaxFactory.LineFeed));
+ }
+ else
+ {
+ _logger.LogWarning(string.Format(
+ Resources.ResolveTypeForwardFailed,
+ symbol.ToDisplayString(),
+ $"{symbol.ContainingAssembly.Name}.dll"));
+ }
+ }
+
+ return compilationUnit;
+ }
+
+ private static IEnumerable EnumerateNamespaces(IAssemblySymbol assemblySymbol)
+ {
+ Stack stack = new();
+ stack.Push(assemblySymbol.GlobalNamespace);
+
+ while (stack.Count > 0)
+ {
+ INamespaceSymbol current = stack.Pop();
+
+ yield return current;
+
+ foreach (INamespaceSymbol subNamespace in current.GetNamespaceMembers())
+ {
+ stack.Push(subNamespace);
+ }
+ }
+ }
+
+ private OptionSet DefineFormattingOptions()
+ {
+ // TODO: consider to move configuration into file.
+ return _adhocWorkspace.Options
+ .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInTypes, true)
+ .WithChangedOption(CSharpFormattingOptions.WrappingKeepStatementsOnSingleLine, true)
+ .WithChangedOption(CSharpFormattingOptions.WrappingPreserveSingleLine, true)
+ .WithChangedOption(CSharpFormattingOptions.IndentBlock, false)
+ .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInMethods, false)
+ .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInProperties, false)
+ .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInAccessors, false)
+ .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInAnonymousMethods, false)
+ .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInControlBlocks, false)
+ .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInAnonymousTypes, false)
+ .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInObjectCollectionArrayInitializers, false)
+ .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInLambdaExpressionBody, false)
+ .WithChangedOption(CSharpFormattingOptions.NewLineForMembersInObjectInit, false)
+ .WithChangedOption(CSharpFormattingOptions.NewLineForMembersInAnonymousTypes, false)
+ .WithChangedOption(CSharpFormattingOptions.NewLineForClausesInQuery, false);
+ }
+}
diff --git a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/CSharpFileBuilder.cs b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/CSharpFileBuilder.cs
index c4a287843beb..ec571332ca36 100644
--- a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/CSharpFileBuilder.cs
+++ b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/CSharpFileBuilder.cs
@@ -1,348 +1,72 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Collections.Immutable;
-using System.Diagnostics;
-using System.Reflection;
-using System.Runtime.CompilerServices;
+#if !NET
+using System.Text.RegularExpressions;
+#endif
using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Formatting;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Editing;
-using Microsoft.CodeAnalysis.Formatting;
-using Microsoft.CodeAnalysis.Options;
-using Microsoft.CodeAnalysis.Simplification;
using Microsoft.DotNet.ApiSymbolExtensions;
using Microsoft.DotNet.ApiSymbolExtensions.Filtering;
using Microsoft.DotNet.ApiSymbolExtensions.Logging;
-using Microsoft.DotNet.GenAPI.SyntaxRewriter;
namespace Microsoft.DotNet.GenAPI
{
///
/// Processes assembly symbols to build corresponding structures in C# language.
///
- public sealed class CSharpFileBuilder : IAssemblySymbolWriter, IDisposable
+ public sealed class CSharpFileBuilder : IAssemblySymbolWriter
{
- private readonly ILog _logger;
private readonly TextWriter _textWriter;
- private readonly ISymbolFilter _symbolFilter;
- private readonly ISymbolFilter _attributeDataSymbolFilter;
- private readonly string? _exceptionMessage;
- private readonly bool _includeAssemblyAttributes;
- private readonly AdhocWorkspace _adhocWorkspace;
- private readonly SyntaxGenerator _syntaxGenerator;
- private readonly IEnumerable _metadataReferences;
+ private readonly string? _header;
+ private readonly CSharpAssemblyDocumentGenerator _docGenerator;
public CSharpFileBuilder(ILog logger,
- ISymbolFilter symbolFilter,
- ISymbolFilter attributeDataSymbolFilter,
- TextWriter textWriter,
- string? exceptionMessage,
- bool includeAssemblyAttributes,
- IEnumerable metadataReferences)
+ TextWriter textWriter,
+ IAssemblySymbolLoader loader,
+ ISymbolFilter symbolFilter,
+ ISymbolFilter attributeDataSymbolFilter,
+ string? header,
+ string? exceptionMessage,
+ bool includeAssemblyAttributes,
+ IEnumerable? metadataReferences = null)
{
- _logger = logger;
_textWriter = textWriter;
- _symbolFilter = symbolFilter;
- _attributeDataSymbolFilter = attributeDataSymbolFilter;
- _exceptionMessage = exceptionMessage;
- _includeAssemblyAttributes = includeAssemblyAttributes;
- _adhocWorkspace = new AdhocWorkspace();
- _syntaxGenerator = SyntaxGenerator.GetGenerator(_adhocWorkspace, LanguageNames.CSharp);
- _metadataReferences = metadataReferences;
+ _header = header;
+ _docGenerator = new CSharpAssemblyDocumentGenerator(logger, loader, symbolFilter, attributeDataSymbolFilter, exceptionMessage, includeAssemblyAttributes, metadataReferences);
}
///
public void WriteAssembly(IAssemblySymbol assemblySymbol)
{
- CSharpCompilationOptions compilationOptions = new(OutputKind.DynamicallyLinkedLibrary,
- nullableContextOptions: NullableContextOptions.Enable);
- Project project = _adhocWorkspace.AddProject(ProjectInfo.Create(
- ProjectId.CreateNewId(), VersionStamp.Create(), assemblySymbol.Name, assemblySymbol.Name, LanguageNames.CSharp,
- compilationOptions: compilationOptions));
- project = project.AddMetadataReferences(_metadataReferences);
-
- IEnumerable namespaceSymbols = EnumerateNamespaces(assemblySymbol).Where(_symbolFilter.Include);
- List namespaceSyntaxNodes = [];
-
- foreach (INamespaceSymbol namespaceSymbol in namespaceSymbols.Order())
- {
- SyntaxNode? syntaxNode = Visit(namespaceSymbol);
-
- if (syntaxNode is not null)
- {
- namespaceSyntaxNodes.Add(syntaxNode);
- }
- }
-
- SyntaxNode compilationUnit = _syntaxGenerator.CompilationUnit(namespaceSyntaxNodes)
- .WithAdditionalAnnotations(Formatter.Annotation, Simplifier.Annotation)
- .Rewrite(new TypeDeclarationCSharpSyntaxRewriter())
- .Rewrite(new BodyBlockCSharpSyntaxRewriter(_exceptionMessage));
-
- if (_includeAssemblyAttributes)
- {
- compilationUnit = GenerateAssemblyAttributes(assemblySymbol, compilationUnit);
- }
-
- compilationUnit = GenerateForwardedTypeAssemblyAttributes(assemblySymbol, compilationUnit);
- compilationUnit = compilationUnit.NormalizeWhitespace(eol: Environment.NewLine);
-
- Document document = project.AddDocument(assemblySymbol.Name, compilationUnit);
- document = Simplifier.ReduceAsync(document).Result;
- document = Formatter.FormatAsync(document, DefineFormattingOptions()).Result;
-
- document.GetSyntaxRootAsync().Result!
- .Rewrite(new SingleLineStatementCSharpSyntaxRewriter())
- .WriteTo(_textWriter);
- }
-
- private SyntaxNode? Visit(INamespaceSymbol namespaceSymbol)
- {
- SyntaxNode namespaceNode = _syntaxGenerator.NamespaceDeclaration(namespaceSymbol.ToDisplayString());
-
- IEnumerable typeMembers = namespaceSymbol.GetTypeMembers().Where(_symbolFilter.Include);
- if (!typeMembers.Any())
- {
- return null;
- }
-
- foreach (INamedTypeSymbol typeMember in typeMembers.Order())
- {
- SyntaxNode typeDeclaration = _syntaxGenerator
- .DeclarationExt(typeMember, _symbolFilter)
- .AddMemberAttributes(_syntaxGenerator, typeMember, _attributeDataSymbolFilter);
-
- typeDeclaration = Visit(typeDeclaration, typeMember);
-
- namespaceNode = _syntaxGenerator.AddMembers(namespaceNode, typeDeclaration);
- }
-
- return namespaceNode;
- }
-
- // Name hiding through inheritance occurs when classes or structs redeclare names that were inherited from base classes.This type of name hiding takes one of the following forms:
- // - A constant, field, property, event, or type introduced in a class or struct hides all base class members with the same name.
- // - A method introduced in a class or struct hides all non-method base class members with the same name, and all base class methods with the same signature(§7.6).
- // - An indexer introduced in a class or struct hides all base class indexers with the same signature(§7.6) .
- private bool HidesBaseMember(ISymbol member)
- {
- if (member.IsOverride)
- {
- return false;
- }
-
- if (member.ContainingType.BaseType is not INamedTypeSymbol baseType)
- {
- return false;
- }
-
- if (member is IMethodSymbol method)
- {
- if (method.MethodKind == MethodKind.ExplicitInterfaceImplementation)
- {
- return false;
- }
-
- // If they're methods, compare their names and signatures.
- return baseType.GetMembers(member.Name)
- .Any(baseMember => _symbolFilter.Include(baseMember) &&
- (baseMember.Kind != SymbolKind.Method ||
- method.SignatureEquals((IMethodSymbol)baseMember)));
- }
- else if (member is IPropertySymbol prop && prop.IsIndexer)
- {
- // If they're indexers, compare their signatures.
- return baseType.GetMembers(member.Name)
- .Any(baseMember => baseMember is IPropertySymbol baseProperty &&
- _symbolFilter.Include(baseMember) &&
- (prop.GetMethod.SignatureEquals(baseProperty.GetMethod) ||
- prop.SetMethod.SignatureEquals(baseProperty.SetMethod)));
- }
- else
- {
- // For all other kinds of members, compare their names.
- return baseType.GetMembers(member.Name)
- .Any(_symbolFilter.Include);
- }
- }
-
- private SyntaxNode Visit(SyntaxNode namedTypeNode, INamedTypeSymbol namedType)
- {
- IEnumerable members = namedType.GetMembers().Where(_symbolFilter.Include);
-
- // If it's a value type
- if (namedType.TypeKind == TypeKind.Struct)
- {
- namedTypeNode = _syntaxGenerator.AddMembers(namedTypeNode, namedType.SynthesizeDummyFields(_symbolFilter, _attributeDataSymbolFilter));
- }
-
- namedTypeNode = _syntaxGenerator.AddMembers(namedTypeNode, namedType.TryGetInternalDefaultConstructor(_symbolFilter));
-
- foreach (ISymbol member in members.Order())
- {
- if (member is IMethodSymbol method)
- {
- // If the method is ExplicitInterfaceImplementation and is derived from an interface that was filtered out, we must filter out it as well.
- if (method.MethodKind == MethodKind.ExplicitInterfaceImplementation &&
- method.ExplicitInterfaceImplementations.Any(m => !_symbolFilter.Include(m.ContainingSymbol) ||
- // if explicit interface implementation method has inaccessible type argument
- m.ContainingType.HasInaccessibleTypeArgument(_symbolFilter)))
- {
- continue;
- }
-
- // Filter out default constructors since these will be added automatically
- if (method.IsImplicitDefaultConstructor(_symbolFilter))
- {
- continue;
- }
- }
-
- // If the property is derived from an interface that was filter out, we must filtered out it either.
- if (member is IPropertySymbol property && !property.ExplicitInterfaceImplementations.IsEmpty &&
- property.ExplicitInterfaceImplementations.Any(m => !_symbolFilter.Include(m.ContainingSymbol)))
- {
- continue;
- }
-
- SyntaxNode memberDeclaration = _syntaxGenerator
- .DeclarationExt(member, _symbolFilter)
- .AddMemberAttributes(_syntaxGenerator, member, _attributeDataSymbolFilter);
-
- if (member is INamedTypeSymbol nestedTypeSymbol)
- {
- memberDeclaration = Visit(memberDeclaration, nestedTypeSymbol);
- }
-
- if (HidesBaseMember(member))
- {
- DeclarationModifiers mods = _syntaxGenerator.GetModifiers(memberDeclaration);
- memberDeclaration = _syntaxGenerator.WithModifiers(memberDeclaration, mods.WithIsNew(isNew: true));
- }
-
- try
- {
- namedTypeNode = _syntaxGenerator.AddMembers(namedTypeNode, memberDeclaration);
- }
- catch (InvalidOperationException e)
- {
- // re-throw the InvalidOperationException with the symbol that caused it.
- throw new InvalidOperationException(string.Format(Resources.AddMemberThrowsException,
- member.ToDisplayString(),
- namedTypeNode,
- e.Message));
- }
- }
-
- return namedTypeNode;
+ _textWriter.Write(GetFormattedHeader(_header));
+ Document document = _docGenerator.GetDocumentForAssembly(assemblySymbol);
+ _docGenerator.GetFormattedRootNodeForDocument(document).WriteTo(_textWriter);
}
- private SyntaxNode GenerateAssemblyAttributes(IAssemblySymbol assembly, SyntaxNode compilationUnit)
+ private static string GetFormattedHeader(string? customHeader)
{
- // When assembly references aren't available, assembly attributes with foreign types won't be resolved.
- ImmutableArray attributes = assembly.GetAttributes().ExcludeNonVisibleOutsideOfAssembly(_attributeDataSymbolFilter);
+ const string defaultFileHeader = """
+ //------------------------------------------------------------------------------
+ //
+ // This code was generated by a tool.
+ //
+ // Changes to this file may cause incorrect behavior and will be lost if
+ // the code is regenerated.
+ //
+ //------------------------------------------------------------------------------
- // Emit assembly attributes from the IAssemblySymbol
- List attributeSyntaxNodes = attributes
- .Where(attribute => !attribute.IsReserved())
- .Select(attribute => _syntaxGenerator.Attribute(attribute)
- .WithTrailingTrivia(SyntaxFactory.LineFeed))
- .ToList();
+ """;
- // [assembly: System.Reflection.AssemblyVersion("x.x.x.x")]
- if (attributes.All(attribute => attribute.AttributeClass?.ToDisplayString() != typeof(AssemblyVersionAttribute).FullName))
+ if (customHeader != null)
{
- attributeSyntaxNodes.Add(_syntaxGenerator.Attribute(typeof(AssemblyVersionAttribute).FullName!,
- SyntaxFactory.AttributeArgument(SyntaxFactory.IdentifierName($"\"{assembly.Identity.Version}\"")))
- .WithTrailingTrivia(SyntaxFactory.LineFeed));
+#if NET
+ return customHeader.ReplaceLineEndings();
+#else
+ return Regex.Replace(customHeader, @"\r\n|\n\r|\n|\r", Environment.NewLine);
+#endif
}
- // [assembly: System.Runtime.CompilerServices.ReferenceAssembly]
- if (attributes.All(attribute => attribute.AttributeClass?.ToDisplayString() != typeof(ReferenceAssemblyAttribute).FullName))
- {
- attributeSyntaxNodes.Add(_syntaxGenerator.Attribute(typeof(ReferenceAssemblyAttribute).FullName!)
- .WithTrailingTrivia(SyntaxFactory.LineFeed));
- }
-
- // [assembly: System.Reflection.AssemblyFlags((System.Reflection.AssemblyNameFlags)0x70)]
- if (attributes.All(attribute => attribute.AttributeClass?.ToDisplayString() != typeof(AssemblyFlagsAttribute).FullName))
- {
- attributeSyntaxNodes.Add(_syntaxGenerator.Attribute(typeof(AssemblyFlagsAttribute).FullName!,
- SyntaxFactory.AttributeArgument(SyntaxFactory.IdentifierName("(System.Reflection.AssemblyNameFlags)0x70")))
- .WithTrailingTrivia(SyntaxFactory.LineFeed));
- }
-
- return _syntaxGenerator.AddAttributes(compilationUnit, attributeSyntaxNodes);
- }
-
- private SyntaxNode GenerateForwardedTypeAssemblyAttributes(IAssemblySymbol assembly, SyntaxNode compilationUnit)
- {
- foreach (INamedTypeSymbol symbol in assembly.GetForwardedTypes().Where(_symbolFilter.Include))
- {
- if (symbol.TypeKind != TypeKind.Error)
- {
- // see https://github.com/dotnet/roslyn/issues/67341
- // GetForwardedTypes returns bound generics, but `typeof` requires unbound
- TypeSyntax typeSyntaxNode = (TypeSyntax)_syntaxGenerator.TypeExpression(symbol.MakeUnboundIfGeneric());
- compilationUnit = _syntaxGenerator.AddAttributes(compilationUnit,
- _syntaxGenerator.Attribute(typeof(TypeForwardedToAttribute).FullName!,
- SyntaxFactory.TypeOfExpression(typeSyntaxNode)).WithTrailingTrivia(SyntaxFactory.LineFeed));
- }
- else
- {
- _logger.LogWarning(string.Format(
- Resources.ResolveTypeForwardFailed,
- symbol.ToDisplayString(),
- $"{symbol.ContainingAssembly.Name}.dll"));
- }
- }
-
- return compilationUnit;
- }
-
- private static IEnumerable EnumerateNamespaces(IAssemblySymbol assemblySymbol)
- {
- Stack stack = new();
- stack.Push(assemblySymbol.GlobalNamespace);
-
- while (stack.Count > 0)
- {
- INamespaceSymbol current = stack.Pop();
-
- yield return current;
-
- foreach (INamespaceSymbol subNamespace in current.GetNamespaceMembers())
- {
- stack.Push(subNamespace);
- }
- }
+ return defaultFileHeader;
}
-
- private OptionSet DefineFormattingOptions()
- {
- // TODO: consider to move configuration into file.
- return _adhocWorkspace.Options
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInTypes, true)
- .WithChangedOption(CSharpFormattingOptions.WrappingKeepStatementsOnSingleLine, true)
- .WithChangedOption(CSharpFormattingOptions.WrappingPreserveSingleLine, true)
- .WithChangedOption(CSharpFormattingOptions.IndentBlock, false)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInMethods, false)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInProperties, false)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInAccessors, false)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInAnonymousMethods, false)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInControlBlocks, false)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInAnonymousTypes, false)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInObjectCollectionArrayInitializers, false)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInLambdaExpressionBody, false)
- .WithChangedOption(CSharpFormattingOptions.NewLineForMembersInObjectInit, false)
- .WithChangedOption(CSharpFormattingOptions.NewLineForMembersInAnonymousTypes, false)
- .WithChangedOption(CSharpFormattingOptions.NewLineForClausesInQuery, false);
- }
-
- ///
- public void Dispose() => _textWriter.Dispose();
}
}
diff --git a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/Filtering/ImplicitSymbolFilter.cs b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/Filtering/ImplicitSymbolFilter.cs
deleted file mode 100644
index 48d2cb66d46e..000000000000
--- a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/Filtering/ImplicitSymbolFilter.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.CodeAnalysis;
-using Microsoft.DotNet.ApiSymbolExtensions;
-using Microsoft.DotNet.ApiSymbolExtensions.Filtering;
-
-namespace Microsoft.DotNet.GenAPI.Filtering
-{
- ///
- /// Filter out implicitly generated members for properties, events, etc.
- ///
- public class ImplicitSymbolFilter : ISymbolFilter
- {
- ///
- /// Determines whether implicitly generated symbols should be included.
- ///
- /// to evaluate.
- /// True to include the or false to filter it out.
- public bool Include(ISymbol symbol)
- {
- if (symbol is IMethodSymbol method)
- {
- if (method.IsImplicitlyDeclared ||
- method.Kind == SymbolKind.NamedType ||
- method.MethodKind == MethodKind.PropertyGet ||
- method.MethodKind == MethodKind.PropertySet ||
- method.MethodKind == MethodKind.EventAdd ||
- method.MethodKind == MethodKind.EventRemove ||
- method.MethodKind == MethodKind.EventRaise ||
- method.MethodKind == MethodKind.DelegateInvoke)
- {
- return false;
- }
-
- // If the method is an explicitly implemented getter or setter, exclude it.
- // https://github.com/dotnet/roslyn/issues/53911
- if (method.MethodKind == MethodKind.ExplicitInterfaceImplementation &&
- method.ExplicitInterfaceImplementations.Any(m => m is { MethodKind: MethodKind.PropertyGet or MethodKind.PropertySet }))
- {
- return false;
- }
- }
-
- if (symbol is ITypeSymbol type)
- {
- if (type.DeclaredAccessibility == Accessibility.Internal && (
- // exclude the compiler generated `` type
- type.Name == "" ||
- // exclude any types which the compiler embedded - marked with EmbeddedAttribute.
- // these will be generated by the compiler when compiling C# syntax that requires them.
- type.GetAttributes().Any(a => a.AttributeClass?.Name == "EmbeddedAttribute" && a.AttributeClass?.ContainingNamespace.ToDisplayString() == "Microsoft.CodeAnalysis")))
- {
- return false;
- }
- }
-
- // exclude compiler-synthesized members on record
- if (symbol.ContainingType is { IsRecord: true })
- {
- if (symbol.IsCompilerGenerated())
- {
- return false;
- }
-
- // see if we can identify the record parameter syntax by locating the compiler generated constructor
- if (symbol.ContainingType.TryGetRecordConstructor(out IMethodSymbol? recordConstructor))
- {
- // exclude the compiler generated constructor
- if (SymbolEqualityComparer.Default.Equals(symbol, recordConstructor))
- {
- return false;
- }
-
- // exclude the compiler generated properties
- if (symbol is IPropertySymbol)
- {
- // Exclude members with the same name as the record constructor's parameters
- return !recordConstructor.Parameters.Any(p => p.Name == symbol.Name);
- }
- }
- }
-
- return true;
- }
- }
-}
diff --git a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/GenAPIApp.cs b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/GenAPIApp.cs
index 51bf68c42017..b8e3625069e1 100644
--- a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/GenAPIApp.cs
+++ b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/GenAPIApp.cs
@@ -8,7 +8,6 @@
using Microsoft.DotNet.ApiSymbolExtensions;
using Microsoft.DotNet.ApiSymbolExtensions.Filtering;
using Microsoft.DotNet.ApiSymbolExtensions.Logging;
-using Microsoft.DotNet.GenAPI.Filtering;
namespace Microsoft.DotNet.GenAPI
{
@@ -22,8 +21,8 @@ public static class GenAPIApp
/// Initialize and run Roslyn-based GenAPI tool.
///
public static void Run(ILog logger,
- string[] assemblies,
- string[]? assemblyReferences,
+ IAssemblySymbolLoader loader,
+ Dictionary assemblySymbols,
string? outputPath,
string? headerFile,
string? exceptionMessage,
@@ -32,75 +31,24 @@ public static void Run(ILog logger,
bool respectInternals,
bool includeAssemblyAttributes)
{
- bool resolveAssemblyReferences = assemblyReferences?.Length > 0;
- // Create, configure and execute the assembly loader.
- AssemblySymbolLoader loader = new(resolveAssemblyReferences, respectInternals);
- if (assemblyReferences is not null)
+ // Invoke an assembly symbol writer for each directly loaded assembly.
+ foreach (KeyValuePair kvp in assemblySymbols)
{
- loader.AddReferenceSearchPaths(assemblyReferences);
+ using TextWriter textWriter = GetTextWriter(outputPath, kvp.Key);
+ CSharpFileBuilder writer = new(logger,
+ textWriter,
+ loader,
+ CompositeSymbolFilter.GetSymbolFilterFromFiles(excludeApiFiles, respectInternals),
+ CompositeSymbolFilter.GetAttributeFilterFromPaths(excludeAttributesFiles, respectInternals),
+ headerFile,
+ exceptionMessage,
+ includeAssemblyAttributes);
+ writer.WriteAssembly(kvp.Value);
}
- IReadOnlyList assemblySymbols = loader.LoadAssemblies(assemblies);
- string headerFileText = ReadHeaderFile(headerFile);
-
- AccessibilitySymbolFilter accessibilitySymbolFilter = new(
- respectInternals,
- includeEffectivelyPrivateSymbols: true,
- includeExplicitInterfaceImplementationSymbols: true);
-
- // Configure the symbol filter
- CompositeSymbolFilter symbolFilter = new();
- if (excludeApiFiles is not null)
- {
- symbolFilter.Add(new DocIdSymbolFilter(excludeApiFiles));
- }
- symbolFilter.Add(new ImplicitSymbolFilter());
- symbolFilter.Add(accessibilitySymbolFilter);
-
- // Configure the attribute data symbol filter
- CompositeSymbolFilter attributeDataSymbolFilter = new();
- if (excludeAttributesFiles is not null)
- {
- attributeDataSymbolFilter.Add(new DocIdSymbolFilter(excludeAttributesFiles));
- }
- attributeDataSymbolFilter.Add(accessibilitySymbolFilter);
-
- // Invoke the CSharpFileBuilder for each directly loaded assembly.
- foreach (IAssemblySymbol? assemblySymbol in assemblySymbols)
- {
- if (assemblySymbol is null)
- continue;
-
- using TextWriter textWriter = GetTextWriter(outputPath, assemblySymbol.Name);
- textWriter.Write(headerFileText);
-
- using CSharpFileBuilder fileBuilder = new(logger,
- symbolFilter,
- attributeDataSymbolFilter,
- textWriter,
- exceptionMessage,
- includeAssemblyAttributes,
- loader.MetadataReferences);
-
- fileBuilder.WriteAssembly(assemblySymbol);
- }
-
- if (loader.HasRoslynDiagnostics(out IReadOnlyList roslynDiagnostics))
- {
- foreach (Diagnostic warning in roslynDiagnostics)
- {
- logger.LogWarning(warning.Id, warning.ToString());
- }
- }
-
- if (loader.HasLoadWarnings(out IReadOnlyList loadWarnings))
- {
- foreach (AssemblyLoadWarning warning in loadWarnings)
- {
- logger.LogWarning(warning.DiagnosticId, warning.Message);
- }
- }
+ loader.LogAllDiagnostics();
+ loader.LogAllWarnings();
}
// Creates a TextWriter capable of writing into Console or a cs file.
@@ -119,33 +67,5 @@ private static TextWriter GetTextWriter(string? outputDirPath, string assemblyNa
return File.CreateText(outputDirPath);
}
-
- // Read the header file if specified, or use default one.
- private static string ReadHeaderFile(string? headerFile)
- {
- const string defaultFileHeader = """
- //------------------------------------------------------------------------------
- //
- // This code was generated by a tool.
- //
- // Changes to this file may cause incorrect behavior and will be lost if
- // the code is regenerated.
- //
- //------------------------------------------------------------------------------
-
- """;
-
- string header = !string.IsNullOrEmpty(headerFile) ?
- File.ReadAllText(headerFile) :
- defaultFileHeader;
-
-#if NET
- header = header.ReplaceLineEndings();
-#else
- header = Regex.Replace(header, @"\r\n|\n\r|\n|\r", Environment.NewLine);
-#endif
-
- return header;
- }
}
}
diff --git a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/IAssemblySymbolWriter.cs b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/IAssemblySymbolWriter.cs
index a739ca961575..e093037b8a78 100644
--- a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/IAssemblySymbolWriter.cs
+++ b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/IAssemblySymbolWriter.cs
@@ -11,9 +11,9 @@ namespace Microsoft.DotNet.GenAPI
public interface IAssemblySymbolWriter
{
///
- /// Process a given assembly symbol.
+ /// Write a given assembly symbol to the instance's desired output.
///
- /// representing the loaded assembly.
+ /// An assembly symbol representing the loaded assembly.
void WriteAssembly(IAssemblySymbol assemblySymbol);
}
}
diff --git a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/INamedTypeSymbolExtensions.cs b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/INamedTypeSymbolExtensions.cs
index fdbee7992df1..4ef1ecedcf6d 100644
--- a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/INamedTypeSymbolExtensions.cs
+++ b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/INamedTypeSymbolExtensions.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -70,14 +69,14 @@ private static SyntaxList FromAttributeData(IEnumerable attrs, bool isReadonly)
+ private static SyntaxNode CreateDummyField(string type, string fieldName, SyntaxList attrs, bool isReadonly)
{
List modifiers = new() { SyntaxFactory.Token(SyntaxKind.PrivateKeyword) };
if (isReadonly)
modifiers.Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword));
SyntaxNode declaration = SyntaxFactory.FieldDeclaration(
SyntaxFactory.VariableDeclaration(
- SyntaxFactory.ParseTypeName(typ))
+ SyntaxFactory.ParseTypeName(type))
.WithVariables(
SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.VariableDeclarator(
@@ -224,7 +223,7 @@ static bool IncludeInternalSymbols(ISymbolFilter filter) =>
yield return constructor;
}
- // Synthesize a base class initializer.
+ // Synthesize a base class initializer.
public static ConstructorInitializerSyntax GenerateBaseConstructorInitializer(this IMethodSymbol baseTypeConstructor)
{
return SyntaxFactory.ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, baseTypeConstructor.CreateDefaultArgumentList());
@@ -248,33 +247,5 @@ public static ArgumentListSyntax CreateDefaultArgumentList(this IMethodSymbol me
return argumentList;
}
-
- // Locates constructor generated by the compiler for `record Foo(...)` syntax
- // If the type is a record and the compiler generated constructor is found it will be returned, otherwise null.
- // The compiler will not generate a constructor in the case where the user defined it themself without using an argument list
- // in the record declaration, or if the record has no parameters.
- public static bool TryGetRecordConstructor(this INamedTypeSymbol type, [NotNullWhen(true)] out IMethodSymbol? recordConstructor)
- {
- if (!type.IsRecord)
- {
- recordConstructor = null;
- return false;
- }
-
- // Locate the compiler generated Deconstruct method.
- var deconstructMethod = (IMethodSymbol?)type.GetMembers("Deconstruct")
- .FirstOrDefault(m => m is IMethodSymbol && m.IsCompilerGenerated());
-
- // Locate the compiler generated constructor by matching parameters to Deconstruct - since we cannot locate it with an attribute.
- recordConstructor = (IMethodSymbol?)type.GetMembers(".ctor")
- .FirstOrDefault(m => m is IMethodSymbol method &&
- method.MethodKind == MethodKind.Constructor &&
- (deconstructMethod == null ?
- method.Parameters.IsEmpty :
- method.Parameters.Select(p => p.Type).SequenceEqual(
- deconstructMethod.Parameters.Select(p => p.Type), SymbolEqualityComparer.Default)));
-
- return recordConstructor != null;
- }
}
}
diff --git a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj
index 1966bb66ab05..0c382c85d75f 100644
--- a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj
+++ b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj
@@ -4,10 +4,6 @@
$(NetToolMinimum);$(NetFrameworkToolCurrent)
-
-
-
-
diff --git a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/SyntaxGeneratorExtensions.cs b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/SyntaxGeneratorExtensions.cs
index 1201e4db2336..3af277f65c4a 100644
--- a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/SyntaxGeneratorExtensions.cs
+++ b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/SyntaxGeneratorExtensions.cs
@@ -1,11 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Transactions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
+using Microsoft.DotNet.ApiSymbolExtensions;
using Microsoft.DotNet.ApiSymbolExtensions.Filtering;
namespace Microsoft.DotNet.GenAPI
diff --git a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/SyntaxRewriter/TypeDeclarationCSharpSyntaxRewriter.cs b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/SyntaxRewriter/TypeDeclarationCSharpSyntaxRewriter.cs
index 80a061eb461f..a3232cdf391f 100644
--- a/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/SyntaxRewriter/TypeDeclarationCSharpSyntaxRewriter.cs
+++ b/src/Compatibility/GenAPI/Microsoft.DotNet.GenAPI/SyntaxRewriter/TypeDeclarationCSharpSyntaxRewriter.cs
@@ -15,8 +15,14 @@ namespace Microsoft.DotNet.GenAPI.SyntaxRewriter
/// - adds partial keyword
/// - remove Object from a list of base types.
///
- public class TypeDeclarationCSharpSyntaxRewriter : CSharpSyntaxRewriter
+ ///
+ /// Initializes a new instance of the class, and optionally allows deciding whether to insert the partial modifier for types or not.
+ ///
+ /// Determines whether to insert the partial modifier for types or not.
+ public class TypeDeclarationCSharpSyntaxRewriter(bool addPartialModifier) : CSharpSyntaxRewriter
{
+ private readonly bool _addPartialModifier = addPartialModifier;
+
///
public override SyntaxNode? VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
{
@@ -83,7 +89,7 @@ public class TypeDeclarationCSharpSyntaxRewriter : CSharpSyntaxRewriter
}
}
- private static T? VisitCommonTypeDeclaration(T? node) where T : TypeDeclarationSyntax
+ private T? VisitCommonTypeDeclaration(T? node) where T : TypeDeclarationSyntax
{
if (node == null)
{
@@ -91,7 +97,7 @@ public class TypeDeclarationCSharpSyntaxRewriter : CSharpSyntaxRewriter
}
node = RemoveBaseType(node, "global::System.Object");
- return AddPartialModifier(node);
+ return _addPartialModifier ? AddPartialModifier(node) : node;
}
private static T? AddPartialModifier(T? node) where T : TypeDeclarationSyntax =>
diff --git a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/AssemblySymbolLoader.cs b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/AssemblySymbolLoader.cs
index 6d7dea1bc8ae..77e15ea89c09 100644
--- a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/AssemblySymbolLoader.cs
+++ b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/AssemblySymbolLoader.cs
@@ -7,6 +7,7 @@
using System.Reflection.PortableExecutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.DotNet.ApiSymbolExtensions.Logging;
namespace Microsoft.DotNet.ApiSymbolExtensions
{
@@ -15,7 +16,8 @@ namespace Microsoft.DotNet.ApiSymbolExtensions
///
public class AssemblySymbolLoader : IAssemblySymbolLoader
{
- // Dictionary that holds the paths to help loading dependencies. Keys will be assembly name and
+ private readonly ILog _logger;
+ // Dictionary that holds the paths to help loading dependencies. Keys will be assembly name and
// value are the containing folder.
private readonly Dictionary _referencePathFiles = new(StringComparer.OrdinalIgnoreCase);
private readonly HashSet _referencePathDirectories = new(StringComparer.OrdinalIgnoreCase);
@@ -34,13 +36,41 @@ public class AssemblySymbolLoader : IAssemblySymbolLoader
///
public const string AssemblyReferenceNotFoundErrorCode = "CP1002";
+ ///
+ /// Creates an assembly symbol loader and its corresponding assembly symbols from the given DLL files in the filesystem.
+ ///
+ /// The logger instance to use for message logging.
+ /// A collection of paths where the assembly DLLs should be searched.
+ /// An optional collection of paths where the assembly references should be searched.
+ /// Whether to include internal symbols or not.
+ /// A tuple containing an assembly symbol loader and its corresponding dictionary of assembly symbols.
+ public static (AssemblySymbolLoader, Dictionary) CreateFromFiles(ILog logger, string[] assembliesPaths, string[]? assemblyReferencesPaths, bool respectInternals = false)
+ {
+ if (assembliesPaths.Length == 0)
+ {
+ return (new AssemblySymbolLoader(logger, resolveAssemblyReferences: true, includeInternalSymbols: respectInternals), new Dictionary());
+ }
+
+ bool atLeastOneReferencePath = assemblyReferencesPaths?.Count() > 0;
+ AssemblySymbolLoader loader = new(logger, resolveAssemblyReferences: atLeastOneReferencePath, respectInternals);
+ if (atLeastOneReferencePath)
+ {
+ loader.AddReferenceSearchPaths(assemblyReferencesPaths!);
+ }
+ Dictionary dictionary = new(loader.LoadAssembliesAsDictionary(assembliesPaths));
+
+ return (loader, dictionary);
+ }
+
///
/// Creates a new instance of the class.
///
+ /// The logger instance to use for message logging.
/// True to attempt to load references for loaded assemblies from the locations specified with . Default is false.
/// True to include all internal metadata for assemblies loaded. Default is false which only includes public and some internal metadata.
- public AssemblySymbolLoader(bool resolveAssemblyReferences = false, bool includeInternalSymbols = false)
+ public AssemblySymbolLoader(ILog logger, bool resolveAssemblyReferences = false, bool includeInternalSymbols = false)
{
+ _logger = logger;
_loadedAssemblies = [];
CSharpCompilationOptions compilationOptions = new(OutputKind.DynamicallyLinkedLibrary, nullableContextOptions: NullableContextOptions.Enable,
metadataImportOptions: includeInternalSymbols ? MetadataImportOptions.Internal : MetadataImportOptions.Public);
@@ -109,6 +139,29 @@ public bool HasLoadWarnings(out IReadOnlyList warnings)
return assemblySymbols;
}
+ ///
+ public IDictionary LoadAssembliesAsDictionary(params string[] paths)
+ {
+ // First resolve all assemblies that are passed in and create metadata references out of them.
+ // Reference assemblies of the passed in assemblies that themselves are passed in, will be skipped to be resolved,
+ // as they are resolved as part of the loop below.
+ ImmutableHashSet fileNames = paths.Select(path => Path.GetFileName(path)).ToImmutableHashSet();
+ List assembliesToReturn = LoadFromPaths(paths, fileNames);
+
+ // Create IAssemblySymbols out of the MetadataReferences.
+ // Doing this after resolving references to make sure that references are available.
+ Dictionary assemblySymbols = [];
+ foreach (MetadataReference metadataReference in assembliesToReturn)
+ {
+ if(_cSharpCompilation.GetAssemblyOrModuleSymbol(metadataReference) is IAssemblySymbol assemblySymbol)
+ {
+ assemblySymbols.Add(assemblySymbol.Name, assemblySymbol);
+ }
+ }
+
+ return assemblySymbols;
+ }
+
///
public IReadOnlyList LoadAssembliesFromArchive(string archivePath, IReadOnlyList relativePaths)
{
@@ -256,6 +309,40 @@ public IEnumerable LoadMatchingAssemblies(IEnumerable
+ public void LogAllDiagnostics(string? headerMessage = null)
+ {
+ if (HasRoslynDiagnostics(out IReadOnlyList roslynDiagnostics))
+ {
+ if (!string.IsNullOrEmpty(headerMessage))
+ {
+ _logger.LogWarning(headerMessage!);
+ }
+
+ foreach (Diagnostic warning in roslynDiagnostics)
+ {
+ _logger.LogWarning(warning.Id, warning.ToString());
+ }
+ }
+ }
+
+ ///
+ public void LogAllWarnings(string? headerMessage = null)
+ {
+ if (HasLoadWarnings(out IReadOnlyList loadWarnings))
+ {
+ if (!string.IsNullOrEmpty(headerMessage))
+ {
+ _logger.LogWarning(headerMessage!);
+ }
+
+ foreach (AssemblyLoadWarning warning in loadWarnings)
+ {
+ _logger.LogWarning(warning.DiagnosticId, warning.Message);
+ }
+ }
+ }
+
///
public IEnumerable MetadataReferences => _cSharpCompilation.References;
diff --git a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/AssemblySymbolLoaderFactory.cs b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/AssemblySymbolLoaderFactory.cs
index 2df44a418f9f..b2b2b1075667 100644
--- a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/AssemblySymbolLoaderFactory.cs
+++ b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/AssemblySymbolLoaderFactory.cs
@@ -1,16 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using Microsoft.DotNet.ApiSymbolExtensions.Logging;
+
namespace Microsoft.DotNet.ApiSymbolExtensions
{
///
/// Factory to create an AssemblySymbolLoader
///
+ /// The logger instance to use for message logging.
/// True to include internal API when reading assemblies from the created.
- public sealed class AssemblySymbolLoaderFactory(bool includeInternalSymbols = false) : IAssemblySymbolLoaderFactory
+ public sealed class AssemblySymbolLoaderFactory(ILog logger, bool includeInternalSymbols = false) : IAssemblySymbolLoaderFactory
{
///
public IAssemblySymbolLoader Create(bool shouldResolveReferences) =>
- new AssemblySymbolLoader(shouldResolveReferences, includeInternalSymbols);
+ new AssemblySymbolLoader(logger, shouldResolveReferences, includeInternalSymbols);
}
}
diff --git a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/CompositeSymbolFilter.cs b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/CompositeSymbolFilter.cs
index d1b8cf29f899..24fda7630790 100644
--- a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/CompositeSymbolFilter.cs
+++ b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/CompositeSymbolFilter.cs
@@ -14,7 +14,114 @@ public sealed class CompositeSymbolFilter(params IEnumerable filt
///
/// List on inner filters.
///
- public List Filters { get; } = new(filters);
+ public List Filters { get; } = [.. filters];
+
+ ///
+ /// Creates a composite filter to exclude APIs using the DocIDs provided in the specifed file paths.
+ ///
+ /// A collection of paths where the exclusion files should be searched.
+ /// Whether to include internal symbols or not.
+ /// Whether to include effectively private symbols or not.
+ /// Whether to include explicit interface implementation symbols or not.
+ /// An instance of the symbol filter.
+ public static ISymbolFilter GetSymbolFilterFromFiles(string[]? apiExclusionFilePaths,
+ bool respectInternals = false,
+ bool includeEffectivelyPrivateSymbols = true,
+ bool includeExplicitInterfaceImplementationSymbols = true)
+ {
+ DocIdSymbolFilter? docIdSymbolFilter =
+ apiExclusionFilePaths?.Count() > 0 ?
+ DocIdSymbolFilter.CreateFromFiles(apiExclusionFilePaths) : null;
+
+ return GetCompositeSymbolFilter(docIdSymbolFilter, respectInternals, includeEffectivelyPrivateSymbols, includeExplicitInterfaceImplementationSymbols, withImplicitSymbolFilter: true);
+ }
+
+ ///
+ /// Creates a composite filter to exclude APIs using the DocIDs provided in the specifed list.
+ ///
+ /// A collection of exclusion list.
+ /// Whether to include internal symbols or not.
+ /// Whether to include effectively private symbols or not.
+ /// Whether to include explicit interface implementation symbols or not.
+ /// An instance of the symbol filter.
+ public static ISymbolFilter GetSymbolFilterFromList(string[]? apiExclusionList,
+ bool respectInternals = false,
+ bool includeEffectivelyPrivateSymbols = true,
+ bool includeExplicitInterfaceImplementationSymbols = true)
+ {
+ DocIdSymbolFilter? docIdSymbolFilter =
+ apiExclusionList?.Count() > 0 ?
+ DocIdSymbolFilter.CreateFromDocIDs(apiExclusionList) : null;
+
+ return GetCompositeSymbolFilter(docIdSymbolFilter, respectInternals, includeEffectivelyPrivateSymbols, includeExplicitInterfaceImplementationSymbols, withImplicitSymbolFilter: true);
+ }
+
+ ///
+ /// Creates an composite filter to exclude attributes using the DocID provided in the specified file paths.
+ ///
+ /// A collection of paths where the exclusion files should be searched.
+ /// Whether to include internal symbols or not.
+ /// Whether to include effectively private symbols or not.
+ /// Whether to include explicit interface implementation symbols or not.
+ /// An instance of the attribute filter.
+ public static ISymbolFilter GetAttributeFilterFromPaths(string[]? attributeExclusionFilePaths,
+ bool respectInternals = false,
+ bool includeEffectivelyPrivateSymbols = true,
+ bool includeExplicitInterfaceImplementationSymbols = true)
+ {
+ DocIdSymbolFilter? docIdSymbolFilter =
+ attributeExclusionFilePaths?.Count() > 0 ?
+ DocIdSymbolFilter.CreateFromFiles(attributeExclusionFilePaths) : null;
+
+ return GetCompositeSymbolFilter(docIdSymbolFilter, respectInternals, includeEffectivelyPrivateSymbols, includeExplicitInterfaceImplementationSymbols, withImplicitSymbolFilter: false);
+ }
+
+ ///
+ /// Creates an composite filter to exclude attributes using the DocID provided in the specified list.
+ ///
+ /// A collection of exclusion list.
+ /// Whether to include internal symbols or not.
+ /// Whether to include effectively private symbols or not.
+ /// Whether to include explicit interface implementation symbols or not.
+ /// An instance of the attribute filter.
+ public static ISymbolFilter GetAttributeFilterFromList(string[]? attributeExclusionList,
+ bool respectInternals = false,
+ bool includeEffectivelyPrivateSymbols = true,
+ bool includeExplicitInterfaceImplementationSymbols = true)
+ {
+ DocIdSymbolFilter? docIdSymbolFilter =
+ attributeExclusionList?.Count() > 0 ?
+ DocIdSymbolFilter.CreateFromDocIDs(attributeExclusionList) : null;
+
+ return GetCompositeSymbolFilter(docIdSymbolFilter, respectInternals, includeEffectivelyPrivateSymbols, includeExplicitInterfaceImplementationSymbols, withImplicitSymbolFilter: false);
+ }
+
+ private static ISymbolFilter GetCompositeSymbolFilter(DocIdSymbolFilter? customFilter,
+ bool respectInternals,
+ bool includeEffectivelyPrivateSymbols,
+ bool includeExplicitInterfaceImplementationSymbols,
+ bool withImplicitSymbolFilter)
+ {
+ AccessibilitySymbolFilter accessibilitySymbolFilter = new(
+ respectInternals,
+ includeEffectivelyPrivateSymbols,
+ includeExplicitInterfaceImplementationSymbols);
+
+ CompositeSymbolFilter filter = new();
+
+ if (customFilter != null)
+ {
+ filter.Add(customFilter);
+ }
+ if (withImplicitSymbolFilter)
+ {
+ filter.Add(new ImplicitSymbolFilter());
+ }
+
+ filter.Add(accessibilitySymbolFilter);
+
+ return filter;
+ }
///
/// Determines whether the should be included.
diff --git a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/DocIdSymbolFilter.cs b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/DocIdSymbolFilter.cs
index 3bb8ba16fac6..4a5435448fb3 100644
--- a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/DocIdSymbolFilter.cs
+++ b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/DocIdSymbolFilter.cs
@@ -9,9 +9,18 @@ namespace Microsoft.DotNet.ApiSymbolExtensions.Filtering
/// Implements the logic of filtering out api.
/// Reads the file with the list of attributes, types, members in DocId format.
///
- public class DocIdSymbolFilter(string[] docIdsToExcludeFiles) : ISymbolFilter
+ public class DocIdSymbolFilter : ISymbolFilter
{
- private readonly HashSet _docIdsToExclude = new(ReadDocIdsAttributes(docIdsToExcludeFiles));
+ private readonly HashSet _docIdsToExclude;
+
+ public static DocIdSymbolFilter CreateFromFiles(params string[] filesWithDocIdsToExclude)
+ => new DocIdSymbolFilter(ReadDocIdsFromFiles(filesWithDocIdsToExclude));
+
+ public static DocIdSymbolFilter CreateFromDocIDs(params string[] docIdsToExclude)
+ => new DocIdSymbolFilter(ReadDocIdsFromList(docIdsToExclude));
+
+ private DocIdSymbolFilter(IEnumerable docIdsToExclude)
+ => _docIdsToExclude = [.. docIdsToExclude];
///
/// Determines whether the should be included.
@@ -29,20 +38,33 @@ public bool Include(ISymbol symbol)
return true;
}
- private static IEnumerable ReadDocIdsAttributes(IEnumerable docIdsToExcludeFiles)
+ private static IEnumerable ReadDocIdsFromList(params string[] ids)
{
- foreach (string docIdsToExcludeFile in docIdsToExcludeFiles)
+ foreach (string id in ids)
{
- foreach (string id in File.ReadAllLines(docIdsToExcludeFile))
- {
#if NET
- if (!string.IsNullOrWhiteSpace(id) && !id.StartsWith('#') && !id.StartsWith("//"))
+ if (!string.IsNullOrWhiteSpace(id) && !id.StartsWith('#') && !id.StartsWith("//"))
#else
- if (!string.IsNullOrWhiteSpace(id) && !id.StartsWith("#") && !id.StartsWith("//"))
+ if (!string.IsNullOrWhiteSpace(id) && !id.StartsWith("#") && !id.StartsWith("//"))
#endif
- {
- yield return id.Trim();
- }
+ {
+ yield return id.Trim();
+ }
+ }
+ }
+
+ private static IEnumerable ReadDocIdsFromFiles(params string[] docIdsToExcludeFiles)
+ {
+ foreach (string docIdsToExcludeFile in docIdsToExcludeFiles)
+ {
+ if (string.IsNullOrWhiteSpace(docIdsToExcludeFile))
+ {
+ continue;
+ }
+
+ foreach (string docId in ReadDocIdsFromList(File.ReadAllLines(docIdsToExcludeFile)))
+ {
+ yield return docId;
}
}
}
diff --git a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/ImplicitSymbolFilter.cs b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/ImplicitSymbolFilter.cs
new file mode 100644
index 000000000000..cbb4ed95f228
--- /dev/null
+++ b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Filtering/ImplicitSymbolFilter.cs
@@ -0,0 +1,84 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.DotNet.ApiSymbolExtensions.Filtering;
+
+///
+/// Filter out implicitly generated members for properties, events, etc.
+///
+public class ImplicitSymbolFilter : ISymbolFilter
+{
+ ///
+ /// Determines whether implicitly generated symbols should be included.
+ ///
+ /// to evaluate.
+ /// True to include the or false to filter it out.
+ public bool Include(ISymbol symbol)
+ {
+ if (symbol is IMethodSymbol method)
+ {
+ if (method.IsImplicitlyDeclared ||
+ method.Kind == SymbolKind.NamedType ||
+ method.MethodKind == MethodKind.PropertyGet ||
+ method.MethodKind == MethodKind.PropertySet ||
+ method.MethodKind == MethodKind.EventAdd ||
+ method.MethodKind == MethodKind.EventRemove ||
+ method.MethodKind == MethodKind.EventRaise ||
+ method.MethodKind == MethodKind.DelegateInvoke)
+ {
+ return false;
+ }
+
+ // If the method is an explicitly implemented getter or setter, exclude it.
+ // https://github.com/dotnet/roslyn/issues/53911
+ if (method.MethodKind == MethodKind.ExplicitInterfaceImplementation &&
+ method.ExplicitInterfaceImplementations.Any(m => m is { MethodKind: MethodKind.PropertyGet or MethodKind.PropertySet }))
+ {
+ return false;
+ }
+ }
+
+ if (symbol is ITypeSymbol type)
+ {
+ if (type.DeclaredAccessibility == Accessibility.Internal && (
+ // exclude the compiler generated `` type
+ type.Name == "" ||
+ // exclude any types which the compiler embedded - marked with EmbeddedAttribute.
+ // these will be generated by the compiler when compiling C# syntax that requires them.
+ type.GetAttributes().Any(a => a.AttributeClass?.Name == "EmbeddedAttribute" && a.AttributeClass?.ContainingNamespace.ToDisplayString() == "Microsoft.CodeAnalysis")))
+ {
+ return false;
+ }
+ }
+
+ // exclude compiler-synthesized members on record
+ if (symbol.ContainingType is { IsRecord: true })
+ {
+ if (symbol.IsCompilerGenerated())
+ {
+ return false;
+ }
+
+ // see if we can identify the record parameter syntax by locating the compiler generated constructor
+ if (symbol.ContainingType.TryGetRecordConstructor(out IMethodSymbol? recordConstructor))
+ {
+ // exclude the compiler generated constructor
+ if (SymbolEqualityComparer.Default.Equals(symbol, recordConstructor))
+ {
+ return false;
+ }
+
+ // exclude the compiler generated properties
+ if (symbol is IPropertySymbol)
+ {
+ // Exclude members with the same name as the record constructor's parameters
+ return !recordConstructor.Parameters.Any(p => p.Name == symbol.Name);
+ }
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/IAssemblySymbolLoader.cs b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/IAssemblySymbolLoader.cs
index cfb78f0a13d1..23fdfc160759 100644
--- a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/IAssemblySymbolLoader.cs
+++ b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/IAssemblySymbolLoader.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.CodeAnalysis;
+using Microsoft.DotNet.ApiSymbolExtensions.Logging;
namespace Microsoft.DotNet.ApiSymbolExtensions
{
@@ -34,12 +35,19 @@ public interface IAssemblySymbolLoader
bool HasLoadWarnings(out IReadOnlyList warnings);
///
- /// Loads a list of assemblies and gets its corresponding from the specified paths.
+ /// Loads a set of assemblies from the filesystem and gets their corresponding instances as a list.
///
/// List of paths to load binaries from. Can be full paths to binaries or directories.
- /// The list of resolved .
+ /// The list of resolved instances, which can be resolved to .
IReadOnlyList LoadAssemblies(params string[] paths);
+ ///
+ /// Loads a set of assemblies from the filesystem and gets their corresponding instances as a dictionary.
+ ///
+ /// List of paths to load binaries from. Can be full paths to binaries or directories.
+ /// The dictionary of resolved instances,excluding those which resolved as .
+ IDictionary LoadAssembliesAsDictionary(params string[] paths);
+
///
/// Loads assemblies from an archive based on the given relative paths.
///
@@ -60,7 +68,7 @@ public interface IAssemblySymbolLoader
///
/// The name to use to resolve the assembly.
/// The stream to read the metadata from.
- /// representing the given . If an
+ /// representing the given . If an
/// assembly with the same was already loaded, the previously loaded assembly is returned.
IAssemblySymbol? LoadAssembly(string name, Stream stream);
@@ -83,6 +91,18 @@ public interface IAssemblySymbolLoader
/// The list of matching assemblies represented as .
IEnumerable LoadMatchingAssemblies(IEnumerable fromAssemblies, IEnumerable searchPaths, bool validateMatchingIdentity = true, bool warnOnMissingAssemblies = true);
+ ///
+ /// Logs all diagnostics that were emitted during the loading of the assemblies.
+ ///
+ /// Optional custom message to prepend to the diagnostics.
+ void LogAllDiagnostics(string? headerMessage = null);
+
+ ///
+ /// Logs all warnings that were emitted during the loading of the assemblies.
+ ///
+ /// Optional custom message to prepend to the warnings.
+ void LogAllWarnings(string? headerMessage = null);
+
///
/// The list of metadata references represented as .
///
diff --git a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Microsoft.DotNet.ApiSymbolExtensions.csproj b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Microsoft.DotNet.ApiSymbolExtensions.csproj
index c531b3bd79a7..c9d256170375 100644
--- a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Microsoft.DotNet.ApiSymbolExtensions.csproj
+++ b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/Microsoft.DotNet.ApiSymbolExtensions.csproj
@@ -19,6 +19,7 @@
+
diff --git a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/SymbolExtensions.cs b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/SymbolExtensions.cs
index 8e7e44b929bc..c8a9b5239cb7 100644
--- a/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/SymbolExtensions.cs
+++ b/src/Compatibility/Microsoft.DotNet.ApiSymbolExtensions/SymbolExtensions.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
namespace Microsoft.DotNet.ApiSymbolExtensions
@@ -126,5 +127,33 @@ public static bool IsEventAdderOrRemover(this IMethodSymbol method) =>
method.MethodKind == MethodKind.EventRemove ||
method.Name.StartsWith("add_", StringComparison.Ordinal) ||
method.Name.StartsWith("remove_", StringComparison.Ordinal);
+
+ // Locates constructor generated by the compiler for `record Foo(...)` syntax
+ // If the type is a record and the compiler generated constructor is found it will be returned, otherwise null.
+ // The compiler will not generate a constructor in the case where the user defined it themself without using an argument list
+ // in the record declaration, or if the record has no parameters.
+ public static bool TryGetRecordConstructor(this INamedTypeSymbol type, [NotNullWhen(true)] out IMethodSymbol? recordConstructor)
+ {
+ if (!type.IsRecord)
+ {
+ recordConstructor = null;
+ return false;
+ }
+
+ // Locate the compiler generated Deconstruct method.
+ var deconstructMethod = (IMethodSymbol?)type.GetMembers("Deconstruct")
+ .FirstOrDefault(m => m is IMethodSymbol && m.IsCompilerGenerated());
+
+ // Locate the compiler generated constructor by matching parameters to Deconstruct - since we cannot locate it with an attribute.
+ recordConstructor = (IMethodSymbol?)type.GetMembers(".ctor")
+ .FirstOrDefault(m => m is IMethodSymbol method &&
+ method.MethodKind == MethodKind.Constructor &&
+ (deconstructMethod == null ?
+ method.Parameters.IsEmpty :
+ method.Parameters.Select(p => p.Type).SequenceEqual(
+ deconstructMethod.Parameters.Select(p => p.Type), SymbolEqualityComparer.Default)));
+
+ return recordConstructor != null;
+ }
}
}
diff --git a/test/Microsoft.DotNet.ApiCompat.IntegrationTests/CompatibleFrameworkInPackageValidatorIntegrationTests.cs b/test/Microsoft.DotNet.ApiCompat.IntegrationTests/CompatibleFrameworkInPackageValidatorIntegrationTests.cs
index 91a896625fd5..f2c1acde2421 100644
--- a/test/Microsoft.DotNet.ApiCompat.IntegrationTests/CompatibleFrameworkInPackageValidatorIntegrationTests.cs
+++ b/test/Microsoft.DotNet.ApiCompat.IntegrationTests/CompatibleFrameworkInPackageValidatorIntegrationTests.cs
@@ -27,7 +27,7 @@ public CompatibleFrameworkInPackageValidatorIntegrationTests(ITestOutputHelper l
new ApiCompatRunner(log,
new SuppressionEngine(),
new ApiComparerFactory(new RuleFactory(log)),
- new AssemblySymbolLoaderFactory()));
+ new AssemblySymbolLoaderFactory(log)));
return (log, validator);
}
diff --git a/test/Microsoft.DotNet.ApiCompat.IntegrationTests/Task/ValidatePackageTargetIntegrationTests.cs b/test/Microsoft.DotNet.ApiCompat.IntegrationTests/Task/ValidatePackageTargetIntegrationTests.cs
index 6bbb8beb8362..028fddd8d262 100644
--- a/test/Microsoft.DotNet.ApiCompat.IntegrationTests/Task/ValidatePackageTargetIntegrationTests.cs
+++ b/test/Microsoft.DotNet.ApiCompat.IntegrationTests/Task/ValidatePackageTargetIntegrationTests.cs
@@ -29,7 +29,7 @@ public ValidatePackageTargetIntegrationTests(ITestOutputHelper log) : base(log)
new ApiCompatRunner(log,
new SuppressionEngine(),
new ApiComparerFactory(new RuleFactory(log)),
- new AssemblySymbolLoaderFactory()));
+ new AssemblySymbolLoaderFactory(log)));
return (log, validator);
}
diff --git a/test/Microsoft.DotNet.ApiCompatibility.Tests/Microsoft.DotNet.ApiCompatibility.Tests.csproj b/test/Microsoft.DotNet.ApiCompatibility.Tests/Microsoft.DotNet.ApiCompatibility.Tests.csproj
index e2c0ccd07842..a5b1a26c8789 100644
--- a/test/Microsoft.DotNet.ApiCompatibility.Tests/Microsoft.DotNet.ApiCompatibility.Tests.csproj
+++ b/test/Microsoft.DotNet.ApiCompatibility.Tests/Microsoft.DotNet.ApiCompatibility.Tests.csproj
@@ -17,7 +17,9 @@
+
+
-
+
diff --git a/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/AssemblyIdentityMustMatchTests.cs b/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/AssemblyIdentityMustMatchTests.cs
index ca9fe2ca9a06..64f09bd6da92 100644
--- a/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/AssemblyIdentityMustMatchTests.cs
+++ b/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/AssemblyIdentityMustMatchTests.cs
@@ -7,6 +7,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.DotNet.ApiCompatibility.Tests;
using Microsoft.DotNet.ApiSymbolExtensions;
+using Microsoft.DotNet.ApiSymbolExtensions.Logging;
using Microsoft.DotNet.ApiSymbolExtensions.Tests;
namespace Microsoft.DotNet.ApiCompatibility.Rules.Tests
@@ -191,13 +192,16 @@ public void RetargetableFlagSet(bool strictMode)
[assembly: AssemblyFlags(AssemblyNameFlags.Retargetable)]
";
+ ILog logger = new ConsoleLog(MessageImportance.High);
+
// Emitting the assembly to a physical location to workaround:
// https://github.com/dotnet/roslyn/issues/54836
+
string leftAssembly = SymbolFactory.EmitAssemblyFromSyntax(syntax, publicKey: _publicKey);
string rightAssembly = SymbolFactory.EmitAssemblyFromSyntax(syntax);
- IAssemblySymbol leftSymbol = new AssemblySymbolLoader().LoadAssembly(leftAssembly);
- IAssemblySymbol rightSymbol = new AssemblySymbolLoader().LoadAssembly(rightAssembly);
+ IAssemblySymbol leftSymbol = new AssemblySymbolLoader(logger).LoadAssembly(leftAssembly);
+ IAssemblySymbol rightSymbol = new AssemblySymbolLoader(logger).LoadAssembly(rightAssembly);
Assert.True(leftSymbol.Identity.IsRetargetable);
Assert.True(rightSymbol.Identity.IsRetargetable);
@@ -210,4 +214,3 @@ public void RetargetableFlagSet(bool strictMode)
}
}
}
-
diff --git a/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/AttributesMustMatchTests.cs b/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/AttributesMustMatchTests.cs
index 11d9b98aea4f..221bb16cdedb 100644
--- a/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/AttributesMustMatchTests.cs
+++ b/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/AttributesMustMatchTests.cs
@@ -20,7 +20,7 @@ public class AttributesMustMatchTests
* - ReturnValues
* - Constructors
* - Generic Parameters
- *
+ *
* Grouped into:
* - Type
* - Member
@@ -29,7 +29,7 @@ public class AttributesMustMatchTests
private static readonly TestRuleFactory s_ruleFactory = new((settings, context) => new AttributesMustMatch(settings, context));
private static ISymbolFilter GetAccessibilityAndAttributeSymbolFiltersAsComposite(params string[] excludeAttributeFiles) =>
- new CompositeSymbolFilter().Add(new AccessibilitySymbolFilter(false)).Add(new DocIdSymbolFilter(excludeAttributeFiles));
+ new CompositeSymbolFilter().Add(new AccessibilitySymbolFilter(false)).Add(DocIdSymbolFilter.CreateFromFiles(excludeAttributeFiles));
public static TheoryData TypesCases => new()
{
@@ -39,7 +39,7 @@ private static ISymbolFilter GetAccessibilityAndAttributeSymbolFiltersAsComposit
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -56,7 +56,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -77,7 +77,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -94,7 +94,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -116,7 +116,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -133,7 +133,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -156,7 +156,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -173,7 +173,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -196,7 +196,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -212,7 +212,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -311,7 +311,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -337,7 +337,7 @@ public void F() {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -370,7 +370,7 @@ public void F() {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -396,7 +396,7 @@ public class First {
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -429,7 +429,7 @@ public class First {
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -457,7 +457,7 @@ public class First {
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -492,7 +492,7 @@ public class First {
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -518,7 +518,7 @@ public First() {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -551,7 +551,7 @@ public First() {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -577,7 +577,7 @@ public class First {
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -610,7 +610,7 @@ public class First {
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -634,7 +634,7 @@ public void F([Bar] int v, [Foo(""S"", A = true, B = 0)] string s) {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -666,7 +666,7 @@ public void F([Baz] int v, [Foo(""T"")] string s) {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -687,7 +687,7 @@ public class First<[Bar] T1, [Foo(""S"", A = true, B = 0)] T2> {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -716,7 +716,7 @@ public class First<[Baz] T1, [Foo(""T"")] T2> {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -740,7 +740,7 @@ public class First {
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -792,7 +792,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -815,7 +815,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -832,7 +832,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -1366,7 +1366,7 @@ public void TestExclusionsFilteredOut()
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -1382,7 +1382,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -1418,7 +1418,7 @@ public void AttributesExcludedButMembersValidated()
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
@@ -1433,7 +1433,7 @@ public class First {}
namespace CompatTests
{
using System;
-
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class FooAttribute : Attribute {
public FooAttribute(String s) {}
diff --git a/test/Microsoft.DotNet.ApiSymbolExtensions.Tests/AssemblySymbolLoaderTests.cs b/test/Microsoft.DotNet.ApiSymbolExtensions.Tests/AssemblySymbolLoaderTests.cs
index bb31747720b9..4091987d73cc 100644
--- a/test/Microsoft.DotNet.ApiSymbolExtensions.Tests/AssemblySymbolLoaderTests.cs
+++ b/test/Microsoft.DotNet.ApiSymbolExtensions.Tests/AssemblySymbolLoaderTests.cs
@@ -6,12 +6,15 @@
using System.Collections.Concurrent;
using System.Reflection;
using Microsoft.CodeAnalysis;
+using Microsoft.DotNet.ApiSymbolExtensions.Logging;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.ApiSymbolExtensions.Tests
{
public class AssemblySymbolLoaderTests : SdkTest
{
+ private ILog _logger = new ConsoleLog(MessageImportance.High);
+
public AssemblySymbolLoaderTests(ITestOutputHelper log) : base(log) { }
private const string SimpleAssemblySourceContents = @"
@@ -89,14 +92,14 @@ private TestAssetInfo GetAsset(TestAssetsManager manager)
[Fact]
public void LoadAssembly_Throws()
{
- AssemblySymbolLoader loader = new();
+ AssemblySymbolLoader loader = new(_logger);
Assert.Throws(() => loader.LoadAssembly(Guid.NewGuid().ToString("N").Substring(0, 8)));
}
[Fact]
public void LoadAssemblyFromSourceFiles_Throws()
{
- AssemblySymbolLoader loader = new();
+ AssemblySymbolLoader loader = new(_logger);
IEnumerable paths = new[] { Guid.NewGuid().ToString("N") };
Assert.Throws(() => loader.LoadAssemblyFromSourceFiles(paths, "assembly1", Array.Empty()));
Assert.Throws("filePaths", () => loader.LoadAssemblyFromSourceFiles(Array.Empty(), "assembly1", Array.Empty()));
@@ -106,7 +109,7 @@ public void LoadAssemblyFromSourceFiles_Throws()
[Fact]
public void LoadMatchingAssemblies_Throws()
{
- AssemblySymbolLoader loader = new();
+ AssemblySymbolLoader loader = new(_logger);
IEnumerable paths = new[] { Guid.NewGuid().ToString("N") };
IAssemblySymbol assembly = SymbolFactory.GetAssemblyFromSyntax("namespace MyNamespace { class Foo { } }");
@@ -119,7 +122,7 @@ public void LoadMatchingAssembliesWarns()
IAssemblySymbol assembly = SymbolFactory.GetAssemblyFromSyntax("namespace MyNamespace { class Foo { } }");
IEnumerable paths = new[] { AppContext.BaseDirectory };
- AssemblySymbolLoader loader = new();
+ AssemblySymbolLoader loader = new(_logger);
IEnumerable symbols = loader.LoadMatchingAssemblies(new[] { assembly }, paths);
Assert.Empty(symbols);
Assert.True(loader.HasLoadWarnings(out IReadOnlyList warnings));
@@ -152,7 +155,7 @@ public void LoadMatchingAssembliesSameIdentitySucceeds()
.Should()
.Pass();
- AssemblySymbolLoader loader = new();
+ AssemblySymbolLoader loader = new(_logger);
IEnumerable matchingAssemblies = loader.LoadMatchingAssemblies(new[] { fromAssembly }, new[] { outputDirectory });
Assert.Single(matchingAssemblies);
@@ -167,7 +170,7 @@ public void LoadMatchingAssemblies_DifferentIdentity(bool validateIdentities)
var assetInfo = GetSimpleTestAsset();
IAssemblySymbol fromAssembly = SymbolFactory.GetAssemblyFromSyntax(SimpleAssemblySourceContents, assemblyName: assetInfo.TestAsset.TestProject.Name);
- AssemblySymbolLoader loader = new();
+ AssemblySymbolLoader loader = new(_logger);
IEnumerable matchingAssemblies = loader.LoadMatchingAssemblies(new[] { fromAssembly }, new[] { assetInfo.OutputDirectory }, validateMatchingIdentity: validateIdentities);
if (validateIdentities)
@@ -194,7 +197,7 @@ public void LoadMatchingAssemblies_DifferentIdentity(bool validateIdentities)
public void LoadsSimpleAssemblyFromDirectory()
{
var assetInfo = GetSimpleTestAsset();
- AssemblySymbolLoader loader = new();
+ AssemblySymbolLoader loader = new(_logger);
IEnumerable symbols = loader.LoadAssemblies(assetInfo.OutputDirectory);
Assert.Single(symbols);
@@ -212,7 +215,7 @@ public void LoadsSimpleAssemblyFromDirectory()
public void LoadSimpleAssemblyFullPath()
{
var assetInfo = GetSimpleTestAsset();
- AssemblySymbolLoader loader = new();
+ AssemblySymbolLoader loader = new(_logger);
IAssemblySymbol symbol = loader.LoadAssembly(Path.Combine(assetInfo.OutputDirectory, assetInfo.TestAsset.TestProject.Name + ".dll"));
IEnumerable types = symbol.GlobalNamespace
@@ -246,7 +249,7 @@ public void LoadsMultipleAssembliesFromDirectory()
.Should()
.Pass();
- AssemblySymbolLoader loader = new();
+ AssemblySymbolLoader loader = new(_logger);
IEnumerable symbols = loader.LoadAssemblies(outputDirectory);
Assert.Equal(2, symbols.Count());
@@ -263,7 +266,7 @@ public void LoadsMultipleAssembliesFromDirectory()
public void LoadAssemblyResolveReferences_WarnsWhenEnabled(bool resolveReferences)
{
var assetInfo = GetSimpleTestAsset();
- AssemblySymbolLoader loader = new(resolveAssemblyReferences: resolveReferences);
+ AssemblySymbolLoader loader = new(_logger, resolveAssemblyReferences: resolveReferences);
loader.LoadAssembly(Path.Combine(assetInfo.OutputDirectory, assetInfo.TestAsset.TestProject.Name + ".dll"));
if (resolveReferences)
@@ -295,7 +298,7 @@ public void LoadAssemblyResolveReferences_WarnsWhenEnabled(bool resolveReference
public void LoadAssembliesShouldResolveReferencesNoWarnings()
{
var assetInfo = GetSimpleTestAsset();
- AssemblySymbolLoader loader = new(resolveAssemblyReferences: true);
+ AssemblySymbolLoader loader = new(_logger, resolveAssemblyReferences: true);
// AddReferenceSearchDirectories should be able to handle directories as well as full path to assemblies.
loader.AddReferenceSearchPaths(Path.GetDirectoryName(typeof(string).Assembly.Location));
loader.AddReferenceSearchPaths(Path.GetFullPath(typeof(string).Assembly.Location));
@@ -314,7 +317,7 @@ public void LoadAssemblyFromStreamNoWarns()
{
var assetInfo = GetSimpleTestAsset();
TestProject testProject = assetInfo.TestAsset.TestProject;
- AssemblySymbolLoader loader = new();
+ AssemblySymbolLoader loader = new(_logger);
using FileStream stream = File.OpenRead(Path.Combine(assetInfo.OutputDirectory, testProject.Name + ".dll"));
IAssemblySymbol symbol = loader.LoadAssembly(testProject.Name, stream);
diff --git a/test/Microsoft.DotNet.GenAPI.Tests/CSharpFileBuilderTests.cs b/test/Microsoft.DotNet.GenAPI.Tests/CSharpFileBuilderTests.cs
index 68009dbd4f0e..3f5d4a5cc3ad 100644
--- a/test/Microsoft.DotNet.GenAPI.Tests/CSharpFileBuilderTests.cs
+++ b/test/Microsoft.DotNet.GenAPI.Tests/CSharpFileBuilderTests.cs
@@ -9,13 +9,13 @@
using Microsoft.DotNet.ApiSymbolExtensions;
using Microsoft.DotNet.ApiSymbolExtensions.Filtering;
using Microsoft.DotNet.ApiSymbolExtensions.Logging;
-using Microsoft.DotNet.ApiSymbolExtensions.Tests;
-using Microsoft.DotNet.GenAPI.Filtering;
namespace Microsoft.DotNet.GenAPI.Tests
{
public class CSharpFileBuilderTests
{
+ private readonly ILog _logger = new ConsoleLog(MessageImportance.High);
+
class AllowAllFilter : ISymbolFilter
{
public bool Include(ISymbol symbol) => true;
@@ -32,44 +32,26 @@ private void RunTest(string original,
bool includeEffectivelyPrivateSymbols = true,
bool includeExplicitInterfaceImplementationSymbols = true,
bool allowUnsafe = false,
- string excludedAttributeFile = null,
+ string[] excludedAttributeList = null,
[CallerMemberName] string assemblyName = "")
{
- StringWriter stringWriter = new();
-
- // Configure symbol filters
- AccessibilitySymbolFilter accessibilitySymbolFilter = new(
- includeInternalSymbols,
- includeEffectivelyPrivateSymbols,
- includeExplicitInterfaceImplementationSymbols);
-
- CompositeSymbolFilter symbolFilter = new CompositeSymbolFilter()
- .Add(new ImplicitSymbolFilter())
- .Add(accessibilitySymbolFilter);
+ using StringWriter stringWriter = new();
- CompositeSymbolFilter attributeDataSymbolFilter = new();
- if (excludedAttributeFile is not null)
- {
- attributeDataSymbolFilter.Add(new DocIdSymbolFilter(new string[] { excludedAttributeFile }));
- }
- attributeDataSymbolFilter.Add(accessibilitySymbolFilter);
+ (IAssemblySymbolLoader loader, Dictionary assemblySymbols) = TestAssemblyLoaderFactory
+ .CreateFromTexts(_logger, assemblyTexts: [(assemblyName, original)], respectInternals: includeInternalSymbols, allowUnsafe);
- IAssemblySymbolWriter csharpFileBuilder = new CSharpFileBuilder(
+ IAssemblySymbolWriter writer = new CSharpFileBuilder(
new ConsoleLog(MessageImportance.Low),
- symbolFilter,
- attributeDataSymbolFilter,
stringWriter,
- null,
- false,
+ loader,
+ CompositeSymbolFilter.GetSymbolFilterFromList([], includeInternalSymbols, includeEffectivelyPrivateSymbols, includeExplicitInterfaceImplementationSymbols),
+ CompositeSymbolFilter.GetAttributeFilterFromList(excludedAttributeList, includeInternalSymbols, includeEffectivelyPrivateSymbols, includeExplicitInterfaceImplementationSymbols),
+ header: string.Empty,
+ exceptionMessage: null,
+ includeAssemblyAttributes: false,
MetadataReferences);
- using Stream assemblyStream = SymbolFactory.EmitAssemblyStreamFromSyntax(original, enableNullable: true, allowUnsafe: allowUnsafe, assemblyName: assemblyName);
- AssemblySymbolLoader assemblySymbolLoader = new(resolveAssemblyReferences: true, includeInternalSymbols: includeInternalSymbols);
- assemblySymbolLoader.AddReferenceSearchPaths(typeof(object).Assembly!.Location!);
- assemblySymbolLoader.AddReferenceSearchPaths(typeof(DynamicAttribute).Assembly!.Location!);
- IAssemblySymbol assemblySymbol = assemblySymbolLoader.LoadAssembly(assemblyName, assemblyStream);
-
- csharpFileBuilder.WriteAssembly(assemblySymbol);
+ writer.WriteAssembly(assemblySymbols.First().Value);
StringBuilder stringBuilder = stringWriter.GetStringBuilder();
string resultedString = stringBuilder.ToString();
@@ -231,7 +213,7 @@ public void TestRecordDeclaration()
{
RunTest(original: """
namespace Foo
- {
+ {
public record RecordClass;
public record RecordClass1(int i);
public record RecordClass2(string s, int i);
@@ -240,7 +222,7 @@ public record DerivedRecord2(string x, int i, double d) : RecordClass2(default(s
public record DerivedRecord3(string x, int i, double d) : RecordClass2(default(string)!, i);
public record DerivedRecord4(double d) : RecordClass2(default(string)!, default);
public record DerivedRecord5() : RecordClass2(default(string)!, default);
-
+
public record RecordClassWithMethods(int i)
{
public void DoSomething() { }
@@ -345,11 +327,11 @@ public void TestRecordStructDeclaration()
RunTest(original: """
namespace Foo
{
-
- public record struct RecordStruct;
+
+ public record struct RecordStruct;
public record struct RecordStruct1(int i);
public record struct RecordStruct2(string s, int i);
-
+
public record struct RecordStructWithMethods(int i)
{
public void DoSomething() { }
@@ -367,10 +349,10 @@ public record struct RecordStructWithConstructors(int i)
public RecordStructWithConstructors() : this(1) { }
public RecordStructWithConstructors(string s) : this(int.Parse(s)) { }
}
-
+
}
""",
- expected: """
+ expected: """
namespace Foo
{
public partial struct RecordStruct : System.IEquatable
@@ -1644,12 +1626,12 @@ public class B
{
public B(int i) {}
}
-
+
public class C : B
{
internal C() : base(0) {}
}
-
+
public class D : B
{
internal D(int i) : base(i) {}
@@ -1672,7 +1654,7 @@ public partial class B
{
public B(int i) {}
}
-
+
public partial class C : B
{
internal C() : base(default) {}
@@ -1702,12 +1684,12 @@ public class B
{
public B(int i) {}
}
-
+
public class C : B
{
internal C() : base(0) {}
}
-
+
public class D : B
{
internal D(int i) : base(i) {}
@@ -1781,8 +1763,8 @@ namespace A
public partial class B
{
protected B() {}
- }
-
+ }
+
public partial class C : B
{
internal C() {}
@@ -1935,7 +1917,7 @@ public class B : A
public class D { }
public class Id { }
-
+
public class V { }
}
""",
@@ -2761,10 +2743,6 @@ public class PublicClass { }
[Fact]
public void TestAttributesExcludedWithFilter()
{
- using TempDirectory root = new();
- string filePath = Path.Combine(root.DirPath, "exclusions.txt");
- File.WriteAllText(filePath, "T:A.AnyTestAttribute");
-
RunTest(original: """
namespace A
{
@@ -2800,7 +2778,7 @@ public partial class PublicClass
}
""",
includeInternalSymbols: false,
- excludedAttributeFile: filePath);
+ excludedAttributeList: ["T:A.AnyTestAttribute"]);
}
[Fact]
@@ -2828,7 +2806,7 @@ public class Foo : System.Collections.ICollection, System.Collections.Generic
}
}
-
+
""",
// https://github.com/dotnet/sdk/issues/32195 tracks interface expansion
expected: """
@@ -2909,7 +2887,7 @@ namespace N {
public ref struct C
where T : unmanaged
{
- public required (string? k, dynamic v, nint n) X { get; init; }
+ public required (string? k, dynamic v, nint n) X { get; init; }
}
public static class E
@@ -2918,7 +2896,7 @@ public static void M(this object c, scoped System.ReadOnlySpan values) { }
}
}
""",
- expected: """
+ expected: """
namespace N
{
public ref partial struct C
@@ -2982,7 +2960,7 @@ public void TestExplicitInterfaceNonGenericCollections()
namespace a
{
#pragma warning disable CS8597
-
+
public partial class MyStringCollection : ICollection, IEnumerable, IList
{
public int Count { get { throw null; } }
@@ -3006,7 +2984,7 @@ public void RemoveAt(int index) { }
void ICollection.CopyTo(Array array, int index) { }
IEnumerator IEnumerable.GetEnumerator() { throw null; }
int IList.Add(object? value) { throw null; }
- bool IList.Contains(object? value) { throw null; }
+ bool IList.Contains(object? value) { throw null; }
int IList.IndexOf(object? value) { throw null; }
void IList.Insert(int index, object? value) { }
void IList.Remove(object? value) { }
@@ -3015,7 +2993,7 @@ void IList.Remove(object? value) { }
#pragma warning restore CS8597
}
""",
- expected: """
+ expected: """
namespace a
{
public partial class MyStringCollection : System.Collections.ICollection, System.Collections.IEnumerable, System.Collections.IList
diff --git a/test/Microsoft.DotNet.GenAPI.Tests/SyntaxRewriter/TypeDeclarationCSharpSyntaxRewriterTests.cs b/test/Microsoft.DotNet.GenAPI.Tests/SyntaxRewriter/TypeDeclarationCSharpSyntaxRewriterTests.cs
index c2f498c1651c..f33ea9ec90e4 100644
--- a/test/Microsoft.DotNet.GenAPI.Tests/SyntaxRewriter/TypeDeclarationCSharpSyntaxRewriterTests.cs
+++ b/test/Microsoft.DotNet.GenAPI.Tests/SyntaxRewriter/TypeDeclarationCSharpSyntaxRewriterTests.cs
@@ -10,7 +10,7 @@ public class TypeDeclarationCSharpSyntaxRewriterTests : CSharpSyntaxRewriterTest
[Fact]
public void TestRemoveSystemObjectAsBaseClass()
{
- CompareSyntaxTree(new TypeDeclarationCSharpSyntaxRewriter(),
+ CompareSyntaxTree(new TypeDeclarationCSharpSyntaxRewriter(addPartialModifier: true),
original: """
namespace A
{
@@ -32,7 +32,7 @@ partial class B
[Fact]
public void TestAddPartialKeyword()
{
- CompareSyntaxTree(new TypeDeclarationCSharpSyntaxRewriter(),
+ CompareSyntaxTree(new TypeDeclarationCSharpSyntaxRewriter(addPartialModifier: true),
original: """
namespace A
{
@@ -54,7 +54,7 @@ partial interface D { }
[Fact]
public void TestPartialTypeDeclaration()
{
- CompareSyntaxTree(new TypeDeclarationCSharpSyntaxRewriter(),
+ CompareSyntaxTree(new TypeDeclarationCSharpSyntaxRewriter(addPartialModifier: true),
original: """
namespace A
{
diff --git a/test/Microsoft.DotNet.GenAPI.Tests/TestAssemblyLoaderFactory.cs b/test/Microsoft.DotNet.GenAPI.Tests/TestAssemblyLoaderFactory.cs
new file mode 100644
index 000000000000..8f7fde2cfe2a
--- /dev/null
+++ b/test/Microsoft.DotNet.GenAPI.Tests/TestAssemblyLoaderFactory.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+using Microsoft.CodeAnalysis;
+using Microsoft.DotNet.ApiSymbolExtensions;
+using Microsoft.DotNet.ApiSymbolExtensions.Logging;
+using Microsoft.DotNet.ApiSymbolExtensions.Tests;
+
+namespace Microsoft.DotNet.GenAPI.Tests;
+
+public class TestAssemblyLoaderFactory
+{
+ public static (IAssemblySymbolLoader, Dictionary) CreateFromTexts(ILog logger, (string, string)[] assemblyTexts, bool respectInternals = false, bool allowUnsafe = false)
+ {
+ if (assemblyTexts.Length == 0)
+ {
+ return (new AssemblySymbolLoader(logger, resolveAssemblyReferences: true, includeInternalSymbols: respectInternals), new Dictionary());
+ }
+
+ AssemblySymbolLoader loader = new(logger, resolveAssemblyReferences: true, includeInternalSymbols: respectInternals);
+ loader.AddReferenceSearchPaths(typeof(object).Assembly!.Location!);
+ loader.AddReferenceSearchPaths(typeof(DynamicAttribute).Assembly!.Location!);
+
+ Dictionary assemblySymbols = new();
+ foreach ((string assemblyName, string assemblyText) in assemblyTexts)
+ {
+ using Stream assemblyStream = SymbolFactory.EmitAssemblyStreamFromSyntax(assemblyText, enableNullable: true, allowUnsafe: allowUnsafe, assemblyName: assemblyName);
+ if (loader.LoadAssembly(assemblyName, assemblyStream) is IAssemblySymbol assemblySymbol)
+ {
+ assemblySymbols.Add(assemblyName, assemblySymbol);
+ }
+ }
+
+ return (loader, assemblySymbols);
+ }
+}