// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// cspell:ignore evnt
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.ReactNative.Managed.CodeGen.Model;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.ContractsLight;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
namespace Microsoft.ReactNative.Managed.CodeGen
{
///
/// Primary class
///
public class CodeAnalyzer
{
public int ConcurrencyLevel { get; set; } = DataflowBlockOptions.Unbounded;
private readonly CancellationToken m_cancellationToken;
private readonly ConcurrentBag m_syntaxTrees = new ConcurrentBag();
private readonly ConcurrentBag m_metadataReferences = new ConcurrentBag();
private Compilation? m_compilation { get; set; }
private ReactTypes? m_reactTypes;
internal ReactTypes ReactTypes
{
get
{
Contract.Assert(m_reactTypes != null);
return m_reactTypes;
}
}
private readonly List m_diagnostics = new List();
public IEnumerable Diagnostics => m_diagnostics;
public CodeAnalyzer(CancellationToken cancellationToken)
{
m_cancellationToken = cancellationToken;
}
internal void AddSyntaxTree(SyntaxTree syntaxTree)
{
m_syntaxTrees.Add(syntaxTree);
}
public async Task ParseSourceFilesAsync(IEnumerable sourceFiles, IEnumerable? preprocessorSymbols)
{
var csharpParseOptions = new CSharpParseOptions(
preprocessorSymbols: preprocessorSymbols,
languageVersion: LanguageVersion.Latest);
var block = new ActionBlock(async file =>
{
string text = await File.ReadAllTextAsync(file, m_cancellationToken);
AddSyntaxTree(CSharpSyntaxTree.ParseText(text, path: file, options: csharpParseOptions));
},
new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = ConcurrencyLevel,
CancellationToken = m_cancellationToken,
});
foreach (var sourceFile in sourceFiles)
{
block.Post(sourceFile);
}
block.Complete();
await block.Completion;
}
public async Task LoadMetadataReferencesAsync(IEnumerable references)
{
var block = new ActionBlock(file =>
{
m_metadataReferences.Add(MetadataReference.CreateFromFile(file));
},
new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = ConcurrencyLevel,
CancellationToken = m_cancellationToken,
});
foreach (var reference in references.Distinct())
{
block.Post(reference);
}
block.Complete();
await block.Completion;
}
public bool TryCompileAndCheckForErrors()
{
Contract.Assert(m_syntaxTrees.Any(), "Expected to have syntax trees loaded");
Contract.Assert(m_metadataReferences.Any(), "Expected to have references loaded");
m_compilation = CSharpCompilation.Create(
"temp",
m_syntaxTrees,
m_metadataReferences,
new CSharpCompilationOptions(OutputKind.WindowsRuntimeMetadata)
);
Contract.Assert(m_compilation != null, "Expect Microsoft.Net.Compilers to always return a compilation unit.");
m_diagnostics.AddRange(m_compilation
.GetDiagnostics(m_cancellationToken)
.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
);
if (m_diagnostics.Count > 0)
{
return false;
}
return ReactTypes.TryLoad(m_compilation, m_diagnostics, out m_reactTypes);
}
internal IEnumerable GetAllTypes()
{
Contract.Assert(m_compilation != null, "Expected to have a compilation. Must call CompileAndCheckForErrors first");
var allTypes = new List();
CollectTypes(m_compilation.Assembly.GlobalNamespace, allTypes);
return allTypes;
}
public ReactAssembly AnalyzeAndConstructAssemblyModel()
{
Contract.Assert(m_compilation != null, "Expected to have a compilation. Must call CompileAndCheckForErrors first");
var assembly = new ReactAssembly();
var processedTypes = new HashSet(SymbolEqualityComparer.Default);
foreach (var type in GetAllTypes())
{
if (TryExtractModule(type, out var module))
{
assembly.Modules.Add(module);
}
// Proactively register serializable types.
switch (type.TypeKind)
{
case TypeKind.Enum:
assembly.SerializableTypes.Add(type, type.TypeKind);
break;
}
// Register extension methods
if (type.MightContainExtensionMethods)
{
foreach (var member in type.GetMembers())
{
if (member is IMethodSymbol method)
{
if (method.IsExtensionMethod &&
method.Parameters.Length == 2 &&
!(method.Parameters[1].Type is ITypeParameterSymbol))
{
if (method.Parameters[0].Type.Equals(ReactTypes.JSValueReader, SymbolEqualityComparer.Default) &&
method.Parameters[1].RefKind == RefKind.Out)
{
assembly.JSReaderFunctions.Add(method.Parameters[1].Type, method);
}
else if (method.Parameters[0].Type.Equals(ReactTypes.JSValueWriter, SymbolEqualityComparer.Default))
{
assembly.JSWriterFunctions.Add(method.Parameters[1].Type, method);
}
}
}
}
}
}
foreach (var module in assembly.Modules)
{
foreach (var method in module.Methods)
{
AddSerializableMethod(method.Method);
}
foreach (var getConstants in module.GetConstants)
{
AddSerializableType(getConstants.Method.ReturnType);
}
foreach (var constant in module.Constants)
{
if (constant.Symbol is IFieldSymbol field)
{
AddSerializableType(field.Type);
}
else if (constant.Symbol is IPropertySymbol property)
{
AddSerializableType(property.Type);
}
}
foreach (var callback in module.Events.Union(module.Functions))
{
foreach (var param in callback.CallbackParameters)
{
AddSerializableType(param.Type);
}
}
}
void AddSerializableMethod(IMethodSymbol method)
{
AddSerializableType(method.ReturnType);
foreach (var param in method.Parameters)
{
AddSerializableType(param.Type);
}
}
void AddSerializableType(ITypeSymbol type)
{
if (processedTypes.Contains(type))
{
return;
}
processedTypes.Add(type);
if (type is INamedTypeSymbol namedType)
{
if (type.TypeKind == TypeKind.Delegate)
{
Contract.Assert(namedType.DelegateInvokeMethod != null);
AddSerializableMethod(namedType.DelegateInvokeMethod);
}
else if (type.ContainingAssembly.Equals(m_compilation.Assembly, SymbolEqualityComparer.Default))
{
if (namedType.TypeKind == TypeKind.Class)
{
// Check that classes have a default constructor.
if (!namedType
.GetMembers()
.Any(member =>
member is IMethodSymbol method &&
method.MethodKind == MethodKind.Constructor &&
method.Parameters.Length == 0))
{
}
}
assembly.SerializableTypes.Add(namedType, namedType.TypeKind);
}
}
}
return assembly;
}
internal bool TryExtractModule(INamedTypeSymbol type, [NotNullWhen(returnValue: true)] out ReactModule? module)
{
if (TryFindAttribute(type, ReactTypes.ReactModuleAttribute, out var attr))
{
string? moduleName = null;
string? eventEmitterName = null;
if (attr.ConstructorArguments.Length > 0)
{
moduleName = attr.ConstructorArguments[0].Value as string;
}
foreach (var namedArgument in attr.NamedArguments)
{
switch (namedArgument.Key)
{
case nameof(ReactModuleAttribute.ModuleName):
moduleName = namedArgument.Value.Value as string;
break;
case nameof(ReactModuleAttribute.EventEmitterName):
eventEmitterName = namedArgument.Value.Value as string;
break;
default:
var location = attr.ApplicationSyntaxReference?.SyntaxTree.GetLocation(attr.ApplicationSyntaxReference.Span);
m_diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.UnexpectedPropertyInAttribute, location ?? Location.None, namedArgument.Key, attr.AttributeClass?.Name));
module = null;
return false;
}
}
moduleName ??= type.Name;
eventEmitterName ??= ReactNativeNames.DefaultEventEmitterName;
module = new ReactModule(type, moduleName, eventEmitterName);
foreach (var member in type.GetMembers())
{
if (member is IMethodSymbol method)
{
if (TryExtractMethod(method, out var asyncMethod))
{
module.Methods.Add(asyncMethod);
}
else if (TryExtractSyncMethod(method, out var syncMethod))
{
module.Methods.Add(syncMethod);
}
else if (TryExtractInitializer(method, out var initializer))
{
module.Initializers.Add(initializer);
}
else if (TryExtractConstantProvider(method, out var constantProvider))
{
module.ConstantProviders.Add(constantProvider);
}
else if (TryExtractGetConstants(method, out var getConstants))
{
module.GetConstants.Add(getConstants);
}
}
else if (member is IPropertySymbol || member is IFieldSymbol)
{
if (TryExtractConstant(member, out var constant))
{
module.Constants.Add(constant);
}
else if (TryExtractEvent(member, eventEmitterName, out var evnt))
{
module.Events.Add(evnt);
}
else if (TryExtractFunction(member, moduleName, out var function))
{
module.Functions.Add(function);
}
}
}
return true;
}
module = null;
return false;
}
private bool TryExtractMethod(IMethodSymbol method, [NotNullWhen(returnValue: true)] out ReactMethod? reactMethod)
{
return TryExtractMethodFromAttributeData(method, ReactTypes.ReactMethodAttribute, synchronous: false, out reactMethod);
}
private bool TryExtractSyncMethod(IMethodSymbol method, [NotNullWhen(returnValue: true)] out ReactMethod? reactMethod)
{
return TryExtractMethodFromAttributeData(method, ReactTypes.ReactSyncMethodAttribute, synchronous: true, out reactMethod);
}
private bool TryExtractMethodFromAttributeData(
IMethodSymbol method,
INamedTypeSymbol attributeType,
bool synchronous,
[NotNullWhen(returnValue: true)] out ReactMethod? reactMethod)
{
Contract.Requires(m_reactTypes != null);
if (TryFindAttribute(method, attributeType, out var attr))
{
string? name = null;
if (attr.ConstructorArguments.Length > 0)
{
name = attr.ConstructorArguments[0].Value as string;
}
foreach (var namedArgument in attr.NamedArguments)
{
switch (namedArgument.Key)
{
case nameof(ReactMethodAttribute.MethodName):
name = namedArgument.Value.Value as string;
break;
default:
var location = attr.ApplicationSyntaxReference?.SyntaxTree.GetLocation(attr.ApplicationSyntaxReference.Span);
m_diagnostics.Add(Diagnostic.Create(
DiagnosticDescriptors.UnexpectedPropertyInAttribute,
location ?? Location.None,
namedArgument.Key,
attr.AttributeClass?.Name));
reactMethod = null;
return false;
}
}
ReactMethod.MethodReturnStyle? returnStyle = null;
// Compute the effective parameters
var effectiveParameters = new List();
ITypeSymbol effectiveReturnType = m_reactTypes.SystemVoid;
int resolveParameterCount = 0;
int rejectParameterCount = 0;
var parameters = method.Parameters;
for (int i = 0; i < parameters.Length; ++i)
{
var param = parameters[i];
if (!synchronous) // IsSynchronous methods don't have support for the callbacks
{
if (i == parameters.Length - 1)
{
if (IsPromiseType(parameters[i].Type, out var promiseGenericType))
{
returnStyle = ReactMethod.MethodReturnStyle.Promise;
effectiveReturnType = promiseGenericType;
break;
}
// if the last argument is a callback, assume this is the resolve function
if (IsCallback(parameters[i].Type, out resolveParameterCount))
{
returnStyle = ReactMethod.MethodReturnStyle.Callback;
break;
}
}
else if (i == parameters.Length - 2)
{
// if the last 2 argument are callbacks, assume these are the resolve and reject functions
if (IsCallback(parameters[i].Type, out resolveParameterCount) &&
IsCallback(parameters[i + 1].Type, out rejectParameterCount))
{
returnStyle = ReactMethod.MethodReturnStyle.TwoCallbacks;
break;
}
}
}
effectiveParameters.Add(param);
}
if (returnStyle == null)
{
if (IsTaskType(method.ReturnType, out var taskReturnType))
{
if (synchronous)
{
m_diagnostics.Add(Diagnostic.Create(
DiagnosticDescriptors.MethodShouldNotReturnTaskWhenSynchronous,
method.GetLocation() ?? Location.None,
method.Name,
attr.AttributeClass?.Name));
reactMethod = null;
return false;
}
effectiveReturnType = taskReturnType;
returnStyle = ReactMethod.MethodReturnStyle.Task;
}
else if (method.ReturnsVoid)
{
returnStyle = ReactMethod.MethodReturnStyle.Void;
}
else
{
returnStyle = ReactMethod.MethodReturnStyle.ReturnValue;
}
}
name ??= method.Name;
reactMethod = new ReactMethod(method, name, returnStyle.Value, effectiveReturnType,
effectiveParameters, synchronous, resolveParameterCount, rejectParameterCount);
return true;
}
reactMethod = null;
return false;
}
private bool TryExtractConstantProvider(IMethodSymbol method, [NotNullWhen(returnValue: true)] out ReactConstantProvider? constantProvider)
{
constantProvider = null;
if (TryFindAttribute(method, ReactTypes.ReactConstantProviderAttribute, out _))
{
if (!IsAccessible(method))
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.ConstantProviderMustBePublicOrInternal,
method.GetLocation(),
method.ReturnType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
method.Name,
method.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
method.DeclaredAccessibility
));
return false;
}
if (!method.ReturnsVoid)
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.ConstantProviderMustReturnVoid,
method.GetLocation(),
method.ReturnType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
method.Name,
method.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)
));
return false;
}
if (method.Parameters.Length != 1 || !method.Parameters[0].Type.Equals(ReactTypes.ReactConstantProvider, SymbolEqualityComparer.Default))
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.ConstantProviderMustHaveOneParameterOfTypeReactContext,
method.GetLocation(),
ReactTypes.ReactConstantProvider.ToDisplayParts(SymbolDisplayFormat.FullyQualifiedFormat),
method.Parameters.Length.ToString(CultureInfo.InvariantCulture),
method.Name,
method.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)
));
return false;
}
constantProvider = new ReactConstantProvider(method);
return true;
}
constantProvider = null;
return false;
}
private bool TryExtractGetConstants(IMethodSymbol method, [NotNullWhen(returnValue: true)] out ReactGetConstants? getConstants)
{
getConstants = null;
if (TryFindAttribute(method, ReactTypes.ReactGetConstantsAttribute, out _))
{
if (!IsAccessible(method))
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.GetConstantsMustBePublicOrInternal,
method.GetLocation(),
method.ReturnType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
method.Name,
method.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
method.DeclaredAccessibility
));
return false;
}
if (method.ReturnsVoid)
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.GetConstantsMustNotReturnVoid,
method.GetLocation(),
method.ReturnType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
method.Name,
method.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)
));
return false;
}
if (method.ReturnType.SpecialType != SpecialType.None
|| (method.ReturnType.TypeKind != TypeKind.Struct && method.ReturnType.TypeKind != TypeKind.Class))
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.GetConstantsMustReturnStructOrClass,
method.GetLocation(),
method.ReturnType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
method.Name,
method.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)
));
return false;
}
if (method.Parameters.Length != 0)
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.GetConstantsMustNotHaveParameters,
method.GetLocation(),
method.Parameters.Length.ToString(CultureInfo.InvariantCulture),
method.Name,
method.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)
));
return false;
}
getConstants = new ReactGetConstants(method);
}
return getConstants != null;
}
private bool TryExtractInitializer(IMethodSymbol method, [NotNullWhen(returnValue: true)] out ReactInitializer? initializer)
{
initializer = null;
if (TryFindAttribute(method, ReactTypes.ReactInitializerAttribute, out _))
{
if (!IsAccessible(method))
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.InitializersMustBePublicOrInternal,
method.GetLocation(),
method.ReturnType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
method.Name,
method.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
method.DeclaredAccessibility
));
return false;
}
if (!method.ReturnsVoid)
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.InitializersMustReturnVoid,
method.GetLocation(),
method.ReturnType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
method.Name,
method.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)
));
return false;
}
if (method.Parameters.Length != 1 || !method.Parameters[0].Type.Equals(ReactTypes.ReactContext, SymbolEqualityComparer.Default))
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.InitializersMustHaveOneParameterOfTypeReactContext,
method.GetLocation(),
ReactTypes.ReactContext.ToDisplayParts(SymbolDisplayFormat.FullyQualifiedFormat),
method.Parameters.Length.ToString(CultureInfo.InvariantCulture),
method.Name,
method.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)
));
return false;
}
initializer = new ReactInitializer(method);
return true;
}
return false;
}
private bool TryExtractConstant(ISymbol symbol, [NotNullWhen(returnValue: true)] out ReactConstant? constant)
{
if (TryFindAttribute(symbol, ReactTypes.ReactConstantAttribute, out var attr))
{
string? name = null;
if (attr.ConstructorArguments.Length > 0)
{
name = attr.ConstructorArguments[0].Value as string;
}
foreach (var namedArgument in attr.NamedArguments)
{
switch (namedArgument.Key)
{
case nameof(ReactConstantAttribute.ConstantName):
name = namedArgument.Value.Value as string;
break;
default:
var location = attr.ApplicationSyntaxReference?.SyntaxTree.GetLocation(attr.ApplicationSyntaxReference.Span);
m_diagnostics.Add(Diagnostic.Create(
DiagnosticDescriptors.UnexpectedPropertyInAttribute,
location ?? Location.None,
namedArgument.Key,
attr.AttributeClass?.Name));
constant = null;
return false;
}
}
name ??= symbol.Name;
if (!IsAccessible(symbol))
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.ConstantsMustBePublicOrInternal,
symbol.GetLocation(),
symbol.Name,
symbol.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
symbol.DeclaredAccessibility
));
constant = null;
return false;
}
if (symbol is IPropertySymbol property && (property.GetMethod == null || !IsAccessible(property.GetMethod)))
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.ConstantsMustBeGettable,
symbol.GetLocation(),
property.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
symbol.ContainingType.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat)));
constant = null;
return false;
}
constant = new ReactConstant(symbol, name);
return true;
}
constant = null;
return false;
}
private bool TryExtractEvent(ISymbol symbol, string defaultEventEmitterName, [NotNullWhen(returnValue: true)] out ReactEvent? reactEvent)
{
if (TryExtractCallback(symbol, defaultEventEmitterName, ReactTypes.ReactEventAttribute, out var callbackParameters, out var name, out var declaredModuleName))
{
reactEvent = new ReactEvent(symbol, callbackParameters, name, declaredModuleName);
return true;
}
reactEvent = null;
return false;
}
private bool TryExtractFunction(ISymbol symbol, string moduleName, [NotNullWhen(returnValue: true)] out ReactFunction? function)
{
if (TryExtractCallback(symbol, moduleName, ReactTypes.ReactFunctionAttribute, out var callbackParameters, out var name, out var declaredModuleName))
{
function = new ReactFunction(symbol, callbackParameters, name, declaredModuleName);
return true;
}
function = null;
return false;
}
private bool TryExtractCallback(
ISymbol symbol,
string defaultCallbackContextName,
INamedTypeSymbol attribute,
out ImmutableArray callbackParameters,
[NotNullWhen(returnValue: true)] out string? name,
[NotNullWhen(returnValue: true)] out string? callbackContextName)
{
callbackParameters = default;
name = null;
callbackContextName = null;
if (TryFindAttribute(symbol, attribute, out var attr))
{
name = null;
callbackContextName = null;
if (attr.ConstructorArguments.Length > 0)
{
name = attr.ConstructorArguments[0].Value as string;
}
foreach (var namedArgument in attr.NamedArguments)
{
switch (namedArgument.Key)
{
case nameof(ReactFunctionAttribute.FunctionName):
case nameof(ReactEventAttribute.EventName):
name = namedArgument.Value.Value as string;
break;
case nameof(ReactFunctionAttribute.ModuleName):
case nameof(ReactEventAttribute.EventEmitterName):
callbackContextName = namedArgument.Value.Value as string;
break;
default:
var location = attr.ApplicationSyntaxReference?.SyntaxTree.GetLocation(attr.ApplicationSyntaxReference.Span);
m_diagnostics.Add(Diagnostic.Create(
DiagnosticDescriptors.UnexpectedPropertyInAttribute,
location ?? Location.None,
namedArgument.Key,
attr.AttributeClass?.Name));
return false;
}
}
ITypeSymbol? type = null;
switch (symbol)
{
case IPropertySymbol property:
type = property.Type;
if (property.SetMethod == null || !IsAccessible(property.SetMethod))
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.CallbacksMustBeSettable,
symbol.GetLocation(),
property.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
symbol.ContainingType.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat)));
return false;
}
break;
case IFieldSymbol field:
type = field.Type;
break;
default:
throw Contract.AssertFailure("Unsupported symbol");
}
if (!IsAccessible(symbol))
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.CallbackMustBePublicOrInternal,
symbol.GetLocation(),
symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
symbol.ContainingType.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat),
symbol.DeclaredAccessibility));
return false;
}
if (!(type is INamedTypeSymbol namedType) || namedType.DelegateInvokeMethod == null)
{
m_diagnostics.Add(
Diagnostic.Create(DiagnosticDescriptors.CallbackTypeMustBeDelegate,
symbol.GetLocation(),
type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
symbol.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat)));
return false;
}
if (!namedType.DelegateInvokeMethod.ReturnsVoid)
{
var location = symbol.Locations.FirstOrDefault();
m_diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.CallbackDelegateMustReturnVoid, location,
symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
symbol.ContainingType.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat),
namedType.DelegateInvokeMethod.ReturnType.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat)));
return false;
}
name ??= symbol.Name;
callbackContextName ??= defaultCallbackContextName;
callbackParameters = namedType.DelegateInvokeMethod.Parameters;
return true;
}
return false;
}
private static void CollectTypes(INamespaceSymbol namespaceSymbol, ICollection result)
{
foreach (var type in namespaceSymbol.GetTypeMembers())
{
CollectTypes(type, result);
}
foreach (var childNs in namespaceSymbol.GetNamespaceMembers())
{
CollectTypes(childNs, result);
}
}
private static void CollectTypes(INamedTypeSymbol type, ICollection result)
{
result.Add(type);
foreach (var nested in type.GetTypeMembers())
{
CollectTypes(nested, result);
}
}
private bool TryFindAttribute(ISymbol symbol, INamedTypeSymbol attributeType, [NotNullWhen(returnValue: true)] out AttributeData? attr)
{
attr = symbol.GetAttributes().FirstOrDefault(potentialMatch => potentialMatch.AttributeClass != null && potentialMatch.AttributeClass.Equals(attributeType, SymbolEqualityComparer.Default));
return attr != null;
}
private bool IsAccessible(ISymbol symbol)
{
return symbol.DeclaredAccessibility == Accessibility.Public ||
symbol.DeclaredAccessibility == Accessibility.Internal;
}
private bool IsPromiseType(ITypeSymbol type, [NotNullWhen(returnValue: true)] out ITypeSymbol? typeParameter)
{
Contract.Requires(m_reactTypes != null);
if (type != null && type is INamedTypeSymbol namedType)
{
if (namedType.IsGenericType)
{
if (namedType.ConstructUnboundGenericType().Equals(
m_reactTypes.IReactPromise.ConstructUnboundGenericType(), SymbolEqualityComparer.Default)
|| namedType.ConstructUnboundGenericType().Equals(
m_reactTypes.ReactPromise.ConstructUnboundGenericType(), SymbolEqualityComparer.Default))
{
typeParameter = namedType.TypeArguments[0];
return true;
}
}
else if (SymbolEqualityComparer.Default.Equals(namedType, m_reactTypes.ReactPromiseOfVoid))
{
typeParameter = m_reactTypes.SystemVoid;
return true;
}
}
typeParameter = null;
return false;
}
private bool IsTaskType(ITypeSymbol type, [NotNullWhen(returnValue: true)] out ITypeSymbol? taskReturnType)
{
Contract.Requires(m_reactTypes != null);
if (type.Equals(m_reactTypes.Task, SymbolEqualityComparer.Default))
{
taskReturnType = m_reactTypes.SystemVoid;
return true;
}
if (type != null &&
type is INamedTypeSymbol namedType &&
namedType.IsGenericType &&
namedType.ConstructUnboundGenericType()
.Equals(m_reactTypes.TaskOfT, SymbolEqualityComparer.Default))
{
taskReturnType = namedType.TypeArguments[0];
return true;
}
taskReturnType = null;
return false;
}
private static bool IsCallback(ITypeSymbol type, out int parameterCount)
{
if (type is INamedTypeSymbol namedType &&
namedType.DelegateInvokeMethod != null)
{
parameterCount = namedType.DelegateInvokeMethod.Parameters.Length;
return true;
}
parameterCount = 0;
return false;
}
}
}