/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Features/CSharp/Portable/MetadataAsSource/CSharpMetadataAsSourceService.cs
233 строки
10 KB
Cyrus Najmabadi
Make the types in CSharp Features sealed
02 окт 2024, 22:50
02 окт 2024, 22:50
1ab7d33
Код
Авторство
О чём код?
// 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. #nullable disable using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.DocumentationComments; using Microsoft.CodeAnalysis.CSharp.Simplification; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.MetadataAsSource; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; namespace Microsoft.CodeAnalysis.CSharp.MetadataAsSource; using static SyntaxFactory; internal sealed partial class CSharpMetadataAsSourceService : AbstractMetadataAsSourceService { private static readonly AbstractFormattingRule s_memberSeparationRule = new FormattingRule(); public static readonly CSharpMetadataAsSourceService Instance = new(); private CSharpMetadataAsSourceService() { } protected override async Task<Document> AddAssemblyInfoRegionAsync(Document document, Compilation symbolCompilation, ISymbol symbol, CancellationToken cancellationToken) { var assemblyInfo = MetadataAsSourceHelpers.GetAssemblyInfo(symbol.ContainingAssembly); var assemblyPath = MetadataAsSourceHelpers.GetAssemblyDisplay(symbolCompilation, symbol.ContainingAssembly); var regionTrivia = RegionDirectiveTrivia(true) .WithTrailingTrivia(new[] { Space, PreprocessingMessage(assemblyInfo) }); var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var newRoot = oldRoot.WithPrependedLeadingTrivia( Trivia(regionTrivia), CarriageReturnLineFeed, Comment("// " + assemblyPath), CarriageReturnLineFeed, Trivia(EndRegionDirectiveTrivia(true)), CarriageReturnLineFeed, CarriageReturnLineFeed); return document.WithSyntaxRoot(newRoot); } protected override ImmutableArray<AbstractFormattingRule> GetFormattingRules(Document document) => [s_memberSeparationRule, .. Formatter.GetDefaultFormattingRules(document)]; protected override async Task<Document> ConvertDocCommentsToRegularCommentsAsync(Document document, IDocumentationCommentFormattingService docCommentFormattingService, CancellationToken cancellationToken) { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var newSyntaxRoot = DocCommentConverter.ConvertToRegularComments(syntaxRoot, docCommentFormattingService, cancellationToken); return document.WithSyntaxRoot(newSyntaxRoot); } protected override ImmutableArray<AbstractReducer> GetReducers() => [ new CSharpNameReducer(), new CSharpEscapingReducer(), new CSharpParenthesizedExpressionReducer(), new CSharpParenthesizedPatternReducer(), new CSharpDefaultExpressionReducer(), ]; /// <summary> /// Adds <c>#nullable enable</c> and <c>#nullable disable</c> annotations to the file as necessary. Note that /// this does not try to be 100% accurate, but rather it handles the most common cases out there. Specifically, /// if a file contains any nullable annotated/not-annotated types, then we prefix the file with <c>#nullable /// enable</c>. Then if we hit any members that explicitly have *oblivious* types, but no annotated or /// non-annotated types, then we switch to <c>#nullable disable</c> for those specific members. /// <para/> /// This is technically innacurate for possible, but very uncommon cases. For example, if the user's code /// explicitly did something like this: /// /// <code> /// public void Goo(string goo, /// #nullable disable /// string bar /// #nullable enable /// string baz); /// </code> /// /// Then we would be unable to handle that. However, this is highly unlikely to happen, and so we accept the /// inaccuracy for the purpose of simplicity and for handling the much more common cases of either the entire /// file being annotated, or the user individually disabling annotations at the member level. /// </summary> protected override async Task<Document> AddNullableRegionsAsync(Document document, CancellationToken cancellationToken) { var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var options = (CSharpParseOptions)tree.Options; // Only valid for C# 8 and above. if (options.LanguageVersion < LanguageVersion.CSharp8) return document; var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); var (_, annotatedOrNotAnnotated) = GetNullableAnnotations(root); // If there are no annotated or not-annotated types, then no need to add `#nullable enable`. if (!annotatedOrNotAnnotated) return document; var newRoot = AddNullableRegions(root, cancellationToken); newRoot = newRoot.WithPrependedLeadingTrivia(CreateNullableTrivia(enable: true)); return document.WithSyntaxRoot(newRoot); } private static (bool oblivious, bool annotatedOrNotAnnotated) GetNullableAnnotations(SyntaxNode node) { return (HasAnnotation(node, NullableSyntaxAnnotation.Oblivious), HasAnnotation(node, NullableSyntaxAnnotation.AnnotatedOrNotAnnotated)); } private static bool HasAnnotation(SyntaxNode node, SyntaxAnnotation annotation) { // see if any child nodes have this annotation. Ignore anything in attributes (like `[Obsolete]void Goo()` // as these are not impacted by `#nullable` regions. Instead, we only care about signature types. var annotatedChildren = node.GetAnnotatedNodes(annotation); return annotatedChildren.Any(n => n.GetAncestorOrThis<AttributeSyntax>() == null); } private static SyntaxTrivia[] CreateNullableTrivia(bool enable) { var keyword = enable ? SyntaxKind.EnableKeyword : SyntaxKind.DisableKeyword; return [ Trivia(NullableDirectiveTrivia(Token(keyword), isActive: enable)), ElasticCarriageReturnLineFeed, ElasticCarriageReturnLineFeed, ]; } private TSyntax AddNullableRegions<TSyntax>(TSyntax node, CancellationToken cancellationToken) where TSyntax : SyntaxNode { return node switch { CompilationUnitSyntax compilationUnit => (TSyntax)(object)compilationUnit.WithMembers(AddNullableRegions(compilationUnit.Members, cancellationToken)), NamespaceDeclarationSyntax ns => (TSyntax)(object)ns.WithMembers(AddNullableRegions(ns.Members, cancellationToken)), TypeDeclarationSyntax type => (TSyntax)(object)AddNullableRegionsAroundTypeMembers(type, cancellationToken), _ => node, }; } private SyntaxList<MemberDeclarationSyntax> AddNullableRegions( SyntaxList<MemberDeclarationSyntax> members, CancellationToken cancellationToken) { return [.. members.Select(m => AddNullableRegions(m, cancellationToken))]; } private TypeDeclarationSyntax AddNullableRegionsAroundTypeMembers( TypeDeclarationSyntax type, CancellationToken cancellationToken) { using var _ = ArrayBuilder<MemberDeclarationSyntax>.GetInstance(out var builder); var currentlyEnabled = true; foreach (var member in type.Members) { cancellationToken.ThrowIfCancellationRequested(); if (member is BaseTypeDeclarationSyntax) { // if we hit a type, and we're currently disabled, then switch us back to enabled for that type. // This ensures whenever we walk into a type-decl, we're always in the enabled-state. builder.Add(TransitionTo(AddNullableRegions(member, cancellationToken), enabled: true, ref currentlyEnabled)); continue; } // we hit a member. see what sort of types it contained. var (oblivious, annotatedOrNotAnnotated) = GetNullableAnnotations(member); // if we have null annotations, transition us back to the enabled state if (annotatedOrNotAnnotated) { builder.Add(TransitionTo(member, enabled: true, ref currentlyEnabled)); } else if (oblivious) { // if we didn't have null annotations, and we had an explicit oblivious type, // then definitely transition us to the disabled state builder.Add(TransitionTo(member, enabled: false, ref currentlyEnabled)); } else { // had no types at all. no need to change state. builder.Add(member); } } var result = type.WithMembers([.. builder]); if (!currentlyEnabled) { // switch us back to enabled as we leave the type. result = result.WithCloseBraceToken( result.CloseBraceToken.WithPrependedLeadingTrivia(CreateNullableTrivia(enable: true))); } return result; } private static MemberDeclarationSyntax TransitionTo(MemberDeclarationSyntax member, bool enabled, ref bool currentlyEnabled) { if (enabled == currentlyEnabled) { // already in the right state. don't start a #nullable region return member; } else { // switch to the desired state and add the right trivia to the node. currentlyEnabled = enabled; return member.WithPrependedLeadingTrivia(CreateNullableTrivia(currentlyEnabled)); } } }