/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/RoslynAnalyzers/Utilities/Workspaces/SyntaxGeneratorExtensions.cs
494 строки
23 KB
Joey Robichaud
Fixup nullabilty warnings
18 июн 2025, 10:39
18 июн 2025, 10:39
6a3c346
Код
Авторство
О чём код?
// 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.Collections.Generic; using System.Linq; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editing; namespace Analyzer.Utilities { internal static class SyntaxGeneratorExtensions { private const string LeftIdentifierName = "left"; private const string RightIdentifierName = "right"; private const string ReferenceEqualsMethodName = "ReferenceEquals"; private const string EqualsMethodName = "Equals"; private const string CompareToMethodName = "CompareTo"; private const string SystemNotImplementedExceptionTypeName = "System.NotImplementedException"; /// <summary> /// Creates a default declaration for an operator equality overload. /// </summary> /// <param name="generator"> /// The <see cref="SyntaxGenerator"/> used to create the declaration. /// </param> /// <param name="containingType"> /// A symbol specifying the type of the operands of the comparison operator. /// </param> /// <returns> /// A <see cref="SyntaxNode"/> representing the declaration. /// </returns> public static SyntaxNode DefaultOperatorEqualityDeclaration(this SyntaxGenerator generator, INamedTypeSymbol containingType) { var leftArgument = generator.IdentifierName(LeftIdentifierName); var rightArgument = generator.IdentifierName(RightIdentifierName); List<SyntaxNode> statements = []; if (containingType.TypeKind == TypeKind.Class) { statements.Add( generator.IfStatement( generator.InvocationExpression( generator.IdentifierName(ReferenceEqualsMethodName), leftArgument, generator.NullLiteralExpression()), new[] { generator.ReturnStatement( generator.InvocationExpression( generator.IdentifierName(ReferenceEqualsMethodName), rightArgument, generator.NullLiteralExpression())) })); } statements.Add( generator.ReturnStatement( generator.InvocationExpression( generator.MemberAccessExpression( leftArgument, EqualsMethodName), rightArgument))); return generator.ComparisonOperatorDeclaration(OperatorKind.Equality, containingType, statements.ToArray()); } /// <summary> /// Creates a reference to a named type suitable for use in accessing a static member of the type. /// </summary> /// <param name="generator">The <see cref="SyntaxGenerator"/> used to create the type reference.</param> /// <param name="typeSymbol">The named type to reference.</param> /// <returns>A <see cref="SyntaxNode"/> representing the type reference expression.</returns> public static SyntaxNode TypeExpressionForStaticMemberAccess(this SyntaxGenerator generator, INamedTypeSymbol typeSymbol) { var qualifiedNameSyntaxKind = generator.QualifiedName(generator.IdentifierName("ignored"), generator.IdentifierName("ignored")).RawKind; var memberAccessExpressionSyntaxKind = generator.MemberAccessExpression(generator.IdentifierName("ignored"), "ignored").RawKind; var typeExpression = generator.TypeExpression(typeSymbol); return QualifiedNameToMemberAccess(qualifiedNameSyntaxKind, memberAccessExpressionSyntaxKind, typeExpression, generator); // Local function static SyntaxNode QualifiedNameToMemberAccess(int qualifiedNameSyntaxKind, int memberAccessExpressionSyntaxKind, SyntaxNode expression, SyntaxGenerator generator) { if (expression.RawKind == qualifiedNameSyntaxKind) { var left = QualifiedNameToMemberAccess(qualifiedNameSyntaxKind, memberAccessExpressionSyntaxKind, expression.ChildNodes().First(), generator); var right = expression.ChildNodes().Last(); return generator.MemberAccessExpression(left, right); } return expression; } } /// <summary> /// Creates a default declaration for an operator inequality overload. /// </summary> /// <param name="generator"> /// The <see cref="SyntaxGenerator"/> used to create the declaration. /// </param> /// <param name="containingType"> /// A symbol specifying the type of the operands of the comparison operator. /// </param> /// <returns> /// A <see cref="SyntaxNode"/> representing the declaration. /// </returns> public static SyntaxNode DefaultOperatorInequalityDeclaration(this SyntaxGenerator generator, INamedTypeSymbol containingType) { var leftArgument = generator.IdentifierName(LeftIdentifierName); var rightArgument = generator.IdentifierName(RightIdentifierName); var returnStatement = generator.ReturnStatement( generator.LogicalNotExpression( generator.ValueEqualsExpression( leftArgument, rightArgument))); return generator.ComparisonOperatorDeclaration(OperatorKind.Inequality, containingType, returnStatement); } /// <summary> /// Creates a default declaration for an operator less than overload. /// </summary> /// <param name="generator"> /// The <see cref="SyntaxGenerator"/> used to create the declaration. /// </param> /// <param name="containingType"> /// A symbol specifying the type of the operands of the comparison operator. /// </param> /// <returns> /// A <see cref="SyntaxNode"/> representing the declaration. /// </returns> public static SyntaxNode DefaultOperatorLessThanDeclaration(this SyntaxGenerator generator, INamedTypeSymbol containingType) { var leftArgument = generator.IdentifierName(LeftIdentifierName); var rightArgument = generator.IdentifierName(RightIdentifierName); SyntaxNode expression; if (containingType.TypeKind == TypeKind.Class) { expression = generator.ConditionalExpression( generator.InvocationExpression( generator.IdentifierName(ReferenceEqualsMethodName), leftArgument, generator.NullLiteralExpression()), generator.LogicalNotExpression( generator.InvocationExpression( generator.IdentifierName(ReferenceEqualsMethodName), rightArgument, generator.NullLiteralExpression())), generator.LessThanExpression( generator.InvocationExpression( generator.MemberAccessExpression(leftArgument, generator.IdentifierName(CompareToMethodName)), rightArgument), generator.LiteralExpression(0))); } else { expression = generator.LessThanExpression( generator.InvocationExpression( generator.MemberAccessExpression(leftArgument, generator.IdentifierName(CompareToMethodName)), rightArgument), generator.LiteralExpression(0)); } var returnStatement = generator.ReturnStatement(expression); return generator.ComparisonOperatorDeclaration(OperatorKind.LessThan, containingType, returnStatement); } /// <summary> /// Creates a default declaration for an operator less than or equal overload. /// </summary> /// <param name="generator"> /// The <see cref="SyntaxGenerator"/> used to create the declaration. /// </param> /// <param name="containingType"> /// A symbol specifying the type of the operands of the comparison operator. /// </param> /// <returns> /// A <see cref="SyntaxNode"/> representing the declaration. /// </returns> public static SyntaxNode DefaultOperatorLessThanOrEqualDeclaration(this SyntaxGenerator generator, INamedTypeSymbol containingType) { var leftArgument = generator.IdentifierName(LeftIdentifierName); var rightArgument = generator.IdentifierName(RightIdentifierName); SyntaxNode expression; if (containingType.TypeKind == TypeKind.Class) { expression = generator.LogicalOrExpression( generator.InvocationExpression( generator.IdentifierName(ReferenceEqualsMethodName), leftArgument, generator.NullLiteralExpression()), generator.LessThanOrEqualExpression( generator.InvocationExpression( generator.MemberAccessExpression(leftArgument, generator.IdentifierName(CompareToMethodName)), rightArgument), generator.LiteralExpression(0))); } else { expression = generator.LessThanOrEqualExpression( generator.InvocationExpression( generator.MemberAccessExpression(leftArgument, generator.IdentifierName(CompareToMethodName)), rightArgument), generator.LiteralExpression(0)); } var returnStatement = generator.ReturnStatement(expression); return generator.ComparisonOperatorDeclaration(OperatorKind.LessThanOrEqual, containingType, returnStatement); } /// <summary> /// Creates a default declaration for an operator greater than overload. /// </summary> /// <param name="generator"> /// The <see cref="SyntaxGenerator"/> used to create the declaration. /// </param> /// <param name="containingType"> /// A symbol specifying the type of the operands of the comparison operator. /// </param> /// <returns> /// A <see cref="SyntaxNode"/> representing the declaration. /// </returns> public static SyntaxNode DefaultOperatorGreaterThanDeclaration(this SyntaxGenerator generator, INamedTypeSymbol containingType) { var leftArgument = generator.IdentifierName(LeftIdentifierName); var rightArgument = generator.IdentifierName(RightIdentifierName); SyntaxNode expression; if (containingType.TypeKind == TypeKind.Class) { expression = generator.LogicalAndExpression( generator.LogicalNotExpression( generator.InvocationExpression( generator.IdentifierName(ReferenceEqualsMethodName), leftArgument, generator.NullLiteralExpression())), generator.GreaterThanExpression( generator.InvocationExpression( generator.MemberAccessExpression(leftArgument, generator.IdentifierName(CompareToMethodName)), rightArgument), generator.LiteralExpression(0))); } else { expression = generator.GreaterThanExpression( generator.InvocationExpression( generator.MemberAccessExpression(leftArgument, generator.IdentifierName(CompareToMethodName)), rightArgument), generator.LiteralExpression(0)); } var returnStatement = generator.ReturnStatement(expression); return generator.ComparisonOperatorDeclaration(OperatorKind.GreaterThan, containingType, returnStatement); } /// <summary> /// Creates a default declaration for an operator greater than or equal overload. /// </summary> /// <param name="generator"> /// The <see cref="SyntaxGenerator"/> used to create the declaration. /// </param> /// <param name="containingType"> /// A symbol specifying the type of the operands of the comparison operator. /// </param> /// <returns> /// A <see cref="SyntaxNode"/> representing the declaration. /// </returns> public static SyntaxNode DefaultOperatorGreaterThanOrEqualDeclaration(this SyntaxGenerator generator, INamedTypeSymbol containingType) { var leftArgument = generator.IdentifierName(LeftIdentifierName); var rightArgument = generator.IdentifierName(RightIdentifierName); SyntaxNode expression; if (containingType.TypeKind == TypeKind.Class) { expression = generator.ConditionalExpression( generator.InvocationExpression( generator.IdentifierName(ReferenceEqualsMethodName), leftArgument, generator.NullLiteralExpression()), generator.InvocationExpression( generator.IdentifierName(ReferenceEqualsMethodName), rightArgument, generator.NullLiteralExpression()), generator.GreaterThanOrEqualExpression( generator.InvocationExpression( generator.MemberAccessExpression(leftArgument, generator.IdentifierName(CompareToMethodName)), rightArgument), generator.LiteralExpression(0))); } else { expression = generator.GreaterThanOrEqualExpression( generator.InvocationExpression( generator.MemberAccessExpression(leftArgument, generator.IdentifierName(CompareToMethodName)), rightArgument), generator.LiteralExpression(0)); } var returnStatement = generator.ReturnStatement(expression); return generator.ComparisonOperatorDeclaration(OperatorKind.GreaterThanOrEqual, containingType, returnStatement); } private static SyntaxNode ComparisonOperatorDeclaration(this SyntaxGenerator generator, OperatorKind operatorKind, INamedTypeSymbol containingType, params SyntaxNode[] statements) { return generator.OperatorDeclaration( operatorKind, [ generator.ParameterDeclaration(LeftIdentifierName, generator.TypeExpression(containingType)), generator.ParameterDeclaration(RightIdentifierName, generator.TypeExpression(containingType)) ], generator.TypeExpression(SpecialType.System_Boolean), Accessibility.Public, DeclarationModifiers.Static, statements); } /// <summary> /// Creates a default declaration for an override of <see cref="object.Equals(object)"/>. /// </summary> /// <param name="generator"> /// The <see cref="SyntaxGenerator"/> used to create the declaration. /// </param> /// <param name="compilation">The compilation</param> /// <param name="containingType"> /// A symbol specifying the type in which the declaration is to be created. /// </param> /// <returns> /// A <see cref="SyntaxNode"/> representing the declaration. /// </returns> public static SyntaxNode DefaultEqualsOverrideDeclaration(this SyntaxGenerator generator, Compilation compilation, INamedTypeSymbol containingType) { var argumentName = generator.IdentifierName("obj"); List<SyntaxNode> statements = []; if (containingType.TypeKind == TypeKind.Class) { statements.AddRange(new[] { generator.IfStatement( generator.InvocationExpression( generator.IdentifierName(ReferenceEqualsMethodName), generator.ThisExpression(), argumentName), new[] { generator.ReturnStatement(generator.TrueLiteralExpression()) }), generator.IfStatement( generator.InvocationExpression( generator.IdentifierName(ReferenceEqualsMethodName), argumentName, generator.NullLiteralExpression()), new[] { generator.ReturnStatement(generator.FalseLiteralExpression()) }) }); } statements.AddRange(generator.DefaultMethodBody(compilation)); return generator.MethodDeclaration( WellKnownMemberNames.ObjectEquals, new[] { generator.ParameterDeclaration(argumentName.ToString(), generator.TypeExpression(SpecialType.System_Object)) }, returnType: generator.TypeExpression(SpecialType.System_Boolean), accessibility: Accessibility.Public, modifiers: DeclarationModifiers.Override, statements: statements); } /// <summary> /// Creates a default declaration for an override of <see cref="object.GetHashCode()"/>. /// </summary> /// <param name="generator"> /// The <see cref="SyntaxGenerator"/> used to create the declaration. /// </param> /// <param name="compilation">The compilation</param> /// <returns> /// A <see cref="SyntaxNode"/> representing the declaration. /// </returns> public static SyntaxNode DefaultGetHashCodeOverrideDeclaration( this SyntaxGenerator generator, Compilation compilation) { return generator.MethodDeclaration( WellKnownMemberNames.ObjectGetHashCode, returnType: generator.TypeExpression(SpecialType.System_Int32), accessibility: Accessibility.Public, modifiers: DeclarationModifiers.Override, statements: generator.DefaultMethodBody(compilation)); } /// <summary> /// Creates a default set of statements to place within a generated method body. /// </summary> /// <param name="generator"> /// The <see cref="SyntaxGenerator"/> used to create the statements. /// </param> /// <param name="compilation">The compilation</param> /// <returns> /// An sequence containing a single statement that throws <see cref="System.NotImplementedException"/>. /// </returns> public static IEnumerable<SyntaxNode> DefaultMethodBody( this SyntaxGenerator generator, Compilation compilation) { yield return DefaultMethodStatement(generator, compilation); } public static SyntaxNode DefaultMethodStatement(this SyntaxGenerator generator, Compilation compilation) { return generator.ThrowStatement(generator.ObjectCreationExpression( generator.TypeExpression( compilation.GetOrCreateTypeByMetadataName(SystemNotImplementedExceptionTypeName)!))); } public static SyntaxNode? TryGetContainingDeclaration(this SyntaxGenerator generator, SyntaxNode? node, DeclarationKind kind) { if (node is null) { return null; } var declarationKind = generator.GetDeclarationKind(node); while (declarationKind != kind) { node = generator.GetDeclaration(node.Parent); if (node is null) { return null; } declarationKind = generator.GetDeclarationKind(node); } return node; } /// <summary> /// Creates the first unused identifier name based on the provided base name. /// </summary> /// <param name="generator">The <see cref="SyntaxGenerator"/> used to create the identifier name.</param> /// <param name="semanticModel">The semantic model.</param> /// <param name="position">The position in the code.</param> /// <param name="baseName">The base name to use.</param> /// <param name="maxTries">Maximum number of tries.</param> /// <returns> /// A <see cref="SyntaxNode"/> representing an unused identifier name. /// This can be either the base name itself or a variation of it with a number appended to make it unique. /// </returns> public static SyntaxNode FirstUnusedIdentifierName(this SyntaxGenerator generator, SemanticModel semanticModel, int position, string baseName, int maxTries = int.MaxValue) { var identifierName = generator.IdentifierName(baseName); if (semanticModel.GetSpeculativeSymbolInfo(position, identifierName, SpeculativeBindingOption.BindAsExpression).Symbol is null) { return identifierName; } for (int i = 1; i < maxTries; i++) { identifierName = generator.IdentifierName($"{baseName}{i}"); if (semanticModel.GetSpeculativeSymbolInfo(position, identifierName, SpeculativeBindingOption.BindAsExpression).Symbol is null) { break; } } return identifierName; } } }