/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Analyzers/Core/CodeFixes/ImplementInterface/ImplementHelpers.cs
260 строк
10 KB
Cyrus Najmabadi
Use WhereAsArray
05 авг 2025, 13:55
05 авг 2025, 13:55
ed8dfdd
Код
Авторство
О чём код?
// 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.Linq; using System.Threading; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ImplementInterface; internal static class ImplementHelpers { public static ImmutableArray<ISymbol> GetDelegatableMembers( Document document, INamedTypeSymbol namedType, Func<ITypeSymbol, bool> includeMemberType, CancellationToken cancellationToken) { var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>(); var fields = namedType.GetMembers() .OfType<IFieldSymbol>() .WhereAsArray(f => !f.IsImplicitlyDeclared && includeMemberType(f.Type)); var properties = namedType.GetMembers() .OfType<IPropertySymbol>() .WhereAsArray(p => !p.IsImplicitlyDeclared && p.Parameters.Length == 0 && p.GetMethod != null && includeMemberType(p.Type)); var parameters = GetNonCapturedPrimaryConstructorParameters(fields, properties); return [.. fields, .. properties, .. parameters]; ImmutableArray<IParameterSymbol> GetNonCapturedPrimaryConstructorParameters( ImmutableArray<IFieldSymbol> fields, ImmutableArray<IPropertySymbol> properties) { using var _2 = ArrayBuilder<IParameterSymbol>.GetInstance(out var result); var primaryConstructor = namedType.InstanceConstructors .FirstOrDefault(c => c.Parameters.Length > 0 && c.Parameters[0].IsPrimaryConstructor(cancellationToken)); if (primaryConstructor != null) { foreach (var parameter in primaryConstructor.Parameters) { if (includeMemberType(parameter.Type) && !IsAssignedToFieldOrProperty(fields, properties, parameter)) { result.Add(parameter); } } } return result.ToImmutableAndClear(); } bool IsAssignedToFieldOrProperty(ImmutableArray<IFieldSymbol> fields, ImmutableArray<IPropertySymbol> properties, IParameterSymbol parameter) => fields.Any(f => IsAssignedToField(f, parameter)) || properties.Any(p => IsAssignedToProperty(p, parameter)); bool IsAssignedToField(IFieldSymbol field, IParameterSymbol parameter) { if (field.DeclaringSyntaxReferences is [var syntaxRef, ..]) { var declarator = syntaxRef.GetSyntax(cancellationToken); if (syntaxFacts.IsVariableDeclarator(declarator)) { var initializer = syntaxFacts.GetInitializerOfVariableDeclarator(declarator); if (InitializerReferencesParameter(initializer, parameter)) return true; } } return false; } bool IsAssignedToProperty(IPropertySymbol property, IParameterSymbol parameter) { if (property.DeclaringSyntaxReferences is [var syntaxRef, ..]) { var declarator = syntaxRef.GetSyntax(cancellationToken); if (syntaxFacts.IsPropertyDeclaration(declarator)) { var initializer = syntaxFacts.GetInitializerOfPropertyDeclaration(declarator); if (InitializerReferencesParameter(initializer, parameter)) return true; } } return false; } bool InitializerReferencesParameter(SyntaxNode? initializer, IParameterSymbol parameter) { if (initializer != null) { var value = syntaxFacts.GetValueOfEqualsValueClause(initializer); value = syntaxFacts.WalkDownParentheses(value); if (syntaxFacts.IsIdentifierName(value)) { syntaxFacts.GetNameAndArityOfSimpleName(value, out var name, out _); if (name == parameter.Name) return true; } } return false; } } public static bool ContainsTypeLessAccessibleThan( ISymbol? first, INamedTypeSymbol second, bool supportsImplicitImplementationOfNonPublicInterfaceMembers) { if (first is null) return false; if (first.DeclaredAccessibility <= Accessibility.NotApplicable || second.DeclaredAccessibility <= Accessibility.NotApplicable) { return false; } if (!supportsImplicitImplementationOfNonPublicInterfaceMembers && first.DeclaredAccessibility < second.DeclaredAccessibility) return true; switch (first) { case IPropertySymbol propertySymbol: if (IsTypeLessAccessibleThanOtherType(propertySymbol.Type, second, [])) return true; if (ContainsTypeLessAccessibleThan(propertySymbol.GetMethod, second, supportsImplicitImplementationOfNonPublicInterfaceMembers)) return true; if (ContainsTypeLessAccessibleThan(propertySymbol.SetMethod, second, supportsImplicitImplementationOfNonPublicInterfaceMembers)) return true; return false; case IMethodSymbol methodSymbol: if (IsTypeLessAccessibleThanOtherType(methodSymbol.ReturnType, second, [])) return true; foreach (var parameter in methodSymbol.Parameters) { if (IsTypeLessAccessibleThanOtherType(parameter.Type, second, [])) return true; } foreach (var typeArg in methodSymbol.TypeArguments) { if (IsTypeLessAccessibleThanOtherType(typeArg, second, [])) return true; } return false; case IEventSymbol eventSymbol: return IsTypeLessAccessibleThanOtherType(eventSymbol.Type, second, []); default: return false; } } private static bool IsTypeLessAccessibleThanOtherType(ITypeSymbol? first, INamedTypeSymbol second, HashSet<ITypeSymbol> alreadyCheckingTypes) { if (first is null) return false; alreadyCheckingTypes.Add(first); if (first is ITypeParameterSymbol typeParameter) { foreach (var constraint in typeParameter.ConstraintTypes) { if (alreadyCheckingTypes.Contains(constraint)) continue; if (IsTypeLessAccessibleThanOtherType(constraint, second, alreadyCheckingTypes)) return true; } } if (first.DeclaredAccessibility <= Accessibility.NotApplicable || second.DeclaredAccessibility <= Accessibility.NotApplicable) { return false; } if (first.DeclaredAccessibility < second.DeclaredAccessibility) return true; if (first is INamedTypeSymbol namedType) { foreach (var genericParam in namedType.TypeArguments) { if (alreadyCheckingTypes.Contains(genericParam)) continue; if (IsTypeLessAccessibleThanOtherType(genericParam, second, alreadyCheckingTypes)) return true; } } if (IsTypeLessAccessibleThanOtherType(first.ContainingType, second, alreadyCheckingTypes)) return true; return false; } public static bool ShouldImplementDisposePattern( Compilation compilation, ImplementInterfaceInfo state, bool explicitly) { // Dispose pattern should be implemented only if - // 1. An interface named 'System.IDisposable' is unimplemented. // 2. This interface has one and only one member - a non-generic method named 'Dispose' that takes no arguments and returns 'void'. // 3. The implementing type is a class that does not already declare any conflicting members named // 'disposedValue' or 'Dispose' (because we will be generating a 'disposedValue' field and a couple of // methods named 'Dispose' as part of implementing the dispose pattern). if (state.ClassOrStructType.TypeKind != TypeKind.Class) return false; var disposeMethod = TryGetIDisposableDispose(compilation); if (disposeMethod == null) return false; var idisposableType = disposeMethod.ContainingType; var unimplementedMembers = explicitly ? state.MembersWithoutExplicitImplementation : state.MembersWithoutExplicitOrImplicitImplementationWhichCanBeImplicitlyImplemented; if (!unimplementedMembers.Any(static (m, idisposableType) => m.type.Equals(idisposableType), idisposableType)) return false; // The dispose pattern is only applicable if the implementing type does // not already have an implementation of IDisposableDispose. return state.ClassOrStructType.FindImplementationForInterfaceMember(disposeMethod) == null; } public static IMethodSymbol? TryGetIDisposableDispose(Compilation compilation) { // Get symbol for 'System.IDisposable'. var idisposable = compilation.GetSpecialType(SpecialType.System_IDisposable); if (idisposable?.TypeKind == TypeKind.Interface) { foreach (var member in idisposable.GetMembers(nameof(IDisposable.Dispose))) { if (member is IMethodSymbol { IsStatic: false, ReturnsVoid: true, Arity: 0, Parameters.Length: 0 } disposeMethod) return disposeMethod; } } return null; } }