| File: System\Windows\Forms\CSharp\Analyzers\Designer\DesignerFileStructureAnalyzer.cs | Web Access |
| Project: ..\..\..\src\winforms\src\System.Windows.Forms.Analyzers.CSharp\src\System.Windows.Forms.Analyzers.CSharp.csproj (System.Windows.Forms.Analyzers.CSharp) |
// 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.Windows.Forms.Analyzers; using System.Windows.Forms.Analyzers.Diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; namespace System.Windows.Forms.CSharp.Analyzers.Designer; /// <summary> /// Enforces the structural boundary between generated Designer code and user code. /// </summary> [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DesignerFileStructureAnalyzer : DiagnosticAnalyzer { /// <inheritdoc/> public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [ SharedDiagnosticDescriptors.s_unexpectedDesignerMember, SharedDiagnosticDescriptors.s_designerFieldPlacement, SharedDiagnosticDescriptors.s_designerEventOrDelegate, SharedDiagnosticDescriptors.s_designerCollectionExpression ]; /// <inheritdoc/> public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis( GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.RegisterCompilationStartAction( startContext => { DesignerTypeFacts facts = new(startContext.Compilation); startContext.RegisterSyntaxNodeAction( context => AnalyzeType(context, facts), SyntaxKind.ClassDeclaration); }); } private static void AnalyzeType(SyntaxNodeAnalysisContext context, DesignerTypeFacts facts) { var typeDeclaration = (ClassDeclarationSyntax)context.Node; if (!DesignerTypeFacts.IsDesignerFile(typeDeclaration.SyntaxTree) || context.SemanticModel.GetDeclaredSymbol( typeDeclaration, context.CancellationToken) is not INamedTypeSymbol type || !facts.IsDesignerDeclaration(type, typeDeclaration.SyntaxTree)) { return; } AnalyzeFieldPlacement(context, typeDeclaration, facts); AnalyzeCollectionExpressions(context, typeDeclaration); foreach (MemberDeclarationSyntax member in typeDeclaration.Members) { switch (member) { case FieldDeclarationSyntax: case ConstructorDeclarationSyntax: break; case MethodDeclarationSyntax method when context.SemanticModel.GetDeclaredSymbol( method, context.CancellationToken) is IMethodSymbol symbol && DesignerTypeFacts.IsAllowedMethod(symbol): if (DesignerTypeFacts.IsInitializeComponent(symbol) && context.SemanticModel.GetOperation(method, context.CancellationToken) is IOperation operation) { AnalyzeInitializers(context, symbol, operation, facts); } break; case EventFieldDeclarationSyntax eventField: foreach (VariableDeclaratorSyntax variable in eventField.Declaration.Variables) { ReportEventOrDelegate(context, variable.Identifier, "Event"); } break; case EventDeclarationSyntax eventDeclaration: ReportEventOrDelegate(context, eventDeclaration.Identifier, "Event"); break; case DelegateDeclarationSyntax delegateDeclaration: ReportEventOrDelegate(context, delegateDeclaration.Identifier, "Delegate"); break; default: context.ReportDiagnostic( Diagnostic.Create( SharedDiagnosticDescriptors.s_unexpectedDesignerMember, GetMemberLocation(member), GetMemberName(member))); break; } } } private static void AnalyzeInitializers( SyntaxNodeAnalysisContext context, IMethodSymbol method, IOperation body, DesignerTypeFacts facts) { HashSet<ISymbol> reportedMembers = new(SymbolEqualityComparer.Default); foreach (IOperation operation in body.DescendantsAndSelf()) { context.CancellationToken.ThrowIfCancellationRequested(); if (operation is not ISimpleAssignmentOperation assignment || facts.GetInitializedComponent(assignment, method.ContainingType) is not ISymbol member || member.DeclaringSyntaxReferences.Any( declaration => DesignerTypeFacts.IsDesignerFile(declaration.SyntaxTree)) || !reportedMembers.Add(member)) { continue; } Location? location = member.Locations.FirstOrDefault(location => location.IsInSource); if (location is not null) { context.ReportDiagnostic(Diagnostic.Create( SharedDiagnosticDescriptors.s_designerFieldPlacement, location, member.Name)); } } } private static void AnalyzeCollectionExpressions( SyntaxNodeAnalysisContext context, ClassDeclarationSyntax typeDeclaration) { foreach (CollectionExpressionSyntax expression in typeDeclaration .DescendantNodes(node => node == typeDeclaration || node is not TypeDeclarationSyntax) .OfType<CollectionExpressionSyntax>()) { context.ReportDiagnostic( Diagnostic.Create( SharedDiagnosticDescriptors.s_designerCollectionExpression, expression.OpenBracketToken.GetLocation())); } } private static void AnalyzeFieldPlacement( SyntaxNodeAnalysisContext context, ClassDeclarationSyntax typeDeclaration, DesignerTypeFacts facts) { int lastNonFieldIndex = -1; for (int i = 0; i < typeDeclaration.Members.Count; i++) { if (typeDeclaration.Members[i] is not FieldDeclarationSyntax) { lastNonFieldIndex = i; } } for (int i = 0; i < lastNonFieldIndex; i++) { if (typeDeclaration.Members[i] is not FieldDeclarationSyntax field) { continue; } foreach (VariableDeclaratorSyntax variable in field.Declaration.Variables) { if (context.SemanticModel.GetDeclaredSymbol(variable, context.CancellationToken) is not IFieldSymbol symbol || facts.IsComponentsMember(symbol)) { continue; } context.ReportDiagnostic( Diagnostic.Create( SharedDiagnosticDescriptors.s_designerFieldPlacement, variable.Identifier.GetLocation(), variable.Identifier.ValueText)); } } } private static string GetMemberName(MemberDeclarationSyntax member) => member switch { PropertyDeclarationSyntax property => property.Identifier.ValueText, IndexerDeclarationSyntax => "this", MethodDeclarationSyntax method => method.Identifier.ValueText, BaseTypeDeclarationSyntax type => type.Identifier.ValueText, _ => member.Kind().ToString() }; private static Location GetMemberLocation(MemberDeclarationSyntax member) => member switch { PropertyDeclarationSyntax property => property.Identifier.GetLocation(), MethodDeclarationSyntax method => method.Identifier.GetLocation(), BaseTypeDeclarationSyntax type => type.Identifier.GetLocation(), _ => member.GetLocation() }; private static void ReportEventOrDelegate( SyntaxNodeAnalysisContext context, SyntaxToken identifier, string kind) => context.ReportDiagnostic( Diagnostic.Create( SharedDiagnosticDescriptors.s_designerEventOrDelegate, identifier.GetLocation(), kind, identifier.ValueText)); }