|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language.Components;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Extensions;
// Optimization pass is the best choice for this class. It's not an optimization, but it also doesn't add semantically
// meaningful information.
internal sealed class MetadataAttributePass : IntermediateNodePassBase, IRazorOptimizationPass
{
private IMetadataIdentifierFeature? _identifierFeature;
protected override void OnInitialized()
{
Engine.TryGetFeature(out _identifierFeature);
Debug.Assert(_identifierFeature is not null);
}
protected override void ExecuteCore(
RazorCodeDocument codeDocument,
DocumentIntermediateNode documentNode,
CancellationToken cancellationToken)
{
if (documentNode.Options == null || documentNode.Options.SuppressMetadataAttributes)
{
// Metadata attributes are turned off (or options not populated), nothing to do.
return;
}
if (string.Equals(documentNode.DocumentKind, ComponentDocumentClassifierPass.ComponentDocumentKind, StringComparison.Ordinal))
{
// Metadata attributes are not used for components.
return;
}
// We need to be able to compute the data we need for the [RazorCompiledItem] attribute - that includes
// a full type name, and a document kind, and optionally an identifier.
//
// If we can't use [RazorCompiledItem] then we don't care about the rest of the attributes.
var @namespace = documentNode.FindPrimaryNamespace();
if (@namespace == null)
{
// No namespace node. Skip.
return;
}
var @class = documentNode.FindPrimaryClass();
if (@class == null || string.IsNullOrEmpty(@class.Name))
{
// No class node or it's incomplete. Skip.
return;
}
if (documentNode.DocumentKind == null)
{
// No document kind. Skip.
return;
}
var identifier = _identifierFeature?.GetIdentifier(codeDocument, codeDocument.Source);
if (identifier == null)
{
// No identifier. Skip
return;
}
// [RazorCompiledItem] is an [assembly: ... ] attribute, so it needs to be applied at the global scope.
documentNode.Children.Insert(0, new RazorCompiledItemAttributeIntermediateNode()
{
TypeName = string.IsNullOrEmpty(@namespace.Name)
? @class.Name
: @namespace.Name + "." + @class.Name,
Kind = documentNode.DocumentKind,
Identifier = identifier,
});
// Now we need to add a [RazorSourceChecksum] for the source and for each import
// these are class attributes, so we need to find the insertion point to put them
// right before the class.
var insert = (int?)null;
for (var j = 0; j < @namespace.Children.Count; j++)
{
if (object.ReferenceEquals(@namespace.Children[j], @class))
{
insert = j;
break;
}
}
if (insert == null)
{
// Can't find a place to put the attributes, just bail.
return;
}
if (documentNode.Options.SuppressMetadataSourceChecksumAttributes)
{
// Checksum attributes are turned off (or options not populated), nothing to do.
return;
}
// Checksum of the main source
var checksum = codeDocument.Source.Text.GetChecksum();
var checksumAlgorithm = codeDocument.Source.Text.ChecksumAlgorithm;
if (checksum == null || checksum.Length == 0 || checksumAlgorithm is CodeAnalysis.Text.SourceHashAlgorithm.None)
{
// Don't generate anything unless we have all of the required information.
return;
}
@namespace.Children.Insert((int)insert++, new RazorSourceChecksumAttributeIntermediateNode()
{
Checksum = checksum,
ChecksumAlgorithm = checksumAlgorithm,
Identifier = identifier,
});
// Now process the checksums of the imports
Debug.Assert(_identifierFeature != null);
foreach (var import in codeDocument.Imports)
{
checksum = import.Text.GetChecksum();
checksumAlgorithm = import.Text.ChecksumAlgorithm;
identifier = _identifierFeature.GetIdentifier(codeDocument, import);
if (checksum == null || checksum.Length == 0 || checksumAlgorithm == CodeAnalysis.Text.SourceHashAlgorithm.None || identifier == null)
{
// It's ok to skip an import if we don't have all of the required information.
continue;
}
@namespace.Children.Insert((int)insert++, new RazorSourceChecksumAttributeIntermediateNode()
{
Checksum = checksum,
ChecksumAlgorithm = checksumAlgorithm,
Identifier = identifier,
});
}
}
}
|