From 12e41c497a4d285223369d4f60de1397879b00d5 Mon Sep 17 00:00:00 2001 From: Owen Smith Date: Wed, 19 Jun 2019 09:51:52 -0400 Subject: [PATCH] D2LXXXX: ban default constructor of certain structs This is a generic version of, and supercedes D2L0029. Includes codefixes to use the initializer for the likely-to-be-intended usecase. This may be a miss for some things, such as Guid.Empty vs Guid.NewGuid(). Could theoretically suggest both. --- .../ImmutableCollectionsAnalyzer.cs | 70 --------------- src/D2L.CodeStyle.Analyzers/Diagnostics.cs | 20 ++--- .../DefaultStructCreationAnalyzer.Codefix.cs | 59 ++++++++++++ .../DefaultStructCreationAnalyzer.cs | 89 +++++++++++++++++++ .../IDefaultStructCreationReplacer.cs | 20 +++++ .../NewGuidBackedIdTypeReplacer.cs | 51 +++++++++++ .../Replacements/NewGuidReplacer.cs | 51 +++++++++++ .../Replacements/NewImmutableArrayReplacer.cs | 48 ++++++++++ .../Specs/DefaultStructCreationAnalyzer.cs | 59 ++++++++++++ .../Specs/ImmutableCollectionsAnalyzer.cs | 44 --------- 10 files changed, 387 insertions(+), 124 deletions(-) delete mode 100644 src/D2L.CodeStyle.Analyzers/ApiUsage/System.Collections.Immutable/ImmutableCollectionsAnalyzer.cs create mode 100644 src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/DefaultStructCreationAnalyzer.Codefix.cs create mode 100644 src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/DefaultStructCreationAnalyzer.cs create mode 100644 src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/IDefaultStructCreationReplacer.cs create mode 100644 src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/NewGuidBackedIdTypeReplacer.cs create mode 100644 src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/NewGuidReplacer.cs create mode 100644 src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/NewImmutableArrayReplacer.cs create mode 100644 tests/D2L.CodeStyle.Analyzers.Test/Specs/DefaultStructCreationAnalyzer.cs delete mode 100644 tests/D2L.CodeStyle.Analyzers.Test/Specs/ImmutableCollectionsAnalyzer.cs diff --git a/src/D2L.CodeStyle.Analyzers/ApiUsage/System.Collections.Immutable/ImmutableCollectionsAnalyzer.cs b/src/D2L.CodeStyle.Analyzers/ApiUsage/System.Collections.Immutable/ImmutableCollectionsAnalyzer.cs deleted file mode 100644 index 5d15c1588..000000000 --- a/src/D2L.CodeStyle.Analyzers/ApiUsage/System.Collections.Immutable/ImmutableCollectionsAnalyzer.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace D2L.CodeStyle.Analyzers.ApiUsage.SystemCollectionsImmutable { - [DiagnosticAnalyzer( LanguageNames.CSharp )] - public sealed class ImmutableCollectionsAnalyzer : DiagnosticAnalyzer { - public override ImmutableArray SupportedDiagnostics { get; } - = ImmutableArray.Create( - Diagnostics.DontUseImmutableArrayConstructor - ); - - public override void Initialize( AnalysisContext context ) { - context.EnableConcurrentExecution(); - context.RegisterCompilationStartAction( RegisterAnalysis ); - - } - public static void RegisterAnalysis( - CompilationStartAnalysisContext context - ) { - var immutableArrayType = context.Compilation - .GetTypeByMetadataName( "System.Collections.Immutable.ImmutableArray`1" ); - - // Bail early if we don't have a reference to ImmutableArray - if( immutableArrayType == null ) { - return; - } - - context.RegisterSyntaxNodeAction( - ctx => AnalyzeNewImmutableArray( ctx, immutableArrayType ), - SyntaxKind.ObjectCreationExpression - ); - } - - public static void AnalyzeNewImmutableArray( - SyntaxNodeAnalysisContext context, - INamedTypeSymbol immutableArrayType - ) { - var node = context.Node as ObjectCreationExpressionSyntax; - - // We're only concerned with the default (no arg) constructor for ImmutableArray - if( node.ArgumentList != null && node.ArgumentList.Arguments.Count != 0 ) { - return; - } - - var specificType = context.SemanticModel.GetTypeInfo( context.Node ).Type - as INamedTypeSymbol; - - // This happens for generic types - if ( specificType == null ) { - return; - } - - // We only care about ImmutableArray`1 - if( specificType.OriginalDefinition != immutableArrayType ) { - return; - } - - // All usages are bad - context.ReportDiagnostic( - Diagnostic.Create( - Diagnostics.DontUseImmutableArrayConstructor, - node.GetLocation() - ) - ); - } - } -} \ No newline at end of file diff --git a/src/D2L.CodeStyle.Analyzers/Diagnostics.cs b/src/D2L.CodeStyle.Analyzers/Diagnostics.cs index f90c8cfc0..0373fcebd 100644 --- a/src/D2L.CodeStyle.Analyzers/Diagnostics.cs +++ b/src/D2L.CodeStyle.Analyzers/Diagnostics.cs @@ -225,16 +225,6 @@ public static class Diagnostics { description: "ImmutableGeneric can only be applied to closed (fully bound) generic types." ); - public static readonly DiagnosticDescriptor DontUseImmutableArrayConstructor = new DiagnosticDescriptor( - id: "D2L0029", - title: "Don't use the default constructor for ImmutableArray", - messageFormat: "The default constructor for ImmutableArray doesn't correctly initialize the object and leads to runtime errors. Use ImmutableArray.Empty for empty arrays, ImmutableArray.Create() for simple cases and ImmutableArray.Builder for more complicated cases.", - category: "Correctness", - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: "The default constructor for ImmutableArray doesn't correctly initialize the object and leads to runtime errors. Use ImmutableArray.Empty for empty arrays, ImmutableArray.Create() for simple cases and ImmutableArray.Builder for more complicated cases." - ); - public static readonly DiagnosticDescriptor UnnecessaryMutabilityAnnotation = new DiagnosticDescriptor( id: "D2L0030", title: "Unnecessary mutability annotation should be removed to keep the code base clean", @@ -505,5 +495,15 @@ public static class Diagnostics { isEnabledByDefault: true ); + public static readonly DiagnosticDescriptor DontCallDefaultStructConstructor = new DiagnosticDescriptor( + id: "D2LXXXX", + title: "Don't call default struct constructor", + messageFormat: "Don't call default constructor of '{0}'", + description: "The default constructor of this type is likely to lead to unexpected results and another initializer should be called instead.", + category: "Correctness", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true + ); + } } diff --git a/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/DefaultStructCreationAnalyzer.Codefix.cs b/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/DefaultStructCreationAnalyzer.Codefix.cs new file mode 100644 index 000000000..7fbc1bdc6 --- /dev/null +++ b/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/DefaultStructCreationAnalyzer.Codefix.cs @@ -0,0 +1,59 @@ +using System.Collections.Immutable; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace D2L.CodeStyle.Analyzers.Language.DefaultStructCreation { + partial class DefaultStructCreationAnalyzer { + + [ExportCodeFixProvider( + LanguageNames.CSharp, + Name = nameof( UseSuggestedInitializerCodefix ) + )] + public sealed class UseSuggestedInitializerCodefix : CodeFixProvider { + + public override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create( Diagnostics.DontCallDefaultStructConstructor.Id ); + + public override FixAllProvider GetFixAllProvider() + => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync( + CodeFixContext ctx + ) { + var root = await ctx.Document + .GetSyntaxRootAsync( ctx.CancellationToken ) + .ConfigureAwait( false ); + + foreach( var diagnostic in ctx.Diagnostics ) { + var span = diagnostic.Location.SourceSpan; + + SyntaxNode syntax = root.FindNode( span, getInnermostNodeForTie: true ); + if( !( syntax is ObjectCreationExpressionSyntax creationExpression ) ) { + continue; + } + + ctx.RegisterCodeFix( + CodeAction.Create( + title: diagnostic.Properties[ACTION_TITLE_KEY], + createChangedDocument: ct => { + SyntaxNode replacement = SyntaxFactory.ParseExpression( + diagnostic.Properties[REPLACEMENT_KEY] + ); + + SyntaxNode newRoot = root.ReplaceNode( creationExpression, replacement ); + Document newDoc = ctx.Document.WithSyntaxRoot( newRoot ); + + return Task.FromResult( newDoc ); + } + ), + diagnostic + ); + } + } + } + } +} \ No newline at end of file diff --git a/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/DefaultStructCreationAnalyzer.cs b/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/DefaultStructCreationAnalyzer.cs new file mode 100644 index 000000000..2b05a9ecc --- /dev/null +++ b/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/DefaultStructCreationAnalyzer.cs @@ -0,0 +1,89 @@ +using System.Collections.Immutable; +using D2L.CodeStyle.Analyzers.Language.DefaultStructCreation.Replacements; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace D2L.CodeStyle.Analyzers.Language.DefaultStructCreation { + + [DiagnosticAnalyzer( LanguageNames.CSharp )] + internal sealed partial class DefaultStructCreationAnalyzer : DiagnosticAnalyzer { + + internal const string ACTION_TITLE_KEY = nameof( DefaultStructCreationAnalyzer ) + ".ActionTitle"; + internal const string REPLACEMENT_KEY = nameof( DefaultStructCreationAnalyzer ) + ".Replacement"; + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create( Diagnostics.DontCallDefaultStructConstructor ); + + public override void Initialize( AnalysisContext context ) { + context.EnableConcurrentExecution(); + + context.ConfigureGeneratedCodeAnalysis( + GeneratedCodeAnalysisFlags.None + ); + + context.RegisterCompilationStartAction( RegisterAnalyzer ); + } + + private static void RegisterAnalyzer( + CompilationStartAnalysisContext context + ) { + var replacers = ImmutableArray.Create( + NewGuidBackedIdTypeReplacer.Instance, + new NewGuidReplacer( context.Compilation ), + new NewImmutableArrayReplacer( context.Compilation ) + ); + + context.RegisterOperationAction( + ctx => AnalyzeObjectCreation( + context: ctx, + operation: ctx.Operation as IObjectCreationOperation, + replacers: replacers + ), + OperationKind.ObjectCreation + ); + } + + private static void AnalyzeObjectCreation( + OperationAnalysisContext context, + IObjectCreationOperation operation, + ImmutableArray replacers + ) { + if( operation.Type.TypeKind != TypeKind.Struct ) { + return; + } + + IMethodSymbol constructor = operation.Constructor; + if( constructor.Parameters.Length > 0 ) { + return; + } + + INamedTypeSymbol structType = operation.Type as INamedTypeSymbol; + + foreach( var replacer in replacers ) { + + if( !replacer.CanReplace( structType ) ) { + continue; + } + + var syntax = operation.Syntax as ObjectCreationExpressionSyntax; + SyntaxNode replacement = replacer + .GetReplacement( structType, syntax.Type ) + .WithTriviaFrom( syntax ); + + context.ReportDiagnostic( Diagnostic.Create( + descriptor: Diagnostics.DontCallDefaultStructConstructor, + location: operation.Syntax.GetLocation(), + properties: ImmutableDictionary + .Empty + .Add( ACTION_TITLE_KEY, replacer.Title ) + .Add( REPLACEMENT_KEY, replacement.ToFullString() ), + structType.ToDisplayString( SymbolDisplayFormat.MinimallyQualifiedFormat ) + ) ); + + break; + } + } + } +} \ No newline at end of file diff --git a/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/IDefaultStructCreationReplacer.cs b/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/IDefaultStructCreationReplacer.cs new file mode 100644 index 000000000..01438c02e --- /dev/null +++ b/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/IDefaultStructCreationReplacer.cs @@ -0,0 +1,20 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace D2L.CodeStyle.Analyzers.Language.DefaultStructCreation.Replacements { + + internal interface IDefaultStructCreationReplacer { + + string Title { get; } + + bool CanReplace( + INamedTypeSymbol structType + ); + + SyntaxNode GetReplacement( + INamedTypeSymbol structType, + TypeSyntax structName + ); + + } +} \ No newline at end of file diff --git a/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/NewGuidBackedIdTypeReplacer.cs b/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/NewGuidBackedIdTypeReplacer.cs new file mode 100644 index 000000000..917905371 --- /dev/null +++ b/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/NewGuidBackedIdTypeReplacer.cs @@ -0,0 +1,51 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace D2L.CodeStyle.Analyzers.Language.DefaultStructCreation.Replacements { + + internal sealed class NewGuidBackedIdTypeReplacer : IDefaultStructCreationReplacer { + + public static readonly IDefaultStructCreationReplacer Instance = new NewGuidBackedIdTypeReplacer(); + + private NewGuidBackedIdTypeReplacer() { } + + string IDefaultStructCreationReplacer.Title { get; } = "Use GenerateNew()"; + + bool IDefaultStructCreationReplacer.CanReplace( + INamedTypeSymbol structType + ) { + ImmutableArray maybeGenerateNew = structType.GetMembers( "GenerateNew" ); + if( maybeGenerateNew.Length != 1 ) { + return false; + } + + if( !( maybeGenerateNew[0] is IMethodSymbol generateNew ) ) { + return false; + } + + if( generateNew.Parameters.Length != 0 ) { + return false; + } + + // Sure seems like one of our Guid-backed IdTypes + return true; + } + + SyntaxNode IDefaultStructCreationReplacer.GetReplacement( + INamedTypeSymbol structType, + TypeSyntax structName + ) { + return SyntaxFactory + .InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + structName, + SyntaxFactory.IdentifierName( "GenerateNew" ) + ) + ); + } + + } +} \ No newline at end of file diff --git a/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/NewGuidReplacer.cs b/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/NewGuidReplacer.cs new file mode 100644 index 000000000..097d4147e --- /dev/null +++ b/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/NewGuidReplacer.cs @@ -0,0 +1,51 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace D2L.CodeStyle.Analyzers.Language.DefaultStructCreation.Replacements { + + internal sealed class NewGuidReplacer : IDefaultStructCreationReplacer { + string IDefaultStructCreationReplacer.Title { get; } = "Use Guid.NewGuid()"; + + private readonly INamedTypeSymbol m_guidType; + + public NewGuidReplacer( + Compilation compilation + ) { + m_guidType = compilation.GetTypeByMetadataName( "System.Guid" ); + } + + bool IDefaultStructCreationReplacer.CanReplace( + INamedTypeSymbol structType + ) { + if( m_guidType == null ) { + return false; + } + + if( m_guidType.TypeKind == TypeKind.Error ) { + return false; + } + + if( m_guidType != structType ) { + return false; + } + + return true; + } + + SyntaxNode IDefaultStructCreationReplacer.GetReplacement( + INamedTypeSymbol structType, + TypeSyntax structName + ) { + return SyntaxFactory + .InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + structName, + SyntaxFactory.IdentifierName( "NewGuid" ) + ) + ); + } + + } +} \ No newline at end of file diff --git a/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/NewImmutableArrayReplacer.cs b/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/NewImmutableArrayReplacer.cs new file mode 100644 index 000000000..ceccfd92e --- /dev/null +++ b/src/D2L.CodeStyle.Analyzers/Language/DefaultStructCreation/Replacements/NewImmutableArrayReplacer.cs @@ -0,0 +1,48 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace D2L.CodeStyle.Analyzers.Language.DefaultStructCreation.Replacements { + + internal sealed class NewImmutableArrayReplacer : IDefaultStructCreationReplacer { + string IDefaultStructCreationReplacer.Title { get; } = "Use ImmutableArray<>.Empty"; + + private readonly INamedTypeSymbol m_immutableArrayType; + + public NewImmutableArrayReplacer( + Compilation compilation + ) { + m_immutableArrayType = compilation + .GetTypeByMetadataName( "System.Collections.Immutable.ImmutableArray`1" ); + } + + bool IDefaultStructCreationReplacer.CanReplace( + INamedTypeSymbol structType + ) { + if( m_immutableArrayType == null ) { + return false; + } + + if( m_immutableArrayType.TypeKind == TypeKind.Error ) { + return false; + } + + if( m_immutableArrayType != structType.OriginalDefinition ) { + return false; + } + + return true; + } + + SyntaxNode IDefaultStructCreationReplacer.GetReplacement( + INamedTypeSymbol structType, + TypeSyntax structName + ) { + return SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + structName, + SyntaxFactory.IdentifierName( "Empty" ) + ); + } + } +} \ No newline at end of file diff --git a/tests/D2L.CodeStyle.Analyzers.Test/Specs/DefaultStructCreationAnalyzer.cs b/tests/D2L.CodeStyle.Analyzers.Test/Specs/DefaultStructCreationAnalyzer.cs new file mode 100644 index 000000000..5c1edbd28 --- /dev/null +++ b/tests/D2L.CodeStyle.Analyzers.Test/Specs/DefaultStructCreationAnalyzer.cs @@ -0,0 +1,59 @@ +// analyzer: D2L.CodeStyle.Analyzers.Language.DefaultStructCreation.DefaultStructCreationAnalyzer + +namespace System.Collections.Immutable { + + public static class ImmutableArray { + public static ImmutableArray Create() { } + } + + public struct ImmutableArray { + public static readonly ImmutableArray Empty; + } + +} + +namespace SpecTests { + + using System; + using System.Collections.Immutable; + + public class Foo { + public void Bar() { + + // System.Guid + { var x = /* DontCallDefaultStructConstructor(Guid) */ new Guid() /**/; } + { var x = /* DontCallDefaultStructConstructor(Guid) */ new System.Guid() /**/; } + { var x = new Guid( "efa31618-dced-4f3d-904f-e7424e4058fb" ); } + { var x = Guid.Empty; } + { var x = Guid.NewGuid(); } + + // System.Collections.Immmutable.ImmutableArray`1 + { var x = /* DontCallDefaultStructConstructor(ImmutableArray) */ new ImmutableArray() /**/; } + { var x = /* DontCallDefaultStructConstructor(ImmutableArray) */ new System.Collections.Immutable.ImmutableArray() /**/; } + { var x = ImmutableArray.Empty; } + { var x = ImmutableArray.Create(); } + { + // constructor doesn't exist + var x = new ImmutableArray( 1 ); + } + + // D2L Guid-Backed Id Types + { var x = /* DontCallDefaultStructConstructor(GuidBackedIdType) */ new GuidBackedIdType() /**/; } + { var x = /* DontCallDefaultStructConstructor(GuidBackedIdType) */ new SpecTests.GuidBackedIdType() /**/; } + { var x = GuidBackedIdType.GenerateNew(); } + + // Things we don't know about + { var x = new SomeOtherStruct(); } + + } + } + + public readonly partial struct GuidBackedIdType { + + public static GuidBackedIdType GenerateNew() {} + + } + + public readonly struct SomeOtherStruct {} + +} diff --git a/tests/D2L.CodeStyle.Analyzers.Test/Specs/ImmutableCollectionsAnalyzer.cs b/tests/D2L.CodeStyle.Analyzers.Test/Specs/ImmutableCollectionsAnalyzer.cs deleted file mode 100644 index 173f75616..000000000 --- a/tests/D2L.CodeStyle.Analyzers.Test/Specs/ImmutableCollectionsAnalyzer.cs +++ /dev/null @@ -1,44 +0,0 @@ -// analyzer: D2L.CodeStyle.Analyzers.ApiUsage.SystemCollectionsImmutable.ImmutableCollectionsAnalyzer - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace D2L.CodeStyle.Analyzers.Specs { - class ImmutableCollectionsAnalyzer { - public object m_good = new object(); - public int[] m_good2 = new int[] { 1, 2, 3 }; - public IEnumerable m_good3 = new List { }; - public IEnumerable m_good4 = ImmutableArray.Create( 1, 2, 3 ); - public ImmutableArray m_good5 = ImmutableArray.Empty; - public int[] m_good6 = new int[0](); // FYI: not an ObjectionCreationSyntax - there is a distinct ArrayCreationSyntax - - public ImmutableArray m_neutral = new ImmutableArray( 1 ); // we don't need to report for this, there is no such constructor. - public ImmutableArray m_neutral2 = new ImmutableArray( new int[] { 1, 2, 3 } ); // this constructor is internal - - public IEnumerable m_bad = /* DontUseImmutableArrayConstructor */ new ImmutableArray { 1, 2, 3 } /**/; - public ImmutableArray m_bad2 = /* DontUseImmutableArrayConstructor */ new ImmutableArray { 1, 2, 3 } /**/; - public ImmutableArray m_bad2 = /* DontUseImmutableArrayConstructor */ new ImmutableArray {} /**/; - public ImmutableArray m_bad2 = /* DontUseImmutableArrayConstructor */ new ImmutableArray() /**/; - - // not handled, but not likely? - public ImmutableArray m_notCurrentlyHandled = default( ImmutableArray ); - - public static void SomeMethod() { - var bad = /* DontUseImmutableArrayConstructor */ new ImmutableArray() /**/; - var good = ImmutableArray.Create( 1, 2, 3 ); - var good2 = new object(); - } - - public static T GenericMethod() where T : new() { - return new T(); - } - - public void SomeOtherMethod() { - // We don't catch this. In general doing so would be too - // complicated for an analyzer: we'd need to look up the defn of - // GenericMethod, do data-flow analysis etc. - var whoops = GenericMethod>(); - } - } -}