/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Analyzers/Core/CodeFixes/ImplementAbstractClass/ImplementAbstractClassData.cs
325 строк
15 KB
David Wengier
Remove per-feature Razor code generation opt-ins
21 июл 2026, 07:49
21 июл 2026, 07:49
b80269d
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.ImplementInterface; using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ImplementAbstractClass; internal sealed class ImplementAbstractClassData( Document document, ImplementTypeOptions options, SyntaxNode classNode, SyntaxToken classIdentifier, INamedTypeSymbol classType, INamedTypeSymbol abstractClassType, ImmutableArray<(INamedTypeSymbol type, ImmutableArray<ISymbol> members)> unimplementedMembers) { private readonly Document _document = document; private readonly ImplementTypeOptions _options = options; private readonly SyntaxNode _classNode = classNode; private readonly SyntaxToken _classIdentifier = classIdentifier; private readonly ImmutableArray<(INamedTypeSymbol type, ImmutableArray<ISymbol> members)> _unimplementedMembers = unimplementedMembers; public readonly INamedTypeSymbol ClassType = classType; public readonly INamedTypeSymbol AbstractClassType = abstractClassType; public static async Task<ImplementAbstractClassData?> TryGetDataAsync( Document document, SyntaxNode classNode, SyntaxToken classIdentifier, CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (semanticModel.GetDeclaredSymbol(classNode, cancellationToken) is not INamedTypeSymbol classType) return null; if (classType.IsAbstract) return null; var abstractClassType = classType.BaseType; if (abstractClassType == null || !abstractClassType.IsAbstractClass()) return null; var codeGenerationContext = new CodeGenerationContext( contextLocation: classIdentifier.GetLocation()); if (!CodeGenerator.CanAdd(document.Project.Solution, classType, codeGenerationContext, cancellationToken)) return null; var unimplementedMembers = classType.GetAllUnimplementedMembers( [abstractClassType], includeMembersRequiringExplicitImplementation: false, cancellationToken); if (unimplementedMembers.IsEmpty) return null; var options = await document.GetImplementTypeOptionsAsync(cancellationToken).ConfigureAwait(false); return new ImplementAbstractClassData( document, options, classNode, classIdentifier, classType, abstractClassType, unimplementedMembers); } public static async Task<Document?> TryImplementAbstractClassAsync( Document document, SyntaxNode classNode, SyntaxToken classIdentifier, CancellationToken cancellationToken) { var data = await TryGetDataAsync(document, classNode, classIdentifier, cancellationToken).ConfigureAwait(false); if (data == null) return null; return await data.ImplementAbstractClassAsync(throughMember: null, canDelegateAllMembers: null, cancellationToken).ConfigureAwait(false); } public async Task<Document> ImplementAbstractClassAsync( ISymbol? throughMember, bool? canDelegateAllMembers, CancellationToken cancellationToken) { var compilation = await _document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var memberDefinitions = GenerateMembers(compilation, throughMember, _options.PropertyGenerationBehavior, cancellationToken); var groupMembers = _options.InsertionBehavior == ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind; // If we're implementing through one of our members, but we can't delegate all members // through it, then give an error message on the class decl letting the user know. var classNodeToAddMembersTo = _classNode; if (throughMember != null && canDelegateAllMembers == false) { classNodeToAddMembersTo = _classNode.ReplaceToken( _classIdentifier, _classIdentifier.WithAdditionalAnnotations(ConflictAnnotation.Create( AnalyzersResources.Base_classes_contain_inaccessible_unimplemented_members))); } var context = new CodeGenerationContext( contextLocation: classNodeToAddMembersTo.GetLocation(), autoInsertionLocation: groupMembers, sortMembers: groupMembers); var info = await _document.GetCodeGenerationInfoAsync(context, cancellationToken).ConfigureAwait(false); var updatedClassNode = info.Service.AddMembers( classNodeToAddMembersTo, memberDefinitions, info, cancellationToken); var root = await _document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var newRoot = root.ReplaceNode(_classNode, updatedClassNode); return _document.WithSyntaxRoot(newRoot); } private ImmutableArray<ISymbol> GenerateMembers( Compilation compilation, ISymbol? throughMember, ImplementTypePropertyGenerationBehavior propertyGenerationBehavior, CancellationToken cancellationToken) { return [.. _unimplementedMembers .SelectMany(t => t.members) .Select(m => GenerateMember(compilation, m, throughMember, propertyGenerationBehavior, cancellationToken)) .WhereNotNull()]; } private ISymbol? GenerateMember( Compilation compilation, ISymbol member, ISymbol? throughMember, ImplementTypePropertyGenerationBehavior propertyGenerationBehavior, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); // Check if we need to add 'unsafe' to the signature we're generating. var syntaxFacts = _document.GetRequiredLanguageService<ISyntaxFactsService>(); var addUnsafe = member.RequiresUnsafeModifier() && !syntaxFacts.IsUnsafeContext(_classNode); return GenerateMember(compilation, member, throughMember, addUnsafe, propertyGenerationBehavior); } private ISymbol? GenerateMember( Compilation compilation, ISymbol member, ISymbol? throughMember, bool addUnsafe, ImplementTypePropertyGenerationBehavior propertyGenerationBehavior) { var modifiers = DeclarationModifiers.Override.WithIsUnsafe(addUnsafe).WithIsRequired(member.IsRequired()); var accessibility = member.ComputeResultantAccessibility(ClassType); // only call through one of members for this symbol if we can actually access the symbol // from our type. if (throughMember != null && !member.IsAccessibleWithin(ClassType, throughMember.GetMemberType())) { return null; } return member switch { IMethodSymbol method => GenerateMethod(compilation, method, throughMember, modifiers, accessibility), IPropertySymbol property => GenerateProperty(compilation, property, throughMember, modifiers, accessibility, propertyGenerationBehavior), IEventSymbol @event => GenerateEvent(@event, throughMember, accessibility, modifiers), _ => null, }; } private ISymbol GenerateMethod( Compilation compilation, IMethodSymbol method, ISymbol? throughMember, DeclarationModifiers modifiers, Accessibility accessibility) { var syntaxFacts = _document.GetRequiredLanguageService<ISyntaxFactsService>(); var generator = SyntaxGenerator.GetGenerator(_document); var body = throughMember == null ? generator.CreateThrowNotImplementedStatement(compilation) : generator.GenerateDelegateThroughMemberStatement(method, throughMember); method = method.EnsureNonConflictingNames(ClassType, syntaxFacts); return CodeGenerationSymbolFactory.CreateMethodSymbol( method, accessibility: accessibility, modifiers: modifiers, statements: [body]); } private IPropertySymbol GenerateProperty( Compilation compilation, IPropertySymbol property, ISymbol? throughMember, DeclarationModifiers modifiers, Accessibility accessibility, ImplementTypePropertyGenerationBehavior propertyGenerationBehavior) { if (property.GetMethod == null) { // Can't generate an auto-prop for a setter-only property. propertyGenerationBehavior = ImplementTypePropertyGenerationBehavior.PreferThrowingProperties; } var generator = _document.GetRequiredLanguageService<Microsoft.CodeAnalysis.Editing.SyntaxGenerator>(); var preferAutoProperties = propertyGenerationBehavior == ImplementTypePropertyGenerationBehavior.PreferAutoProperties; var getMethod = ShouldGenerateAccessor(property.GetMethod) ? CodeGenerationSymbolFactory.CreateAccessorSymbol( property.GetMethod, attributes: default, accessibility: property.GetMethod.ComputeResultantAccessibility(ClassType), statements: generator.GetGetAccessorStatements( compilation, property, conflictingProperty: null, throughMember, preferAutoProperties)) : null; var setMethod = ShouldGenerateAccessor(property.SetMethod) ? CodeGenerationSymbolFactory.CreateAccessorSymbol( property.SetMethod, attributes: default, accessibility: property.SetMethod.ComputeResultantAccessibility(ClassType), statements: generator.GetSetAccessorStatements( compilation, property, conflictingProperty: null, throughMember, preferAutoProperties)) : null; return CodeGenerationSymbolFactory.CreatePropertySymbol( property, accessibility: accessibility, modifiers: modifiers, getMethod: getMethod, setMethod: setMethod); } private IEventSymbol GenerateEvent( IEventSymbol @event, ISymbol? throughMember, Accessibility accessibility, DeclarationModifiers modifiers) { var generator = _document.GetRequiredLanguageService<Microsoft.CodeAnalysis.Editing.SyntaxGenerator>(); return CodeGenerationSymbolFactory.CreateEventSymbol( @event, accessibility: accessibility, modifiers: modifiers, addMethod: GetEventAddOrRemoveMethod(@event, @event.AddMethod, throughMember, generator.AddEventHandler), removeMethod: GetEventAddOrRemoveMethod(@event, @event.RemoveMethod, throughMember, generator.RemoveEventHandler)); } private IMethodSymbol? GetEventAddOrRemoveMethod( IEventSymbol @event, IMethodSymbol? accessor, ISymbol? throughMember, Func<SyntaxNode, SyntaxNode, SyntaxNode> createAddOrRemoveHandler) { if (accessor == null || throughMember == null) return null; var generator = _document.GetRequiredLanguageService<Microsoft.CodeAnalysis.Editing.SyntaxGenerator>(); var throughExpression = generator.CreateDelegateThroughExpression(@event, throughMember); var statement = generator.ExpressionStatement(createAddOrRemoveHandler( generator.MemberAccessExpression(throughExpression, @event.Name), generator.IdentifierName("value"))); return CodeGenerationSymbolFactory.CreateAccessorSymbol( attributes: default, accessibility: Accessibility.NotApplicable, statements: [statement]); } private bool ShouldGenerateAccessor([NotNullWhen(true)] IMethodSymbol? method) => method != null && ClassType.FindImplementationForAbstractMember(method) == null; public ImmutableArray<(ISymbol symbol, bool canDelegateAllMembers)> GetDelegatableMembers(CancellationToken cancellationToken) { var members = ImplementHelpers.GetDelegatableMembers( _document, ClassType, t => InheritsFromOrEquals(t, AbstractClassType), cancellationToken); using var _ = ArrayBuilder<(ISymbol symbol, bool canDelegateAllMembers)>.GetInstance(out var result); var allUnimplementedMembers = _unimplementedMembers.SelectManyAsArray(t => t.members); // Have to make sure the field or prop has at least one unimplemented member exposed that we could actually // call from our type. For example, if we're calling through a type that isn't derived from us, then we // can't access protected members. foreach (var member in members) { var memberType = member.GetMemberType(); var accessibleCount = allUnimplementedMembers.Count(m => m.IsAccessibleWithin(ClassType, throughType: memberType)); if (accessibleCount > 0) { // there was at least one unimplemented member that we could implement here through one of our // members. Return this as a a delegatable member. Also indicate if we will be able to delegate // all unimplemented members through this. If not, we'll let the user know that they can delegate // through this but that it will not fully fix the error. result.Add((member, canDelegateAllMembers: accessibleCount == allUnimplementedMembers.Length)); } } return result.ToImmutableAndClear(); } private static bool InheritsFromOrEquals(ITypeSymbol type, ITypeSymbol baseType) => GetBaseTypesAndThis(type).Contains(t => SymbolEquivalenceComparer.Instance.Equals(t, baseType)); private static IEnumerable<ITypeSymbol> GetBaseTypesAndThis(ITypeSymbol? type) { var current = type; while (current != null) { yield return current; // Workaround until https://github.com/dotnet/roslyn/issues/41733 is fixed. Right // now the compiler gives no way to determine the effective base class of a type // parameter. So we do a poor mans version and attempt to figure it out from the // constraints ourselves. if (current.BaseType == null && current is ITypeParameterSymbol typeParameter) { var constraints = typeParameter.ConstraintTypes; current = constraints.OfType<INamedTypeSymbol>().FirstOrDefault() ?? constraints.FirstOrDefault(t => t.IsReferenceType); continue; } current = current.BaseType; } } }