提交 93209407 编写于 作者: S srivatsn

Adding an analyzer to the CodeAnalysis analyzers package that issues an error...

Adding an analyzer to the CodeAnalysis analyzers package that issues an error if someone tries to implement an interface marked with an attribute.

Adding a internal type (System.Runtime.CompilerServices.InternalImplementationOnlyAttribute) to CodeAnalysis.dll and applying it to ISymbol so that we can prevent folks from implementing ISymbol (in order for us to be able to make changes to it in the future). Chose the System namespace because the LDM wants this to be a compiler feature in the future. The analyzer just checks by name and so the type can be internal and if a different dll needs it, it can define the attribute by itself. (changeset 1394725)
上级 769e907a
......@@ -126,6 +126,7 @@
<Compile Include="Emit\EditAndContinue\EncHoistedLocalMetadata.cs" />
<Compile Include="Emit\EmitOptions.cs" />
<Compile Include="Emit\EditAndContinue\EncVariableSlotAllocator.cs" />
<Compile Include="InternalImplementationOnlyAttribute.cs" />
<Compile Include="InternalUtilities\AssemblyLocationLightUp.cs" />
<Compile Include="Emit\EditAndContinueMethodDebugInformation.cs" />
<Compile Include="InternalUtilities\DocumentationCommentId.cs" />
......
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace System.Runtime.CompilerServices
{
/// <summary>
/// This is a marker attribute that can be put on an interface to denote that only internal implementations
/// of that interface should exist.
/// </summary>
internal sealed class InternalImplementationOnlyAttribute : Attribute
{
}
}
......@@ -4,8 +4,8 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis
{
......@@ -13,6 +13,7 @@ namespace Microsoft.CodeAnalysis
/// Represents a symbol (namespace, class, method, parameter, etc.)
/// exposed by the compiler.
/// </summary>
[InternalImplementationOnly]
public interface ISymbol : IEquatable<ISymbol>
{
/// <summary>
......
......@@ -64,6 +64,9 @@
<Compile Include="Helpers\AttributeHelpers.cs" />
<Compile Include="Helpers\DocumentChangedAction.cs" />
<Compile Include="Helpers\ITypeSymbolExtensions.cs" />
<Compile Include="InternalImplementationOnlyAnalyzer.cs">
<ExcludeFromStyleCop>true</ExcludeFromStyleCop>
</Compile>
<Compile Include="MetaAnalyzers\DiagnosticAnalyzerFieldsAnalyzer.cs" />
<Compile Include="MetaAnalyzers\DiagnosticAnalyzerAttributeAnalyzer.cs" />
<Compile Include="MetaAnalyzers\DiagnosticAnalyzerCorrectnessAnalyzer.CompilationAnalyzer.cs" />
......
......@@ -142,6 +142,33 @@ internal class CodeAnalysisDiagnosticsResources {
}
}
/// <summary>
/// Looks up a localized string similar to The author of this interface did not intend to have third party implementations of this interface and reserves the right to change it. Implementing this interface could therefore result in a source or binary compatibility issue with a future version of this interface..
/// </summary>
internal static string InternalImplementationOnlyDescription {
get {
return ResourceManager.GetString("InternalImplementationOnlyDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type {0} cannot implement interface {1} because {1} is not available for public implementation..
/// </summary>
internal static string InternalImplementationOnlyMessage {
get {
return ResourceManager.GetString("InternalImplementationOnlyMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &quot;Only internal implementations of this interface are allowed.&quot;.
/// </summary>
internal static string InternalImplementationOnlyTitle {
get {
return ResourceManager.GetString("InternalImplementationOnlyTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to ReportDiagnostic should only be invoked with supported DiagnosticDescriptors that are returned from DiagnosticAnalyzer.SupportedDiagnostics property. Otherwise, the reported diagnostic will be filtered out by the analysis engine..
/// </summary>
......
......@@ -195,4 +195,13 @@
<data name="DoNotStorePerCompilationDataOntoFieldsDescription" xml:space="preserve">
<value>Instance of a diagnostic analyzer might outlive the lifetime of compilation. Hence, storing per-compilation data, such as symbols, into the fields of a diagnostic analyzer might cause stale compilations to stay alive and cause memory leaks. Instead, you should store this data on a separate type instantiatied in a compilation start action, registered using '{0}.{1}' API. An instance of this type will be created per-compilation and it won't outlive compilation's lifetime, hence avoiding memory leaks.</value>
</data>
<data name="InternalImplementationOnlyDescription" xml:space="preserve">
<value>The author of this interface did not intend to have third party implementations of this interface and reserves the right to change it. Implementing this interface could therefore result in a source or binary compatibility issue with a future version of this interface.</value>
</data>
<data name="InternalImplementationOnlyMessage" xml:space="preserve">
<value>Type {0} cannot implement interface {1} because {1} is not available for public implementation.</value>
</data>
<data name="InternalImplementationOnlyTitle" xml:space="preserve">
<value>"Only internal implementations of this interface are allowed."</value>
</data>
</root>
\ No newline at end of file
......@@ -5,5 +5,6 @@ internal static class DiagnosticCategory
public const string AnalyzerCorrectness = "AnalyzerCorrectness";
public const string AnalyzerLocalization = "AnalyzerLocalization";
public const string AnalyzerPerformance = "AnalyzerPerformance";
public const string Compatibility = "Compatibility";
}
}
......@@ -10,5 +10,6 @@ internal static class DiagnosticIds
public const string InvalidSyntaxKindTypeArgumentRuleId = "RS1006";
public const string UseLocalizableStringsInDescriptorRuleId = "RS1007";
public const string DoNotStorePerCompilationDataOntoFieldsRuleId = "RS1008";
public const string InternalImplementationOnlyRuleId = "RS1009";
}
}
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.CodeAnalysis.Analyzers
{
[DiagnosticAnalyzer]
public class InternalImplementationOnlyAnalyzer : DiagnosticAnalyzer
{
private const string InternalImplementationOnlyAttributeName = "InternalImplementationOnlyAttribute";
private const string InternalImplementationOnlyAttributeFullName = "System.Runtime.CompilerServices.InternalImplementationOnlyAttribute";
private readonly static LocalizableString localizableTitle = new LocalizableResourceString(nameof(CodeAnalysisDiagnosticsResources.InternalImplementationOnlyTitle), CodeAnalysisDiagnosticsResources.ResourceManager, typeof(CodeAnalysisDiagnosticsResources));
private readonly static LocalizableString localizableMessageFormat = new LocalizableResourceString(nameof(CodeAnalysisDiagnosticsResources.InternalImplementationOnlyMessage), CodeAnalysisDiagnosticsResources.ResourceManager, typeof(CodeAnalysisDiagnosticsResources));
private readonly static LocalizableString localizableDescription = new LocalizableResourceString(nameof(CodeAnalysisDiagnosticsResources.InternalImplementationOnlyDescription), CodeAnalysisDiagnosticsResources.ResourceManager, typeof(CodeAnalysisDiagnosticsResources));
public static DiagnosticDescriptor Rule = new DiagnosticDescriptor(
DiagnosticIds.InternalImplementationOnlyRuleId,
localizableTitle,
localizableMessageFormat,
DiagnosticCategory.Compatibility,
DiagnosticSeverity.Error,
true,
localizableDescription);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context) => context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
private static void AnalyzeSymbol(SymbolAnalysisContext context)
{
var namedTypeSymbol = (INamedTypeSymbol)context.Symbol;
// If any interface implemented by this type has the attribute and if the interface and this type are not
// in "internals visible" context, then issue an error.
foreach (var iface in namedTypeSymbol.AllInterfaces)
{
var attributes = AttributeHelpers.GetApplicableAttributes(iface);
// We are doing a string comparison of the name here because we don't care where the attribute comes from.
// CodeAnalysis.dll itself has this attribute and if the user assembly also had it, symbol equality will fail
// but we should still issue the error.
if (attributes.Any(a => a.AttributeClass.Name.Equals(InternalImplementationOnlyAttributeName)
&& a.AttributeClass.ToDisplayString().Equals(InternalImplementationOnlyAttributeFullName)))
{
if (!iface.ContainingAssembly.GivesAccessTo(namedTypeSymbol.ContainingAssembly))
{
context.ReportDiagnostic(Diagnostic.Create(Rule, namedTypeSymbol.Locations.First(), namedTypeSymbol.Name, iface.Name));
break;
}
}
}
}
}
}
......@@ -102,6 +102,7 @@
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Compile Include="InternalImplementationOnlyTests.cs" />
<Compile Include="MetaAnalyzers\DoNotStorePerCompilationDataOntoFieldsRuleTests.cs" />
<Compile Include="MetaAnalyzers\InvalidReportDiagnosticRuleTests.cs" />
<Compile Include="MetaAnalyzers\InvalidSyntaxKindTypeArgumentRuleTests.cs" />
......
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Analyzers;
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace Microsoft.CodeAnalysis.UnitTests.Analyzers
{
public class InternalImplementationOnlyTests : DiagnosticAnalyzerTestBase
{
private const string AttributeStringCSharp = @"
namespace System.Runtime.CompilerServices
{
internal class InternalImplementationOnlyAttribute : System.Attribute {}
}
";
[Fact]
public void CSharp_VerifySameAssembly()
{
var source = AttributeStringCSharp + @"
[System.Runtime.CompilerServices.InternalImplementationOnly]
public interface IFoo { }
class Foo : IFoo { }
";
// Verify no diagnostic since interface is in the same assembly.
VerifyCSharp(source, addLanguageSpecificCodeAnalysisReference: false);
}
[Fact]
public void CSharp_VerifyDifferentAssembly()
{
var source1 = AttributeStringCSharp + @"
[System.Runtime.CompilerServices.InternalImplementationOnly]
public interface IFoo { }
public interface IBar : IFoo { }
";
var source2 = @"
class Foo : IFoo { }
class Boo : IBar { }";
var expected = new[] { GetCSharpExpectedDiagnostic(2, 7, "Foo", "IFoo"), GetCSharpExpectedDiagnostic(4, 7, "Boo", "IFoo") };
// Verify errors since interface is not in a friend assembly.
VerifyCSharpAcrossTwoAssemblies(source1, source2, expected);
}
[Fact]
public void CSharp_VerifyDifferentFriendAssembly()
{
var source1 = @"
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""TestProject"")]
" + AttributeStringCSharp + @"
[System.Runtime.CompilerServices.InternalImplementationOnly]
public interface IFoo { }
public interface IBar : IFoo { }
";
var source2 = @"
class Foo : IFoo { }
class Boo : IBar { }";
// Verify no diagnostic since interface is in a friend assembly.
VerifyCSharpAcrossTwoAssemblies(source1, source2);
}
[Fact]
public void CSharp_VerifyISymbol()
{
var source = @"
class Foo : Microsoft.CodeAnalysis.ISymbol { }
class Bar : Microsoft.CodeAnalysis.IAssemblySymbol { }
";
var expected = new[] { GetCSharpExpectedDiagnostic(2, 7, "Foo", "ISymbol"), GetCSharpExpectedDiagnostic(3, 7, "Bar", "ISymbol") };
// Verify that ISymbol is not implementable.
VerifyCSharp(source, addLanguageSpecificCodeAnalysisReference: true, expected: expected);
}
private const string AttributeStringBasic = @"
Namespace System.Runtime.CompilerServices
Friend Class InternalImplementationOnlyAttribute
Inherits System.Attribute
End Class
End Namespace
";
[Fact]
public void Basic_VerifySameAssembly()
{
var source = AttributeStringBasic + @"
<System.Runtime.CompilerServices.InternalImplementationOnly>
Public Interface IFoo
End Interface
Class Foo
Implements IFoo
End Class
";
// Verify no diagnostic since interface is in the same assembly.
VerifyBasic(source, addLanguageSpecificCodeAnalysisReference: false);
}
[Fact]
public void Basic_VerifyDifferentAssembly()
{
var source1 = AttributeStringBasic + @"
<System.Runtime.CompilerServices.InternalImplementationOnly>
Public Interface IFoo
End Interface
Public Interface IBar
Inherits IFoo
End Interface
";
var source2 = @"
Class Foo
Implements IFoo
End Class
Class Bar
Implements IBar
End Class
";
var expected = new[] { GetBasicExpectedDiagnostic(2, 7, "Foo", "IFoo"), GetBasicExpectedDiagnostic(6, 7, "Bar", "IFoo") };
// Verify errors since interface is not in a friend assembly.
VerifyBasicAcrossTwoAssemblies(source1, source2, expected);
}
[Fact]
public void Basic_VerifyDifferentFriendAssembly()
{
var source1 = @"
<Assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""TestProject"")>
" + AttributeStringBasic + @"
<System.Runtime.CompilerServices.InternalImplementationOnly>
Public Interface IFoo
End Interface
Public Interface IBar
Inherits IFoo
End Interface
";
var source2 = @"
Class Foo
Implements IFoo
End Class
Class Bar
Implements IBar
End Class
";
// Verify no diagnostic since interface is in a friend assembly.
VerifyBasicAcrossTwoAssemblies(source1, source2);
}
[Fact]
public void Basic_VerifyISymbol()
{
var source = @"
Class Foo
Implements Microsoft.CodeAnalysis.ISymbol
End Class
Class Bar
Implements Microsoft.CodeAnalysis.IAssemblySymbol
End Class
";
var expected = new[] { GetBasicExpectedDiagnostic(2, 7, "Foo", "ISymbol"), GetBasicExpectedDiagnostic(5, 7, "Bar", "ISymbol") };
// Verify that ISymbol is not implementable.
VerifyBasic(source, addLanguageSpecificCodeAnalysisReference: true, expected: expected);
}
private void VerifyAcrossTwoAssemblies(string source1, string source2, string language, params DiagnosticResult[] expected)
{
Debug.Assert(language == LanguageNames.CSharp || language == LanguageNames.VisualBasic);
var project1 = CreateProject(new string[] { source1 }, language: language, addLanguageSpecificCodeAnalysisReference: false);
var project2 = CreateProject(new string[] { source2 }, language: language, addLanguageSpecificCodeAnalysisReference: false, addToSolution: project1.Solution)
.AddProjectReference(new ProjectReference(project1.Id));
var analyzer = language == LanguageNames.CSharp ? GetCSharpDiagnosticAnalyzer() : GetBasicDiagnosticAnalyzer();
GetSortedDiagnostics(analyzer, project2.Documents.ToArray()).Verify(analyzer, expected);
}
private void VerifyCSharpAcrossTwoAssemblies(string source1, string source2, params DiagnosticResult[] expected)
{
VerifyAcrossTwoAssemblies(source1, source2, LanguageNames.CSharp, expected);
}
private void VerifyBasicAcrossTwoAssemblies(string source1, string source2, params DiagnosticResult[] expected)
{
VerifyAcrossTwoAssemblies(source1, source2, LanguageNames.VisualBasic, expected);
}
protected override DiagnosticAnalyzer GetBasicDiagnosticAnalyzer()
{
return new InternalImplementationOnlyAnalyzer();
}
protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer()
{
return new InternalImplementationOnlyAnalyzer();
}
private static DiagnosticResult GetCSharpExpectedDiagnostic(int line, int column, string typeName, string interfaceName)
{
return GetExpectedDiagnostic(LanguageNames.CSharp, line, column, typeName, interfaceName);
}
private static DiagnosticResult GetBasicExpectedDiagnostic(int line, int column, string typeName, string interfaceName)
{
return GetExpectedDiagnostic(LanguageNames.VisualBasic, line, column, typeName, interfaceName);
}
private static DiagnosticResult GetExpectedDiagnostic(string language, int line, int column, string typeName, string interfaceName)
{
var fileName = language == LanguageNames.CSharp ? "Test0.cs" : "Test0.vb";
return new DiagnosticResult
{
Id = DiagnosticIds.InternalImplementationOnlyRuleId,
Message = string.Format(CodeAnalysisDiagnosticsResources.InternalImplementationOnlyMessage, typeName, interfaceName),
Severity = DiagnosticSeverity.Error,
Locations = new[]
{
new DiagnosticResultLocation(fileName, line, column)
}
};
}
}
}
......@@ -252,7 +252,7 @@ protected static Document CreateDocument(string source, string language = Langua
return CreateProject(new[] { source }, language, addLanguageSpecificCodeAnalysisReference).Documents.First();
}
protected static Project CreateProject(string[] sources, string language = LanguageNames.CSharp, bool addLanguageSpecificCodeAnalysisReference = true)
protected static Project CreateProject(string[] sources, string language = LanguageNames.CSharp, bool addLanguageSpecificCodeAnalysisReference = true, Solution addToSolution = null)
{
string fileNamePrefix = DefaultFilePathPrefix;
string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt;
......@@ -260,8 +260,7 @@ protected static Project CreateProject(string[] sources, string language = Langu
var projectId = ProjectId.CreateNewId(debugName: TestProjectName);
var solution = new CustomWorkspace()
.CurrentSolution
var solution = (addToSolution ?? new CustomWorkspace().CurrentSolution)
.AddProject(projectId, TestProjectName, TestProjectName, language)
.AddMetadataReference(projectId, CorlibReference)
.AddMetadataReference(projectId, SystemCoreReference)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册