|
// 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.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
[assembly: System.Resources.NeutralResourcesLanguage("en-US")]
namespace Microsoft.Interop.JavaScript
{
[Generator]
public sealed class JSImportGenerator : IIncrementalGenerator
{
internal sealed record IncrementalStubGenerationContext(
JSSignatureContext SignatureContext,
ContainingSyntaxContext ContainingSyntaxContext,
ContainingSyntax StubMethodSyntaxTemplate,
MethodSignatureDiagnosticLocations DiagnosticLocation,
JSImportData JSImportData,
SequenceEqualImmutableArray<DiagnosticInfo> Diagnostics);
public static class StepNames
{
public const string CalculateStubInformation = nameof(CalculateStubInformation);
public const string GenerateSingleStub = nameof(GenerateSingleStub);
}
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Collect all methods adorned with JSImportAttribute
var attributedMethods = context.SyntaxProvider
.ForAttributeWithMetadataName(Constants.JSImportAttribute,
static (node, ct) => node is MethodDeclarationSyntax,
static (context, ct) => new { Syntax = (MethodDeclarationSyntax)context.TargetNode, Symbol = (IMethodSymbol)context.TargetSymbol });
// Validate if attributed methods can have source generated
var methodsWithDiagnostics = attributedMethods.Select(static (data, ct) =>
{
Diagnostic? diagnostic = GetDiagnosticIfInvalidMethodForGeneration(data.Syntax, data.Symbol);
return new { Syntax = data.Syntax, Symbol = data.Symbol, Diagnostic = diagnostic };
});
var methodsToGenerate = methodsWithDiagnostics.Where(static data => data.Diagnostic is null);
var invalidMethodDiagnostics = methodsWithDiagnostics.Where(static data => data.Diagnostic is not null);
// Report diagnostics for invalid methods
context.RegisterSourceOutput(invalidMethodDiagnostics, static (context, invalidMethod) =>
{
context.ReportDiagnostic(invalidMethod.Diagnostic);
});
IncrementalValueProvider<StubEnvironment> stubEnvironment = context.CreateStubEnvironmentProvider();
// Validate environment that is being used to generate stubs.
context.RegisterDiagnostics(stubEnvironment.Combine(attributedMethods.Collect()).SelectMany((data, ct) =>
{
if (data.Right.IsEmpty // no attributed methods
|| data.Left.Compilation.Options is CSharpCompilationOptions { AllowUnsafe: true }) // Unsafe code enabled
{
return ImmutableArray<DiagnosticInfo>.Empty;
}
return ImmutableArray.Create(DiagnosticInfo.Create(GeneratorDiagnostics.JSImportRequiresAllowUnsafeBlocks, null));
}));
IncrementalValuesProvider<(MemberDeclarationSyntax, ImmutableArray<DiagnosticInfo>)> generateSingleStub = methodsToGenerate
.Combine(stubEnvironment)
.Select(static (data, ct) => new
{
data.Left.Syntax,
data.Left.Symbol,
Environment = data.Right,
})
.Select(
static (data, ct) => CalculateStubInformation(data.Syntax, data.Symbol, data.Environment, ct)
)
.WithTrackingName(StepNames.CalculateStubInformation)
.Select(
static (data, ct) => GenerateSource(data)
)
.WithComparer(Comparers.GeneratedSyntax)
.WithTrackingName(StepNames.GenerateSingleStub);
context.RegisterDiagnostics(generateSingleStub.SelectMany((stubInfo, ct) => stubInfo.Item2));
context.RegisterConcatenatedSyntaxOutputs(generateSingleStub.Select((data, ct) => data.Item1), "JSImports.g.cs");
}
private static SyntaxTokenList StripTriviaFromModifiers(SyntaxTokenList tokenList)
{
SyntaxToken[] strippedTokens = new SyntaxToken[tokenList.Count];
for (int i = 0; i < tokenList.Count; i++)
{
strippedTokens[i] = tokenList[i].WithoutTrivia();
}
return new SyntaxTokenList(strippedTokens);
}
private static MemberDeclarationSyntax PrintGeneratedSource(
ContainingSyntax userDeclaredMethod,
JSSignatureContext stub,
ContainingSyntaxContext containingSyntaxContext,
BlockSyntax stubCode)
{
// Create stub function
MethodDeclarationSyntax stubMethod = MethodDeclaration(stub.SignatureContext.StubReturnType, userDeclaredMethod.Identifier)
.AddAttributeLists(stub.SignatureContext.AdditionalAttributes.ToArray())
.WithAttributeLists(SingletonList(AttributeList(SingletonSeparatedList(
Attribute(IdentifierName(Constants.DebuggerNonUserCodeAttribute))))))
.WithModifiers(StripTriviaFromModifiers(userDeclaredMethod.Modifiers))
.WithParameterList(ParameterList(SeparatedList(stub.SignatureContext.StubParameters)))
.WithBody(stubCode);
FieldDeclarationSyntax sigField = FieldDeclaration(VariableDeclaration(IdentifierName(Constants.JSFunctionSignatureGlobal))
.WithVariables(SingletonSeparatedList(VariableDeclarator(Identifier(stub.BindingName)))))
.AddModifiers(Token(SyntaxKind.StaticKeyword));
MemberDeclarationSyntax toPrint = containingSyntaxContext.WrapMembersInContainingSyntaxWithUnsafeModifier(stubMethod, sigField);
return toPrint;
}
private static JSImportData? ProcessJSImportAttribute(AttributeData attrData)
{
// Found the JSImport, but it has an error so report the error.
// This is most likely an issue with targeting an incorrect TFM.
if (attrData.AttributeClass?.TypeKind is null or TypeKind.Error)
{
return null;
}
if (attrData.ConstructorArguments.Length == 1)
{
return new JSImportData(attrData.ConstructorArguments[0].Value!.ToString(), null);
}
if (attrData.ConstructorArguments.Length == 2)
{
return new JSImportData(attrData.ConstructorArguments[0].Value!.ToString(), attrData.ConstructorArguments[1].Value!.ToString());
}
return null;
}
private static IncrementalStubGenerationContext CalculateStubInformation(
MethodDeclarationSyntax originalSyntax,
IMethodSymbol symbol,
StubEnvironment environment,
CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
// Get any attributes of interest on the method
AttributeData? jsImportAttr = null;
foreach (AttributeData attr in symbol.GetAttributes())
{
if (attr.AttributeClass is not null
&& attr.AttributeClass.ToDisplayString() == Constants.JSImportAttribute)
{
jsImportAttr = attr;
}
}
Debug.Assert(jsImportAttr is not null);
var locations = new MethodSignatureDiagnosticLocations(originalSyntax);
var generatorDiagnostics = new GeneratorDiagnosticsBag(new DescriptorProvider(), locations, SR.ResourceManager, typeof(FxResources.Microsoft.Interop.JavaScript.JSImportGenerator.SR));
// Process the JSImport attribute
JSImportData? jsImportData = ProcessJSImportAttribute(jsImportAttr!);
if (jsImportData is null)
{
generatorDiagnostics.ReportConfigurationNotSupported(jsImportAttr!, "Invalid syntax");
jsImportData = new JSImportData("INVALID_CSHARP_SYNTAX", null);
}
// Create the stub.
var signatureContext = JSSignatureContext.Create(symbol, environment, generatorDiagnostics, ct);
var containingTypeContext = new ContainingSyntaxContext(originalSyntax);
var methodSyntaxTemplate = new ContainingSyntax(originalSyntax.Modifiers, SyntaxKind.MethodDeclaration, originalSyntax.Identifier, originalSyntax.TypeParameterList);
return new IncrementalStubGenerationContext(
signatureContext,
containingTypeContext,
methodSyntaxTemplate,
locations,
jsImportData,
new SequenceEqualImmutableArray<DiagnosticInfo>(generatorDiagnostics.Diagnostics.ToImmutableArray()));
}
private static (MemberDeclarationSyntax, ImmutableArray<DiagnosticInfo>) GenerateSource(
IncrementalStubGenerationContext incrementalContext)
{
var diagnostics = new GeneratorDiagnosticsBag(new DescriptorProvider(), incrementalContext.DiagnosticLocation, SR.ResourceManager, typeof(FxResources.Microsoft.Interop.JavaScript.JSImportGenerator.SR));
// Generate stub code
var stubGenerator = new JSImportCodeGenerator(
incrementalContext.SignatureContext.SignatureContext.ElementTypeInformation,
incrementalContext.JSImportData,
incrementalContext.SignatureContext,
diagnostics,
new JSGeneratorResolver());
BlockSyntax code = stubGenerator.GenerateJSImportBody();
return (PrintGeneratedSource(incrementalContext.StubMethodSyntaxTemplate, incrementalContext.SignatureContext, incrementalContext.ContainingSyntaxContext, code), incrementalContext.Diagnostics.Array.AddRange(diagnostics.Diagnostics));
}
private static Diagnostic? GetDiagnosticIfInvalidMethodForGeneration(MethodDeclarationSyntax methodSyntax, IMethodSymbol method)
{
// Verify the method has no generic types or defined implementation
// and is marked static and partial.
if (methodSyntax.TypeParameterList is not null
|| methodSyntax.Body is not null
|| !methodSyntax.Modifiers.Any(SyntaxKind.StaticKeyword)
|| !methodSyntax.Modifiers.Any(SyntaxKind.PartialKeyword))
{
return Diagnostic.Create(GeneratorDiagnostics.InvalidImportAttributedMethodSignature, methodSyntax.Identifier.GetLocation(), method.Name);
}
// Verify that the types the method is declared in are marked partial.
for (SyntaxNode? parentNode = methodSyntax.Parent; parentNode is TypeDeclarationSyntax typeDecl; parentNode = parentNode.Parent)
{
if (!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
{
return Diagnostic.Create(GeneratorDiagnostics.InvalidImportAttributedMethodContainingTypeMissingModifiers, methodSyntax.Identifier.GetLocation(), method.Name, typeDecl.Identifier);
}
}
// Verify the method does not have a ref return
if (method.ReturnsByRef || method.ReturnsByRefReadonly)
{
return Diagnostic.Create(GeneratorDiagnostics.ReturnConfigurationNotSupported, methodSyntax.Identifier.GetLocation(), "ref return", method.ToDisplayString());
}
return null;
}
}
}
|