| File: JSInteropAnalyzer.cs | Web Access |
| Project: src\aspnetcore\src\Components\Analyzers\src\Microsoft.AspNetCore.Components.Analyzers.csproj (Microsoft.AspNetCore.Components.Analyzers) |
// 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 Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; #nullable enable namespace Microsoft.AspNetCore.Components.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class JSInteropAnalyzer : DiagnosticAnalyzer { private const string JSRuntimeExtensionsTypeName = "Microsoft.JSInterop.JSRuntimeExtensions"; private const string JSObjectReferenceExtensionsTypeName = "Microsoft.JSInterop.JSObjectReferenceExtensions"; private const string JSInProcessRuntimeExtensionsTypeName = "Microsoft.JSInterop.JSInProcessRuntimeExtensions"; private const string JSInProcessObjectReferenceExtensionsTypeName = "Microsoft.JSInterop.JSInProcessObjectReferenceExtensions"; private const string IJSRuntimeTypeName = "Microsoft.JSInterop.IJSRuntime"; private const string IJSInProcessRuntimeTypeName = "Microsoft.JSInterop.IJSInProcessRuntime"; private const string IJSObjectReferenceTypeName = "Microsoft.JSInterop.IJSObjectReference"; private const string IJSInProcessObjectReferenceTypeName = "Microsoft.JSInterop.IJSInProcessObjectReference"; public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.UnguardedJSInteropCall); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.RegisterCompilationStartAction(context => { var ijsRuntimeType = context.Compilation.GetTypeByMetadataName(IJSRuntimeTypeName); var ijsInProcessRuntimeType = context.Compilation.GetTypeByMetadataName(IJSInProcessRuntimeTypeName); var ijsObjectReferenceType = context.Compilation.GetTypeByMetadataName(IJSObjectReferenceTypeName); var ijsInProcessObjectReferenceType = context.Compilation.GetTypeByMetadataName(IJSInProcessObjectReferenceTypeName); var jsRuntimeExtensionsType = context.Compilation.GetTypeByMetadataName(JSRuntimeExtensionsTypeName); var jsObjectReferenceExtensionsType = context.Compilation.GetTypeByMetadataName(JSObjectReferenceExtensionsTypeName); var jsInProcessRuntimeExtensionsType = context.Compilation.GetTypeByMetadataName(JSInProcessRuntimeExtensionsTypeName); var jsInProcessObjectReferenceExtensionsType = context.Compilation.GetTypeByMetadataName(JSInProcessObjectReferenceExtensionsTypeName); if (ijsRuntimeType is null && ijsInProcessRuntimeType is null && ijsObjectReferenceType is null && ijsInProcessObjectReferenceType is null && jsRuntimeExtensionsType is null && jsObjectReferenceExtensionsType is null && jsInProcessRuntimeExtensionsType is null && jsInProcessObjectReferenceExtensionsType is null) { return; } context.RegisterOperationAction(context => { var invocation = (IInvocationOperation)context.Operation; var targetMethod = invocation.TargetMethod; if (IsInsideTryBlockWithCatch(invocation)) { return; } if (!IsJSInteropInvocation( invocation, targetMethod, ijsRuntimeType, ijsInProcessRuntimeType, ijsObjectReferenceType, ijsInProcessObjectReferenceType, jsRuntimeExtensionsType, jsObjectReferenceExtensionsType, jsInProcessRuntimeExtensionsType, jsInProcessObjectReferenceExtensionsType)) { return; } context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.UnguardedJSInteropCall, invocation.Syntax.GetLocation(), targetMethod.Name)); }, OperationKind.Invocation); }); } private static bool IsJSInteropInvocation( IInvocationOperation invocation, IMethodSymbol targetMethod, INamedTypeSymbol? ijsRuntimeType, INamedTypeSymbol? ijsInProcessRuntimeType, INamedTypeSymbol? ijsObjectReferenceType, INamedTypeSymbol? ijsInProcessObjectReferenceType, INamedTypeSymbol? jsRuntimeExtensionsType, INamedTypeSymbol? jsObjectReferenceExtensionsType, INamedTypeSymbol? jsInProcessRuntimeExtensionsType, INamedTypeSymbol? jsInProcessObjectReferenceExtensionsType) { if (IsJSInteropType(targetMethod.ContainingType, ijsRuntimeType, ijsInProcessRuntimeType, ijsObjectReferenceType, ijsInProcessObjectReferenceType)) { return true; } if (!IsJSInteropExtensionClass(targetMethod.ContainingType, jsRuntimeExtensionsType, jsObjectReferenceExtensionsType, jsInProcessRuntimeExtensionsType, jsInProcessObjectReferenceExtensionsType)) { return false; } var receiverType = GetReceiverType(invocation); return IsJSInteropType(receiverType, ijsRuntimeType, ijsInProcessRuntimeType, ijsObjectReferenceType, ijsInProcessObjectReferenceType); } private static bool IsInsideTryBlockWithCatch(IOperation invocationOperation) { var previous = invocationOperation; var current = invocationOperation.Parent; while (current is not null) { switch (current) { case IMethodBodyOperation: case IConstructorBodyOperation: case IAnonymousFunctionOperation: case ILocalFunctionOperation: return false; case ITryOperation tryOperation when tryOperation.Catches.Length > 0 && ReferenceEquals(previous, tryOperation.Body): return true; } previous = current; current = current.Parent; } return false; } private static ITypeSymbol? GetReceiverType(IInvocationOperation invocation) { if (invocation.TargetMethod.IsExtensionMethod && invocation.Arguments.Length > 0) { return invocation.Arguments[0].Value.Type; } return invocation.Instance?.Type; } private static bool IsJSInteropType( ITypeSymbol? type, INamedTypeSymbol? ijsRuntimeType, INamedTypeSymbol? ijsInProcessRuntimeType, INamedTypeSymbol? ijsObjectReferenceType, INamedTypeSymbol? ijsInProcessObjectReferenceType) { if (type is null) { return false; } if (ImplementsInterface(type, ijsRuntimeType)) { return true; } if (ImplementsInterface(type, ijsInProcessRuntimeType)) { return true; } if (ImplementsInterface(type, ijsObjectReferenceType)) { return true; } if (ImplementsInterface(type, ijsInProcessObjectReferenceType)) { return true; } return false; } private static bool ImplementsInterface(ITypeSymbol type, INamedTypeSymbol? interfaceType) { if (interfaceType is not null) { if (SymbolEqualityComparer.Default.Equals(type, interfaceType)) { return true; } foreach (var iface in type.AllInterfaces) { if (SymbolEqualityComparer.Default.Equals(iface, interfaceType)) { return true; } } return false; } return false; } private static bool IsJSInteropExtensionClass( INamedTypeSymbol containingType, INamedTypeSymbol? jsRuntimeExtensionsType, INamedTypeSymbol? jsObjectReferenceExtensionsType, INamedTypeSymbol? jsInProcessRuntimeExtensionsType, INamedTypeSymbol? jsInProcessObjectReferenceExtensionsType) { return SymbolEqualityComparer.Default.Equals(containingType, jsRuntimeExtensionsType) || SymbolEqualityComparer.Default.Equals(containingType, jsObjectReferenceExtensionsType) || SymbolEqualityComparer.Default.Equals(containingType, jsInProcessRuntimeExtensionsType) || SymbolEqualityComparer.Default.Equals(containingType, jsInProcessObjectReferenceExtensionsType); } }