// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using System.Globalization; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Intermediate; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { public class InstrumentationPass : RazorIRPassBase, IRazorIROptimizationPass { public override int Order => DefaultFeatureOrder; public override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument) { var walker = new Visitor(); walker.VisitDocument(irDocument); for (var i = 0; i < walker.Items.Count; i++) { var node = walker.Items[i]; AddInstrumentation(node); } } private static void AddInstrumentation(InstrumentationItem item) { var beginContextMethodName = "BeginContext"; /* ORIGINAL: BeginContextMethodName */ var endContextMethodName = "EndContext"; /* ORIGINAL: EndContextMethodName */ var beginNode = new CSharpStatementIRNode() { Parent = item.Node.Parent }; RazorIRBuilder.Create(beginNode) .Add(new RazorIRToken() { Kind = RazorIRToken.TokenKind.CSharp, Content = string.Format("{0}({1}, {2}, {3});", beginContextMethodName, item.Source.AbsoluteIndex.ToString(CultureInfo.InvariantCulture), item.Source.Length.ToString(CultureInfo.InvariantCulture), item.IsLiteral ? "true" : "false") }); var endNode = new CSharpStatementIRNode() { Parent = item.Node.Parent }; RazorIRBuilder.Create(endNode) .Add(new RazorIRToken() { Kind = RazorIRToken.TokenKind.CSharp, Content = string.Format("{0}();", endContextMethodName) }); var nodeIndex = item.Node.Parent.Children.IndexOf(item.Node); item.Node.Parent.Children.Insert(nodeIndex, beginNode); item.Node.Parent.Children.Insert(nodeIndex + 2, endNode); } private struct InstrumentationItem { public InstrumentationItem(RazorIRNode node, bool isLiteral, SourceSpan source) { Node = node; IsLiteral = isLiteral; Source = source; } public RazorIRNode Node { get; } public bool IsLiteral { get; } public SourceSpan Source { get; } } private class Visitor : RazorIRNodeWalker { public List Items { get; } = new List(); public override void VisitHtml(HtmlContentIRNode node) { if (node.Source != null) { Items.Add(new InstrumentationItem(node, isLiteral: true, source: node.Source.Value)); } VisitDefault(node); } public override void VisitCSharpExpression(CSharpExpressionIRNode node) { if (node.Source != null && !(node.Parent is CSharpAttributeValueIRNode)) { Items.Add(new InstrumentationItem(node, isLiteral: false, source: node.Source.Value)); } VisitDefault(node); } public override void VisitExecuteTagHelpers(ExecuteTagHelpersIRNode node) { // As a special case the TagHelperIRNode (which must be the parent) is the one that carries // the location. The execute node won't have one, but the instrumentation goes around the call // to execute. if (node.Parent is TagHelperIRNode && node.Parent.Source != null) { Items.Add(new InstrumentationItem(node, isLiteral: false, source: node.Parent.Source.Value)); } VisitDefault(node); } public override void VisitAddTagHelperHtmlAttribute(AddTagHelperHtmlAttributeIRNode node) { // We don't want to instrument TagHelper attributes. Do nothing. } public override void VisitSetTagHelperProperty(SetTagHelperPropertyIRNode node) { // We don't want to instrument TagHelper attributes. Do nothing. } } } }