From 5cc42ffc6162bdcf41fceb09a9373062557feb21 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Tue, 10 Oct 2023 18:23:24 -0500 Subject: [PATCH 1/7] wip --- .../APIView/Languages/CodeFileBuilder.cs | 43 +++++++++++++++++-- .../APIView/Languages/CompilationFactory.cs | 8 ++-- .../APIViewUnitTests/APIViewUnitTests.csproj | 1 + .../AssemblyReferences/InternalsVisibleTo.cs | 33 ++++++++++++++ .../APIViewUnitTests/CodeFileBuilderTests.cs | 13 ++++-- src/dotnet/APIView/APIViewUnitTests/Common.cs | 12 ++++++ .../APIViewUnitTests/DiagnosticProject.cs | 16 ++++++- .../ExactFormatting/InternalsVisibleTo.cs | 33 ++++++++++++++ 8 files changed, 145 insertions(+), 14 deletions(-) create mode 100644 src/dotnet/APIView/APIViewUnitTests/AssemblyReferences/InternalsVisibleTo.cs create mode 100644 src/dotnet/APIView/APIViewUnitTests/ExactFormatting/InternalsVisibleTo.cs diff --git a/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs b/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs index a5d4f9e3cc6..fa07bb96590 100644 --- a/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs +++ b/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using APIView; @@ -6,11 +6,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.SymbolDisplay; -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; -using System.IO; using System.Linq; namespace ApiView @@ -50,7 +48,7 @@ public class CodeFileBuilder public ICodeFileBuilderSymbolOrderProvider SymbolOrderProvider { get; set; } = new CodeFileBuilderSymbolOrderProvider(); - public const string CurrentVersion = "23"; + public const string CurrentVersion = "24"; private IEnumerable EnumerateNamespaces(IAssemblySymbol assemblySymbol) { @@ -81,6 +79,7 @@ public CodeFile Build(IAssemblySymbol assemblySymbol, bool runAnalysis, List(); foreach (var namespaceSymbol in SymbolOrderProvider.OrderNamespaces(EnumerateNamespaces(assemblySymbol))) @@ -119,6 +118,36 @@ public CodeFile Build(IAssemblySymbol assemblySymbol, bool runAnalysis, List a.AttributeClass.Name == "InternalsVisibleToAttribute"); + if (assemblyAttributes != null && assemblyAttributes.Any()) + { + builder.NewLine(); + builder.Append("InternalsVisibleTo attributes:", CodeFileTokenKind.Text); + builder.NewLine(); + foreach (AttributeData attribute in assemblyAttributes) + { + builder.Append($"{attribute.AttributeClass.Name}(\"", CodeFileTokenKind.Text); + if (attribute.ConstructorArguments.Length > 0) + { + var param = attribute.ConstructorArguments[0].Value.ToString(); + var firstComma = param.IndexOf(','); + param = firstComma > 0 ? param[..firstComma] : param; + builder.Append(new CodeFileToken(param, CodeFileTokenKind.Text) + { + // allow assembly to be commentable + DefinitionId = attribute.AttributeClass.Name + }); + } + builder.Append("\")", CodeFileTokenKind.Text); + builder.NewLine(); + } + + builder.NewLine(); + } + } + public static void BuildDependencies(CodeFileTokensBuilder builder, List dependencies) { if (dependencies != null && dependencies.Any()) @@ -505,6 +534,9 @@ private bool IsHiddenFromIntellisense(ISymbol member) => member.GetAttributes().Any(d => d.AttributeClass?.Name == "EditorBrowsableAttribute" && (EditorBrowsableState) d.ConstructorArguments[0].Value == EditorBrowsableState.Never); + private bool IsDecoratedWithAttribute(ISymbol member, string attributeName) => + member.GetAttributes().Any(d => d.AttributeClass?.Name == attributeName); + private void BuildTypedConstant(CodeFileTokensBuilder builder, TypedConstant typedConstant) { if (typedConstant.IsNull) @@ -692,6 +724,9 @@ private bool IsAccessible(ISymbol s) case Accessibility.ProtectedOrInternal: case Accessibility.Public: return true; + case Accessibility.Internal: + return s.GetAttributes().Any(a => a.AttributeClass.Name == "FriendAttribute") || + (s is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.BaseType?.Name == "Attribute" && namedTypeSymbol.Name == "FriendAttribute"); default: return IsAccessibleExplicitInterfaceImplementation(s); } diff --git a/src/dotnet/APIView/APIView/Languages/CompilationFactory.cs b/src/dotnet/APIView/APIView/Languages/CompilationFactory.cs index db8edae0abd..6b009f4afc4 100644 --- a/src/dotnet/APIView/APIView/Languages/CompilationFactory.cs +++ b/src/dotnet/APIView/APIView/Languages/CompilationFactory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using Microsoft.CodeAnalysis; @@ -11,7 +11,7 @@ namespace ApiView { public static class CompilationFactory { - private static HashSet AllowedAssemblies = new HashSet(new [] + private static HashSet AllowedAssemblies = new HashSet(new[] { "Microsoft.Bcl.AsyncInterfaces" }, StringComparer.InvariantCultureIgnoreCase); @@ -44,7 +44,7 @@ public static IAssemblySymbol GetCompilation(Stream stream, Stream documentation // MetadataReference.CreateFromStream closes the stream reference = MetadataReference.CreateFromStream(memoryStream, documentation: documentation); } - var compilation = CSharpCompilation.Create(null).AddReferences(reference); + var compilation = CSharpCompilation.Create(null, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, metadataImportOptions: MetadataImportOptions.All)).AddReferences(reference); var corlibLocation = typeof(object).Assembly.Location; var runtimeFolder = Path.GetDirectoryName(corlibLocation); @@ -63,4 +63,4 @@ public static IAssemblySymbol GetCompilation(Stream stream, Stream documentation return (IAssemblySymbol)compilation.GetAssemblyOrModuleSymbol(reference); } } -} \ No newline at end of file +} diff --git a/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj b/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj index 07c48a4c833..d72a84b2d3f 100644 --- a/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj +++ b/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj @@ -33,6 +33,7 @@ + diff --git a/src/dotnet/APIView/APIViewUnitTests/AssemblyReferences/InternalsVisibleTo.cs b/src/dotnet/APIView/APIViewUnitTests/AssemblyReferences/InternalsVisibleTo.cs new file mode 100644 index 00000000000..ce6b77bcc66 --- /dev/null +++ b/src/dotnet/APIView/APIViewUnitTests/AssemblyReferences/InternalsVisibleTo.cs @@ -0,0 +1,33 @@ +/*-*/ +using System; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("TestProject")] +class FriendAttribute : Attribute { + public FriendAttribute(string friendAssemblyName) { + FriendAssemblyName = friendAssemblyName; + } + + public string FriendAssemblyName { get; } +} +/*-*/ +namespace A { +public class PublicClass + { + public int PublicProperty { get; set; } + internal int InternalProperty { get; set; } + + [Friend("TestProject")] + internal int InternalPropertyWithFriendAttribute { get; set; } + + public void PublicMethod() + { } + + internal void InternalMethod() + { } + + [Friend("TestProject")] + internal void InternalMethodWithFriendAttribute() + { } + } +} diff --git a/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs b/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs index aba9d33e0f3..8ca3773e3d2 100644 --- a/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs +++ b/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs @@ -37,15 +37,20 @@ public static IEnumerable ExactFormattingFiles [Theory] [MemberData(nameof(ExactFormattingFiles))] public async Task VerifyFormatted(string name) + { + ExtractCodeAndFormat(name, out string code, out string formatted); + await AssertFormattingAsync(code, formatted); + } + + private void ExtractCodeAndFormat(string name, out string code, out string formatted) { var manifestResourceStream = typeof(CodeFileBuilderTests).Assembly.GetManifestResourceStream(name); var streamReader = new StreamReader(manifestResourceStream); - var code = streamReader.ReadToEnd(); + code = streamReader.ReadToEnd(); code = code.Trim(' ', '\t', '\r', '\n'); - var formatted = _stripRegex.Replace(code, string.Empty); + formatted = _stripRegex.Replace(code, string.Empty); formatted = RemoveEmptyLines(formatted); formatted = formatted.Trim(' ', '\t', '\r', '\n'); - await AssertFormattingAsync(code, formatted); } private async Task AssertFormattingAsync(string code, string formatted) @@ -69,7 +74,7 @@ private async Task AssertFormattingAsync(string code, string formatted) private string RemoveEmptyLines(string content) { var lines = content - .Split(Environment.NewLine) + .Split('\n') .Where(s => !string.IsNullOrWhiteSpace(s)) .ToArray(); diff --git a/src/dotnet/APIView/APIViewUnitTests/Common.cs b/src/dotnet/APIView/APIViewUnitTests/Common.cs index a24b8a8592e..c6b30188d86 100644 --- a/src/dotnet/APIView/APIViewUnitTests/Common.cs +++ b/src/dotnet/APIView/APIViewUnitTests/Common.cs @@ -12,6 +12,17 @@ internal static class Common public static async Task BuildDllAsync(Stream stream, string code) { var project = DiagnosticProject.Create(typeof(CodeFileBuilderTests).Assembly, LanguageVersion.Latest, new[] { code }) + .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, metadataImportOptions: MetadataImportOptions.All )); + + var compilation = await project.GetCompilationAsync(); + Assert.Empty(compilation.GetDiagnostics().Where(d => d.Severity > DiagnosticSeverity.Warning)); + + compilation.Emit(stream); + } + + public static async Task BuildDllWithProjectDepAsync(Stream stream, string code, string dependentCode) + { + var project = DiagnosticProject.Create(typeof(CodeFileBuilderTests).Assembly, LanguageVersion.Latest, new[] { code }, new[] { dependentCode }) .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); var compilation = await project.GetCompilationAsync(); @@ -19,5 +30,6 @@ public static async Task BuildDllAsync(Stream stream, string code) compilation.Emit(stream); } + } } diff --git a/src/dotnet/APIView/APIViewUnitTests/DiagnosticProject.cs b/src/dotnet/APIView/APIViewUnitTests/DiagnosticProject.cs index 4e3e3638b14..66f184c0043 100644 --- a/src/dotnet/APIView/APIViewUnitTests/DiagnosticProject.cs +++ b/src/dotnet/APIView/APIViewUnitTests/DiagnosticProject.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using Microsoft.CodeAnalysis; @@ -24,10 +24,11 @@ public class DiagnosticProject /// Project name. /// public static string TestProjectName = "TestProject"; + public static string TestDependencyProjectName = "DependencyTestProject"; private static readonly Dictionary _solutionCache = new Dictionary(); - public static Project Create(Assembly testAssembly, LanguageVersion languageVersion, string[] sources) + public static Project Create(Assembly testAssembly, LanguageVersion languageVersion, string[] sources, string[] dependencySources = null) { Solution solution; lock (_solutionCache) @@ -40,6 +41,16 @@ public static Project Create(Assembly testAssembly, LanguageVersion languageVers .AddProject(projectId, TestProjectName, TestProjectName, LanguageNames.CSharp) .WithProjectParseOptions(projectId, new CSharpParseOptions(languageVersion)); + if(dependencySources?.Length > 0) + { + var dependencyProjectId = ProjectId.CreateNewId(debugName: TestDependencyProjectName); + solution = solution.AddProject(dependencyProjectId, TestDependencyProjectName, TestDependencyProjectName, LanguageNames.CSharp) + .WithProjectParseOptions(dependencyProjectId, new CSharpParseOptions(languageVersion)); + + // Add the dependency project as a reference to the test project + solution = solution.AddProjectReference(projectId, new ProjectReference(dependencyProjectId)); + } + foreach (var defaultCompileLibrary in DependencyContext.Load(testAssembly).CompileLibraries) { foreach (var resolveReferencePath in defaultCompileLibrary.ResolveReferencePaths(new AppLocalResolver())) @@ -51,6 +62,7 @@ public static Project Create(Assembly testAssembly, LanguageVersion languageVers } } + solution.Projects.Single(p => p.Name == TestProjectName); var testProject = solution.ProjectIds.Single(); var fileNamePrefix = DefaultFilePathPrefix; diff --git a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/InternalsVisibleTo.cs b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/InternalsVisibleTo.cs new file mode 100644 index 00000000000..e71eb621047 --- /dev/null +++ b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/InternalsVisibleTo.cs @@ -0,0 +1,33 @@ +/*-*/ +using System; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Azure.Some.Client")] +class FriendAttribute : Attribute { + public FriendAttribute(string friendAssemblyName) { + FriendAssemblyName = friendAssemblyName; + } + + public string FriendAssemblyName { get; } +} +/*-*/ +namespace A { +public class PublicClass + { + public int PublicProperty { get; set; } + internal int InternalProperty { get; set; } + + [Friend("TestProject")] + internal int InternalPropertyWithFriendAttribute { get; set; } + + public void PublicMethod() + { } + + internal void InternalMethod() + { } + + [Friend("TestProject")] + internal void InternalMethodWithFriendAttribute() + { } + } +} From aea807baa2274c0d0263f9ef1d1da56bac0897c3 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 11 Oct 2023 16:52:09 -0500 Subject: [PATCH 2/7] Exposes internal to --- .../APIView/Languages/CodeFileBuilder.cs | 34 +++++++++++++++---- .../APIView/Languages/CompilationFactory.cs | 2 +- .../APIViewUnitTests/APIViewUnitTests.csproj | 2 +- .../AssemblyReferences/InternalsVisibleTo.cs | 33 ------------------ .../APIViewUnitTests/CodeFileBuilderTests.cs | 24 +++++++++---- .../ExactFormatting/Accessibility.cs | 3 +- .../APIViewUnitTests/ExactFormatting/Class.cs | 1 + .../ExactFormatting/Delegate.cs | 1 + .../APIViewUnitTests/ExactFormatting/Enum.cs | 3 ++ .../ExactFormatting/Fields.cs | 1 + .../ExactFormatting/InternalsVisibleTo.cs | 33 ------------------ .../ExactFormatting/Methods.cs | 1 + .../ExactFormatting/Properties.cs | 1 + .../ExactFormatting/Struct.cs | 3 +- 14 files changed, 59 insertions(+), 83 deletions(-) delete mode 100644 src/dotnet/APIView/APIViewUnitTests/AssemblyReferences/InternalsVisibleTo.cs delete mode 100644 src/dotnet/APIView/APIViewUnitTests/ExactFormatting/InternalsVisibleTo.cs diff --git a/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs b/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs index fa07bb96590..7f8adf7cc1b 100644 --- a/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs +++ b/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs @@ -120,15 +120,17 @@ public CodeFile Build(IAssemblySymbol assemblySymbol, bool runAnalysis, List a.AttributeClass.Name == "InternalsVisibleToAttribute"); + var assemblyAttributes = assemblySymbol.GetAttributes() + .Where(a => + a.AttributeClass.Name == "InternalsVisibleToAttribute" && + !a.ConstructorArguments[0].Value.ToString().Contains(".Tests") && + !a.ConstructorArguments[0].Value.ToString().Contains("DynamicProxyGenAssembly2")); if (assemblyAttributes != null && assemblyAttributes.Any()) { - builder.NewLine(); - builder.Append("InternalsVisibleTo attributes:", CodeFileTokenKind.Text); + builder.Append("Exposes internals to:", CodeFileTokenKind.Text); builder.NewLine(); foreach (AttributeData attribute in assemblyAttributes) { - builder.Append($"{attribute.AttributeClass.Name}(\"", CodeFileTokenKind.Text); if (attribute.ConstructorArguments.Length > 0) { var param = attribute.ConstructorArguments[0].Value.ToString(); @@ -140,7 +142,6 @@ public static void BuildInternalsVisibleToAttributes(CodeFileTokensBuilder build DefinitionId = attribute.AttributeClass.Name }); } - builder.Append("\")", CodeFileTokenKind.Text); builder.NewLine(); } @@ -616,9 +617,28 @@ private void DisplayName(CodeFileTokensBuilder builder, ISymbol symbol, ISymbol builder.Keyword(SyntaxFacts.GetText(ToEffectiveAccessibility(symbol.DeclaredAccessibility))); builder.Space(); } - foreach (var symbolDisplayPart in symbol.ToDisplayParts(_defaultDisplayFormat)) + if (symbol is IPropertySymbol propSymbol) + { + var parts = propSymbol.ToDisplayParts(_defaultDisplayFormat); + for (int i = 0; i < parts.Length; i++) + { + // Skip internal setters + if (parts[i].Kind == SymbolDisplayPartKind.Keyword && parts[i].ToString() == "internal") + { + while (i < parts.Length && parts[i].ToString() != "}") + { + i++; + } + } + builder.Append(MapToken(definedSymbol, parts[i])); + } + } + else { - builder.Append(MapToken(definedSymbol, symbolDisplayPart)); + foreach (var symbolDisplayPart in symbol.ToDisplayParts(_defaultDisplayFormat)) + { + builder.Append(MapToken(definedSymbol, symbolDisplayPart)); + } } } diff --git a/src/dotnet/APIView/APIView/Languages/CompilationFactory.cs b/src/dotnet/APIView/APIView/Languages/CompilationFactory.cs index 6b009f4afc4..8ae475a7d50 100644 --- a/src/dotnet/APIView/APIView/Languages/CompilationFactory.cs +++ b/src/dotnet/APIView/APIView/Languages/CompilationFactory.cs @@ -44,7 +44,7 @@ public static IAssemblySymbol GetCompilation(Stream stream, Stream documentation // MetadataReference.CreateFromStream closes the stream reference = MetadataReference.CreateFromStream(memoryStream, documentation: documentation); } - var compilation = CSharpCompilation.Create(null, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, metadataImportOptions: MetadataImportOptions.All)).AddReferences(reference); + var compilation = CSharpCompilation.Create(null, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, metadataImportOptions: MetadataImportOptions.Internal)).AddReferences(reference); var corlibLocation = typeof(object).Assembly.Location; var runtimeFolder = Path.GetDirectoryName(corlibLocation); diff --git a/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj b/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj index d72a84b2d3f..b2dd989f62c 100644 --- a/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj +++ b/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/dotnet/APIView/APIViewUnitTests/AssemblyReferences/InternalsVisibleTo.cs b/src/dotnet/APIView/APIViewUnitTests/AssemblyReferences/InternalsVisibleTo.cs deleted file mode 100644 index ce6b77bcc66..00000000000 --- a/src/dotnet/APIView/APIViewUnitTests/AssemblyReferences/InternalsVisibleTo.cs +++ /dev/null @@ -1,33 +0,0 @@ -/*-*/ -using System; -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("TestProject")] -class FriendAttribute : Attribute { - public FriendAttribute(string friendAssemblyName) { - FriendAssemblyName = friendAssemblyName; - } - - public string FriendAssemblyName { get; } -} -/*-*/ -namespace A { -public class PublicClass - { - public int PublicProperty { get; set; } - internal int InternalProperty { get; set; } - - [Friend("TestProject")] - internal int InternalPropertyWithFriendAttribute { get; set; } - - public void PublicMethod() - { } - - internal void InternalMethod() - { } - - [Friend("TestProject")] - internal void InternalMethodWithFriendAttribute() - { } - } -} diff --git a/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs b/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs index 8ca3773e3d2..1c5c474e874 100644 --- a/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs +++ b/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs @@ -22,26 +22,32 @@ public CodeFileBuilderTests(ITestOutputHelper testOutputHelper) private Regex _stripRegex = new Regex(@"/\*-\*/(.*?)/\*-\*/", RegexOptions.Singleline); - public static IEnumerable ExactFormattingFiles + public static IEnumerable FormattingFiles(string folder) { - get - { var assembly = typeof(CodeFileBuilderTests).Assembly; return assembly.GetManifestResourceNames() - .Where(r => r.Contains("ExactFormatting")) + .Where(r => r.Contains(folder)) .Select(r => new object[] { r }) .ToArray(); - } } [Theory] - [MemberData(nameof(ExactFormattingFiles))] + [MemberData(nameof(FormattingFiles), new object[] { "ExactFormatting" })] public async Task VerifyFormatted(string name) { ExtractCodeAndFormat(name, out string code, out string formatted); await AssertFormattingAsync(code, formatted); } + [Theory] + [MemberData(nameof(FormattingFiles), new object[] { "InternalsVisibleTo" })] + public async Task VerifyFormattedWithInternalVisibleTo(string name) + { + ExtractCodeAndFormat(name, out string code, out string formatted); + formatted = $"{Environment.NewLine}Exposes internals to:{Environment.NewLine}Azure.Some.Client{Environment.NewLine}{Environment.NewLine}" + formatted; + await AssertFormattingAsync(code, formatted); + } + private void ExtractCodeAndFormat(string name, out string code, out string formatted) { var manifestResourceStream = typeof(CodeFileBuilderTests).Assembly.GetManifestResourceStream(name); @@ -68,6 +74,12 @@ private async Task AssertFormattingAsync(string code, string formatted) var formattedModel = new CodeFileRenderer().Render(codeModel).CodeLines; var formattedString = string.Join(Environment.NewLine, formattedModel.Select(l => l.DisplayString)); _testOutputHelper.WriteLine(formattedString); + if(formatted != formattedString) + { + _testOutputHelper.WriteLine(String.Empty); + _testOutputHelper.WriteLine("Expected:"); + _testOutputHelper.WriteLine(formatted); + } Assert.Equal(formatted, formattedString); } diff --git a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Accessibility.cs b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Accessibility.cs index 97cfda1d12e..2658bf7e84d 100644 --- a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Accessibility.cs +++ b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Accessibility.cs @@ -1,4 +1,4 @@ -namespace A { +namespace A { public class Class { public Class(int a)/*-*/{/*-*/;/*-*/}/*-*/ protected Class()/*-*/{/*-*/;/*-*/}/*-*/ @@ -7,5 +7,6 @@ public class Class { protected/*-*/ internal/*-*/ int C { get; set; } /*-*/private protected int D { get; }/*-*/ /*-*/private int E { get; }/*-*/ + public int F { get;/*-*/internal set;/*-*/ } } } diff --git a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Class.cs b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Class.cs index 3041fd31110..f3b67ac6c45 100644 --- a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Class.cs +++ b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Class.cs @@ -10,4 +10,5 @@ public abstract class Class2 { } public static class Class3 { } + /*-*/internal class InternalClass { }/*-*/ } diff --git a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Delegate.cs b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Delegate.cs index 94b7fc1f6b2..dc1fad5dddc 100644 --- a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Delegate.cs +++ b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Delegate.cs @@ -1,3 +1,4 @@ namespace A { public delegate int A(int b, bool d = false); + /*-*/internal delegate int B(int b, bool d = false);/*-*/ } diff --git a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Enum.cs b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Enum.cs index f11e1753423..4807e091c8a 100644 --- a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Enum.cs +++ b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Enum.cs @@ -14,4 +14,7 @@ public enum NotFlags { C = 2, D = 7, } + /*-*/internal enum InternalEnum {/*-*/ + /*-*/A = 1/*-*/ + /*-*/}/*-*/ } diff --git a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Fields.cs b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Fields.cs index 91bbf8110f4..bf53c883e7f 100644 --- a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Fields.cs +++ b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Fields.cs @@ -5,5 +5,6 @@ public class Class { public const int I = 0; public static readonly int R; public string S; + /*-*/internal string T;/*-*/ } } diff --git a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/InternalsVisibleTo.cs b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/InternalsVisibleTo.cs deleted file mode 100644 index e71eb621047..00000000000 --- a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/InternalsVisibleTo.cs +++ /dev/null @@ -1,33 +0,0 @@ -/*-*/ -using System; -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Azure.Some.Client")] -class FriendAttribute : Attribute { - public FriendAttribute(string friendAssemblyName) { - FriendAssemblyName = friendAssemblyName; - } - - public string FriendAssemblyName { get; } -} -/*-*/ -namespace A { -public class PublicClass - { - public int PublicProperty { get; set; } - internal int InternalProperty { get; set; } - - [Friend("TestProject")] - internal int InternalPropertyWithFriendAttribute { get; set; } - - public void PublicMethod() - { } - - internal void InternalMethod() - { } - - [Friend("TestProject")] - internal void InternalMethodWithFriendAttribute() - { } - } -} diff --git a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Methods.cs b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Methods.cs index dfe462f5464..8c817f8ed5d 100644 --- a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Methods.cs +++ b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Methods.cs @@ -9,5 +9,6 @@ public class Class { public void M3(string s, int m = 3)/*-*/{/*-*/;/*-*/}/*-*/ public Class M3(string s, int m = 3, DateTime d = default)/*-*/{ return null/*-*/;/*-*/}/*-*/ public void M4()/*-*/{/*-*/;/*-*/}/*-*/ + /*-*/internal void P() { }/*-*/ } } diff --git a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Properties.cs b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Properties.cs index bf5f6976f45..7b99b0d1388 100644 --- a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Properties.cs +++ b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Properties.cs @@ -5,5 +5,6 @@ public class Class { public static int B { get; set; } public int C { get; } public int D { get; set; } + /*-*/internal int E { get; set; }/*-*/ } } diff --git a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Struct.cs b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Struct.cs index 69130efcd2f..7a7aaff4e7c 100644 --- a/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Struct.cs +++ b/src/dotnet/APIView/APIViewUnitTests/ExactFormatting/Struct.cs @@ -8,4 +8,5 @@ public readonly struct S1 { public S1(int a)/*-*/{ A = a/*-*/;/*-*/}/*-*/ public int? A { get; } } -} \ No newline at end of file + /*-*/internal struct S2 { }/*-*/ +} From 8c48ce1b8c9c38fb4dabe4972ea679b31dbc4c9b Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 11 Oct 2023 17:13:04 -0500 Subject: [PATCH 3/7] hide FriendAttribute itself --- src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs | 6 +++--- src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs b/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs index 7f8adf7cc1b..a92b3f534b9 100644 --- a/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs +++ b/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs @@ -459,7 +459,7 @@ private void BuildAttributes(CodeFileTokensBuilder builder, ImmutableArray a.AttributeClass.Name == "FriendAttribute") || - (s is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.BaseType?.Name == "Attribute" && namedTypeSymbol.Name == "FriendAttribute"); + return s.GetAttributes().Any(a => a.AttributeClass.Name == "FriendAttribute"); + //|| (s is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.BaseType?.Name == "Attribute" && namedTypeSymbol.Name == "FriendAttribute"); default: return IsAccessibleExplicitInterfaceImplementation(s); } diff --git a/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs b/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs index 1c5c474e874..f3fdf85b938 100644 --- a/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs +++ b/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs @@ -44,7 +44,7 @@ public async Task VerifyFormatted(string name) public async Task VerifyFormattedWithInternalVisibleTo(string name) { ExtractCodeAndFormat(name, out string code, out string formatted); - formatted = $"{Environment.NewLine}Exposes internals to:{Environment.NewLine}Azure.Some.Client{Environment.NewLine}{Environment.NewLine}" + formatted; + formatted = $"Exposes internals to:{Environment.NewLine}Azure.Some.Client{Environment.NewLine}{Environment.NewLine}" + formatted; await AssertFormattingAsync(code, formatted); } From 7d4f61264a1c64d567b9ed0f3b73431d3163a7b4 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Wed, 11 Oct 2023 18:03:21 -0500 Subject: [PATCH 4/7] . --- .../InternalsVisibleTo/InternalsVisibleTo.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/InternalsVisibleTo.cs diff --git a/src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/InternalsVisibleTo.cs b/src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/InternalsVisibleTo.cs new file mode 100644 index 00000000000..c48d9361e26 --- /dev/null +++ b/src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/InternalsVisibleTo.cs @@ -0,0 +1,26 @@ +/*-*/ +using System; +using System.Runtime.CompilerServices; +using B; + +[assembly: InternalsVisibleTo("Azure.Some.Client")] +[assembly: InternalsVisibleTo("Azure.Some.Client.Tests")] +namespace B { + internal class FriendAttribute : Attribute { + public FriendAttribute(string friendAssemblyName) { } + } +} +/*-*/ +namespace A { + public class PublicClass { + public PublicClass()/*-*/{/*-*/;/*-*/}/*-*/ + /*-*/internal int InternalProperty { get; set; }/*-*/ + [Friend("TestProject")] + internal void InternalMethodWithFriendAttribute()/*-*/{/*-*/;/*-*/}/*-*/ + [Friend("TestProject")] + internal int InternalPropertyWithFriendAttribute { get; set; } + /*-*/internal void InternalMethod(){ }/*-*/ + public void PublicMethod()/*-*/{/*-*/;/*-*/}/*-*/ + public int PublicProperty { get; set; } + } +} From bd99ce765d8a29c80822123e53584afdaa4e0130 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Thu, 12 Oct 2023 12:25:53 -0500 Subject: [PATCH 5/7] cleanup and more tests --- .../APIView/Languages/CodeFileBuilder.cs | 5 +- src/dotnet/APIView/APIViewUnitTests/Common.cs | 14 +---- .../APIViewUnitTests/DiagnosticProject.cs | 16 +---- .../InternalsVisibleTo/Inheritance.cs | 58 +++++++++++++++++++ .../{InternalsVisibleTo.cs => Properties.cs} | 1 + 5 files changed, 64 insertions(+), 30 deletions(-) create mode 100644 src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/Inheritance.cs rename src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/{InternalsVisibleTo.cs => Properties.cs} (93%) diff --git a/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs b/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs index a92b3f534b9..d3f8be175b2 100644 --- a/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs +++ b/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using APIView; @@ -124,6 +124,7 @@ public static void BuildInternalsVisibleToAttributes(CodeFileTokensBuilder build .Where(a => a.AttributeClass.Name == "InternalsVisibleToAttribute" && !a.ConstructorArguments[0].Value.ToString().Contains(".Tests") && + !a.ConstructorArguments[0].Value.ToString().Contains(".Perf") && !a.ConstructorArguments[0].Value.ToString().Contains("DynamicProxyGenAssembly2")); if (assemblyAttributes != null && assemblyAttributes.Any()) { @@ -144,7 +145,6 @@ public static void BuildInternalsVisibleToAttributes(CodeFileTokensBuilder build } builder.NewLine(); } - builder.NewLine(); } } @@ -746,7 +746,6 @@ private bool IsAccessible(ISymbol s) return true; case Accessibility.Internal: return s.GetAttributes().Any(a => a.AttributeClass.Name == "FriendAttribute"); - //|| (s is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.BaseType?.Name == "Attribute" && namedTypeSymbol.Name == "FriendAttribute"); default: return IsAccessibleExplicitInterfaceImplementation(s); } diff --git a/src/dotnet/APIView/APIViewUnitTests/Common.cs b/src/dotnet/APIView/APIViewUnitTests/Common.cs index c6b30188d86..bbf43b56775 100644 --- a/src/dotnet/APIView/APIViewUnitTests/Common.cs +++ b/src/dotnet/APIView/APIViewUnitTests/Common.cs @@ -12,24 +12,12 @@ internal static class Common public static async Task BuildDllAsync(Stream stream, string code) { var project = DiagnosticProject.Create(typeof(CodeFileBuilderTests).Assembly, LanguageVersion.Latest, new[] { code }) - .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, metadataImportOptions: MetadataImportOptions.All )); + .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, metadataImportOptions: MetadataImportOptions.Internal )); var compilation = await project.GetCompilationAsync(); Assert.Empty(compilation.GetDiagnostics().Where(d => d.Severity > DiagnosticSeverity.Warning)); compilation.Emit(stream); } - - public static async Task BuildDllWithProjectDepAsync(Stream stream, string code, string dependentCode) - { - var project = DiagnosticProject.Create(typeof(CodeFileBuilderTests).Assembly, LanguageVersion.Latest, new[] { code }, new[] { dependentCode }) - .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - - var compilation = await project.GetCompilationAsync(); - Assert.Empty(compilation.GetDiagnostics().Where(d => d.Severity > DiagnosticSeverity.Warning)); - - compilation.Emit(stream); - } - } } diff --git a/src/dotnet/APIView/APIViewUnitTests/DiagnosticProject.cs b/src/dotnet/APIView/APIViewUnitTests/DiagnosticProject.cs index 66f184c0043..4e3e3638b14 100644 --- a/src/dotnet/APIView/APIViewUnitTests/DiagnosticProject.cs +++ b/src/dotnet/APIView/APIViewUnitTests/DiagnosticProject.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using Microsoft.CodeAnalysis; @@ -24,11 +24,10 @@ public class DiagnosticProject /// Project name. /// public static string TestProjectName = "TestProject"; - public static string TestDependencyProjectName = "DependencyTestProject"; private static readonly Dictionary _solutionCache = new Dictionary(); - public static Project Create(Assembly testAssembly, LanguageVersion languageVersion, string[] sources, string[] dependencySources = null) + public static Project Create(Assembly testAssembly, LanguageVersion languageVersion, string[] sources) { Solution solution; lock (_solutionCache) @@ -41,16 +40,6 @@ public static Project Create(Assembly testAssembly, LanguageVersion languageVers .AddProject(projectId, TestProjectName, TestProjectName, LanguageNames.CSharp) .WithProjectParseOptions(projectId, new CSharpParseOptions(languageVersion)); - if(dependencySources?.Length > 0) - { - var dependencyProjectId = ProjectId.CreateNewId(debugName: TestDependencyProjectName); - solution = solution.AddProject(dependencyProjectId, TestDependencyProjectName, TestDependencyProjectName, LanguageNames.CSharp) - .WithProjectParseOptions(dependencyProjectId, new CSharpParseOptions(languageVersion)); - - // Add the dependency project as a reference to the test project - solution = solution.AddProjectReference(projectId, new ProjectReference(dependencyProjectId)); - } - foreach (var defaultCompileLibrary in DependencyContext.Load(testAssembly).CompileLibraries) { foreach (var resolveReferencePath in defaultCompileLibrary.ResolveReferencePaths(new AppLocalResolver())) @@ -62,7 +51,6 @@ public static Project Create(Assembly testAssembly, LanguageVersion languageVers } } - solution.Projects.Single(p => p.Name == TestProjectName); var testProject = solution.ProjectIds.Single(); var fileNamePrefix = DefaultFilePathPrefix; diff --git a/src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/Inheritance.cs b/src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/Inheritance.cs new file mode 100644 index 00000000000..442428baf67 --- /dev/null +++ b/src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/Inheritance.cs @@ -0,0 +1,58 @@ +/*-*/using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using C; + +[assembly: InternalsVisibleTo("Azure.Some.Client")] +[assembly: InternalsVisibleTo("Azure.Some.Client.Tests")] +[assembly: InternalsVisibleTo("Azure.Some.Client.Perf")] +namespace C { + internal class FriendAttribute : Attribute { + public FriendAttribute(string friendAssemblyName) { } + } +} +internal interface IInternal +{ + void M(); + void N(); +}/*-*/ +[Friend("TestProject")] +internal interface IInternalWithFriend { + void M(); + void N(); +} +namespace A { + public interface I1 { + } + public interface I2 { + } + public abstract class K : I1 { + protected K()/*-*/{/*-*/;/*-*/}/*-*/ + public abstract void M(); + } + public abstract class L : IDisposable, IAsyncDisposable { + protected L()/*-*/{/*-*/;/*-*/}/*-*/ + public abstract void Dispose(); + public abstract ValueTask DisposeAsync(); + } + [Friend("TestProject")] + internal abstract class M : IDisposable { + protected M()/*-*/{/*-*/;/*-*/}/*-*/ + void IDisposable.Dispose()/*-*/{/*-*/;/*-*/}/*-*/ + } + public class NClass : K, I1, I2 { + public NClass()/*-*/{/*-*/;/*-*/}/*-*/ + public override sealed void M()/*-*/{/*-*/;/*-*/}/*-*/ + } + public class OClass/*-*/ : IInternal/*-*/ { + public OClass()/*-*/{/*-*/;/*-*/}/*-*/ + public void M()/*-*/{/*-*/;/*-*/}/*-*//*-*/ + void IInternal.N(){} + /*-*/ + } + public class PClass : IInternalWithFriend { + public PClass()/*-*/{/*-*/;/*-*/}/*-*/ + void IInternalWithFriend.N()/*-*/{/*-*/;/*-*/}/*-*/ + public void M()/*-*/{/*-*/;/*-*/}/*-*/ + } +} diff --git a/src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/InternalsVisibleTo.cs b/src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/Properties.cs similarity index 93% rename from src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/InternalsVisibleTo.cs rename to src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/Properties.cs index c48d9361e26..518e5badef9 100644 --- a/src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/InternalsVisibleTo.cs +++ b/src/dotnet/APIView/APIViewUnitTests/InternalsVisibleTo/Properties.cs @@ -5,6 +5,7 @@ [assembly: InternalsVisibleTo("Azure.Some.Client")] [assembly: InternalsVisibleTo("Azure.Some.Client.Tests")] +[assembly: InternalsVisibleTo("Azure.Some.Client.Perf")] namespace B { internal class FriendAttribute : Attribute { public FriendAttribute(string friendAssemblyName) { } From 793ff21a5e74b4a1edfcac61a08f6854a7683f3a Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Thu, 12 Oct 2023 12:43:11 -0500 Subject: [PATCH 6/7] fix --- src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs b/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs index d3f8be175b2..39b0df22f30 100644 --- a/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs +++ b/src/dotnet/APIView/APIView/Languages/CodeFileBuilder.cs @@ -617,7 +617,7 @@ private void DisplayName(CodeFileTokensBuilder builder, ISymbol symbol, ISymbol builder.Keyword(SyntaxFacts.GetText(ToEffectiveAccessibility(symbol.DeclaredAccessibility))); builder.Space(); } - if (symbol is IPropertySymbol propSymbol) + if (symbol is IPropertySymbol propSymbol && propSymbol.DeclaredAccessibility != Accessibility.Internal) { var parts = propSymbol.ToDisplayParts(_defaultDisplayFormat); for (int i = 0; i < parts.Length; i++) From f125910bc1b8b2d3f57abf1907a4e72bef1bb293 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Thu, 12 Oct 2023 13:00:52 -0500 Subject: [PATCH 7/7] revert newlines tests change --- src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs b/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs index f3fdf85b938..16635a22a3a 100644 --- a/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs +++ b/src/dotnet/APIView/APIViewUnitTests/CodeFileBuilderTests.cs @@ -86,7 +86,7 @@ private async Task AssertFormattingAsync(string code, string formatted) private string RemoveEmptyLines(string content) { var lines = content - .Split('\n') + .Split(Environment.NewLine) .Where(s => !string.IsNullOrWhiteSpace(s)) .ToArray();