diff options
author | N. Taylor Mullen <nimullen@microsoft.com> | 2019-05-17 01:08:10 +0300 |
---|---|---|
committer | N. Taylor Mullen <nimullen@microsoft.com> | 2019-05-17 01:08:10 +0300 |
commit | d05e19ae6e994f2fecc0628cfe92b4d323d0ac1d (patch) | |
tree | 177ebedce6fdb9bcc4d610e1e7288423c1bcee28 | |
parent | babfb20188f3be92ed6fde13dab08b638f3d3bd0 (diff) |
Bug commitnimullen/operationactionbug
-rw-r--r-- | src/Mvc/Mvc.Analyzers/src/SymbolNames.cs | 2 | ||||
-rw-r--r-- | src/Mvc/Mvc.Analyzers/src/TagHelpersInCodeBlocksAnalyzer.cs | 278 | ||||
-rw-r--r-- | src/Mvc/Mvc.Analyzers/src/ViewFeaturesAnalyzerContext.cs | 3 |
3 files changed, 198 insertions, 85 deletions
diff --git a/src/Mvc/Mvc.Analyzers/src/SymbolNames.cs b/src/Mvc/Mvc.Analyzers/src/SymbolNames.cs index 5b579bac6c..944d2af448 100644 --- a/src/Mvc/Mvc.Analyzers/src/SymbolNames.cs +++ b/src/Mvc/Mvc.Analyzers/src/SymbolNames.cs @@ -78,5 +78,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public const string TaskTypeName = "System.Threading.Tasks.Task"; public const string TagHelperRunnerFieldName = "__tagHelperRunner"; + + public const string TagHelperRunnerTypeName = "Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner"; } } diff --git a/src/Mvc/Mvc.Analyzers/src/TagHelpersInCodeBlocksAnalyzer.cs b/src/Mvc/Mvc.Analyzers/src/TagHelpersInCodeBlocksAnalyzer.cs index 288b0d0f46..b5ab6aa03b 100644 --- a/src/Mvc/Mvc.Analyzers/src/TagHelpersInCodeBlocksAnalyzer.cs +++ b/src/Mvc/Mvc.Analyzers/src/TagHelpersInCodeBlocksAnalyzer.cs @@ -2,136 +2,244 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; namespace Microsoft.AspNetCore.Mvc.Analyzers { [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class TagHelpersInCodeBlocksAnalyzer : ViewFeatureAnalyzerBase + public class TagHelpersInCodeBlocksAnalyzer : DiagnosticAnalyzer { public TagHelpersInCodeBlocksAnalyzer() - : base(DiagnosticDescriptors.MVC1006_FunctionsContainingTagHelpersMustBeAsyncAndReturnTask) { + TagHelperInCodeBlockDiagnostic = DiagnosticDescriptors.MVC1006_FunctionsContainingTagHelpersMustBeAsyncAndReturnTask; + SupportedDiagnostics = ImmutableArray.Create(new[] { TagHelperInCodeBlockDiagnostic }); } - protected override void InitializeWorker(ViewFeaturesAnalyzerContext analyzerContext) + private DiagnosticDescriptor TagHelperInCodeBlockDiagnostic { get; } + + public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } + + public override void Initialize(AnalysisContext context) { - analyzerContext.Context.RegisterSyntaxNodeAction(context => + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.RegisterCompilationStartAction(context => { - var invocationExpression = (InvocationExpressionSyntax)context.Node; - var symbol = context.SemanticModel.GetSymbolInfo(invocationExpression, context.CancellationToken).Symbol; - if (symbol == null || symbol.Kind != SymbolKind.Method) + var symbolCache = new SymbolCache(context.Compilation); + + if (symbolCache.TagHelperRunnerRunAsyncMethodSymbol == null) { + // No-op if we can't find bits we care about. + Debugger.Launch(); return; } - var method = (IMethodSymbol)symbol; - if (!IsTagHelperRunnerRunAsync(invocationExpression, method)) + var diagnostics = context.Compilation.GetDiagnostics(); + Debugger.Launch(); + + context.RegisterOperationAction(context => { - return; - } + Debugger.Launch(); + }, OperationKind.Await, OperationKind.Invocation); + }); + } + + internal void InitializeWorker(CompilationStartAnalysisContext context, SymbolCache symbolCache) + { + context.RegisterOperationAction(context => + { + Debugger.Launch(); + }, OperationKind.Await, OperationKind.Invocation); + + context.RegisterOperationAction(context => + { + var awaitOperation = (IAwaitOperation)context.Operation; + + //if (!IsTagHelperRunnerRunAsync(awaitOperation.TargetMethod, symbolCache)) + //{ + // return; + //} + + //var parent = context.Operation.Parent; + //while (parent != null && !IsParentMethod(parent)) + //{ + // parent = parent.Parent; + //} + + //if (parent == null) + //{ + // return; + //} + + //// I'd like to register for invocation operations so I can detect awaits inside of this local function. + + //bool IsParentMethod(IOperation operation) + //{ + // if (operation.Kind == OperationKind.LocalFunction) + // { + // return true; + // } + + // if (operation.Kind == OperationKind.MethodBody) + // { + // return true; + // } - var containingFunction = context.Node.FirstAncestorOrSelf<SyntaxNode>(node => - node.IsKind(SyntaxKind.ParenthesizedLambdaExpression) || - node.IsKind(SyntaxKind.AnonymousMethodExpression) || - node.IsKind(SyntaxKind.LocalFunctionStatement) || - node.IsKind(SyntaxKind.MethodDeclaration)); + // if (operation.Kind == OperationKind.AnonymousFunction) + // { + // return true; + // } - if (containingFunction == null) + // return false; + //} + + }, OperationKind.Await); + + context.RegisterSymbolStartAction(context => + { + var method = (IMethodSymbol)context.Symbol; + + if (method.IsAsync) { - // In practice should never happen because the Razor bits at the bare minimum should be encompassed by a method declaration. - // That being said, if a user were to write malformed code that fulfilled our TagHelper lookup outside of a method block we - // would get a null here. return; } - switch (containingFunction) + context.RegisterOperationAction(context => { - case ParenthesizedLambdaExpressionSyntax parenthesizedLambda: - var lambdaSymbol = (IMethodSymbol)context.SemanticModel.GetSymbolInfo(parenthesizedLambda).Symbol; - if (!lambdaSymbol.IsAsync || - !analyzerContext.TaskType.IsAssignableFrom(lambdaSymbol.ReturnType)) - { - context.ReportDiagnostic(Diagnostic.Create( - SupportedDiagnostic, - parenthesizedLambda.ParameterList.GetLocation(), - new[] { "lambda" })); - } - - break; - case AnonymousMethodExpressionSyntax anonymousMethod: - var anonymousMethodSymbol = (IMethodSymbol)context.SemanticModel.GetSymbolInfo(anonymousMethod).Symbol; - if (!anonymousMethodSymbol.IsAsync || - !analyzerContext.TaskType.IsAssignableFrom(anonymousMethodSymbol.ReturnType)) - { - context.ReportDiagnostic(Diagnostic.Create( - SupportedDiagnostic, - anonymousMethod.DelegateKeyword.GetLocation(), - new[] { "method" })); - } - - break; - case LocalFunctionStatementSyntax localFunction: - var localFunctionReturnType = (INamedTypeSymbol)context.SemanticModel.GetSymbolInfo(localFunction.ReturnType).Symbol; - if (!analyzerContext.TaskType.IsAssignableFrom(localFunctionReturnType) || - localFunction.Modifiers.IndexOf(SyntaxKind.AsyncKeyword) == -1) - { - context.ReportDiagnostic(Diagnostic.Create( - SupportedDiagnostic, - localFunction.Identifier.GetLocation(), - new[] { "local function" })); - } - break; - case MethodDeclarationSyntax methodDeclaration: - var methodDeclarationReturnType = (INamedTypeSymbol)context.SemanticModel.GetSymbolInfo(methodDeclaration.ReturnType).Symbol; - if (!analyzerContext.TaskType.IsAssignableFrom(methodDeclarationReturnType) || - methodDeclaration.Modifiers.IndexOf(SyntaxKind.AsyncKeyword) == -1) - { - context.ReportDiagnostic(Diagnostic.Create( - SupportedDiagnostic, - methodDeclaration.Identifier.GetLocation(), - new[] { "method" })); - } - break; - } - - }, SyntaxKind.InvocationExpression); + var invocationOperation = (IInvocationOperation)context.Operation; + + if (!IsTagHelperRunnerRunAsync(invocationOperation.TargetMethod, symbolCache)) + { + return; + } + + //context.ReportDiagnostic(Diagnostic.Create( + // TagHelperInCodeBlockDiagnostic, + // method.Identifier.GetLocation(), + // new[] { "method" })); + }, OperationKind.Invocation); + + }, SymbolKind.Method); + + /* + * void Foo() + * { + * await __tagHelperRunner.RunAsync... + * } + */ + + //context.RegisterSyntaxNodeAction(context => + //{ + // var invocationExpression = (InvocationExpressionSyntax)context.Node; + // var symbol = context.SemanticModel.GetSymbolInfo(invocationExpression, context.CancellationToken).Symbol; + + // if (symbol == null || symbol.Kind != SymbolKind.Method) + // { + // return; + // } + + // var method = (IMethodSymbol)symbol; + + // if (!IsTagHelperRunnerRunAsync(method, symbolCache)) + // { + // return; + // } + + // var containingFunction = context.Node.FirstAncestorOrSelf<SyntaxNode>(node => + // node.IsKind(SyntaxKind.ParenthesizedLambdaExpression) || + // node.IsKind(SyntaxKind.AnonymousMethodExpression) || + // node.IsKind(SyntaxKind.LocalFunctionStatement) || + // node.IsKind(SyntaxKind.MethodDeclaration)); + + // if (containingFunction == null) + // { + // // In practice should never happen because the Razor bits at the bare minimum should be encompassed by a method declaration. + // // That being said, if a user were to write malformed code that fulfilled our TagHelper lookup outside of a method block we + // // would get a null here. + // return; + // } + + // switch (containingFunction) + // { + // case ParenthesizedLambdaExpressionSyntax parenthesizedLambda: + // var lambdaSymbol = (IMethodSymbol)context.SemanticModel.GetSymbolInfo(parenthesizedLambda).Symbol; + // if (!lambdaSymbol.IsAsync) + // { + // context.ReportDiagnostic(Diagnostic.Create( + // TagHelperInCodeBlockDiagnostic, + // parenthesizedLambda.ParameterList.GetLocation(), + // new[] { "lambda" })); + // } + + // break; + // case AnonymousMethodExpressionSyntax anonymousMethod: + // var anonymousMethodSymbol = (IMethodSymbol)context.SemanticModel.GetSymbolInfo(anonymousMethod).Symbol; + // if (!anonymousMethodSymbol.IsAsync) + // { + // context.ReportDiagnostic(Diagnostic.Create( + // TagHelperInCodeBlockDiagnostic, + // anonymousMethod.DelegateKeyword.GetLocation(), + // new[] { "method" })); + // } + + // break; + // case LocalFunctionStatementSyntax localFunction: + // var localFunctionReturnType = (INamedTypeSymbol)context.SemanticModel.GetSymbolInfo(localFunction.ReturnType).Symbol; + // if (localFunction.Modifiers.IndexOf(SyntaxKind.AsyncKeyword) == -1) + // { + // context.ReportDiagnostic(Diagnostic.Create( + // TagHelperInCodeBlockDiagnostic, + // localFunction.Identifier.GetLocation(), + // new[] { "local function" })); + // } + // break; + // case MethodDeclarationSyntax methodDeclaration: + // var methodDeclarationReturnType = (INamedTypeSymbol)context.SemanticModel.GetSymbolInfo(methodDeclaration.ReturnType).Symbol; + // if (methodDeclaration.Modifiers.IndexOf(SyntaxKind.AsyncKeyword) == -1) + // { + // context.ReportDiagnostic(Diagnostic.Create( + // TagHelperInCodeBlockDiagnostic, + // methodDeclaration.Identifier.GetLocation(), + // new[] { "method" })); + // } + // break; + // } + + //}, SyntaxKind.InvocationExpression); } - private bool IsTagHelperRunnerRunAsync(InvocationExpressionSyntax parentExpression, IMethodSymbol method) + private bool IsTagHelperRunnerRunAsync(IMethodSymbol method, SymbolCache symbolCache) { if (method.IsGenericMethod) { return false; } - if (!string.Equals(SymbolNames.RunAsyncMethodName, method.Name, StringComparison.Ordinal)) + if (method != symbolCache.TagHelperRunnerRunAsyncMethodSymbol) { return false; } - if (!parentExpression.Expression.IsKind(SyntaxKind.SimpleMemberAccessExpression)) - { - return false; - } + return true; + } - var memberAccessExpressionSyntax = (MemberAccessExpressionSyntax)parentExpression.Expression; - if (!memberAccessExpressionSyntax.Expression.IsKind(SyntaxKind.IdentifierName)) + internal readonly struct SymbolCache + { + public SymbolCache(Compilation compilation) { - return false; - } + var tagHelperRunnerType = compilation.GetTypeByMetadataName(SymbolNames.TagHelperRunnerTypeName); + var members = tagHelperRunnerType.GetMembers(SymbolNames.RunAsyncMethodName); - var identifier = (IdentifierNameSyntax)memberAccessExpressionSyntax.Expression; - if (!string.Equals(SymbolNames.TagHelperRunnerFieldName, identifier.Identifier.ValueText, StringComparison.Ordinal)) - { - return false; + TagHelperRunnerRunAsyncMethodSymbol = members.Length == 1 ? (IMethodSymbol)members[0] : null; } - return true; + public IMethodSymbol TagHelperRunnerRunAsyncMethodSymbol { get; } } } } diff --git a/src/Mvc/Mvc.Analyzers/src/ViewFeaturesAnalyzerContext.cs b/src/Mvc/Mvc.Analyzers/src/ViewFeaturesAnalyzerContext.cs index 9f7323aa80..0caba5e3b5 100644 --- a/src/Mvc/Mvc.Analyzers/src/ViewFeaturesAnalyzerContext.cs +++ b/src/Mvc/Mvc.Analyzers/src/ViewFeaturesAnalyzerContext.cs @@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers HtmlHelperType = GetType(SymbolNames.IHtmlHelperType); HtmlHelperPartialExtensionsType = GetType(SymbolNames.HtmlHelperPartialExtensionsType); TaskType = GetType(SymbolNames.TaskTypeName); + TagHelperRunnerType = GetType(SymbolNames.TagHelperRunnerTypeName); } public CompilationStartAnalysisContext Context { get; } @@ -26,6 +27,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public INamedTypeSymbol TaskType { get; } + public INamedTypeSymbol TagHelperRunnerType { get; } + private INamedTypeSymbol GetType(string name) => Context.Compilation.GetTypeByMetadataName(name); public bool IsHtmlHelperExtensionMethod(IMethodSymbol method) |