feat(workflow): analyzer recognizes both StellaOps and Ablera workflow namespaces

The vendored copy of StellaOps.Workflow in Serdica uses a parallel
namespace (Ablera.Serdica.Workflow.Abstractions). The analyzer now
looks up well-known types in both namespaces and treats both
assembly-name prefixes (StellaOps.Workflow.* and
Ablera.Serdica.Workflow.*) as trusted leaves.

Activation still requires the Abstractions assembly to be in the
compilation; absent either namespace's IDeclarativeWorkflow<T>, the
analyzer is a no-op.

20/20 analyzer tests still pass.
This commit is contained in:
master
2026-04-15 08:55:39 +03:00
parent 4ec9e55707
commit 25ea70e080

View File

@@ -1,17 +1,35 @@
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
namespace StellaOps.Workflow.Analyzer;
internal sealed class WorkflowWellKnownTypes
{
public const string TrustedAssemblyPrefix = "StellaOps.Workflow";
private static readonly string[] TrustedAssemblyPrefixes =
{
"StellaOps.Workflow",
"Ablera.Serdica.Workflow",
};
public INamedTypeSymbol DeclarativeWorkflowOfT { get; }
public INamedTypeSymbol WorkflowSpecOfT { get; }
private static readonly string[] DeclarativeWorkflowMetadataNames =
{
"StellaOps.Workflow.Abstractions.IDeclarativeWorkflow`1",
"Ablera.Serdica.Workflow.Abstractions.IDeclarativeWorkflow`1",
};
private static readonly string[] WorkflowSpecMetadataNames =
{
"StellaOps.Workflow.Abstractions.WorkflowSpec`1",
"Ablera.Serdica.Workflow.Abstractions.WorkflowSpec`1",
};
public ImmutableArray<INamedTypeSymbol> DeclarativeWorkflowOfT { get; }
public ImmutableArray<INamedTypeSymbol> WorkflowSpecOfT { get; }
private WorkflowWellKnownTypes(
INamedTypeSymbol declarativeWorkflowOfT,
INamedTypeSymbol workflowSpecOfT)
ImmutableArray<INamedTypeSymbol> declarativeWorkflowOfT,
ImmutableArray<INamedTypeSymbol> workflowSpecOfT)
{
DeclarativeWorkflowOfT = declarativeWorkflowOfT;
WorkflowSpecOfT = workflowSpecOfT;
@@ -19,27 +37,53 @@ internal sealed class WorkflowWellKnownTypes
public static WorkflowWellKnownTypes? TryResolve(Compilation compilation)
{
var declarativeWorkflowOfT = compilation.GetTypeByMetadataName(
"StellaOps.Workflow.Abstractions.IDeclarativeWorkflow`1");
var workflowSpecOfT = compilation.GetTypeByMetadataName(
"StellaOps.Workflow.Abstractions.WorkflowSpec`1");
if (declarativeWorkflowOfT is null || workflowSpecOfT is null)
var declarativeBuilder = ImmutableArray.CreateBuilder<INamedTypeSymbol>();
foreach (var name in DeclarativeWorkflowMetadataNames)
{
var t = compilation.GetTypeByMetadataName(name);
if (t is not null)
{
declarativeBuilder.Add(t);
}
}
if (declarativeBuilder.Count == 0)
{
return null;
}
return new WorkflowWellKnownTypes(declarativeWorkflowOfT, workflowSpecOfT);
var specBuilder = ImmutableArray.CreateBuilder<INamedTypeSymbol>();
foreach (var name in WorkflowSpecMetadataNames)
{
var t = compilation.GetTypeByMetadataName(name);
if (t is not null)
{
specBuilder.Add(t);
}
}
if (specBuilder.Count == 0)
{
return null;
}
return new WorkflowWellKnownTypes(
declarativeBuilder.ToImmutable(),
specBuilder.ToImmutable());
}
public bool IsDeclarativeWorkflowImplementation(INamedTypeSymbol type)
{
foreach (var iface in type.AllInterfaces)
{
if (iface.IsGenericType &&
SymbolEqualityComparer.Default.Equals(
iface.ConstructedFrom, DeclarativeWorkflowOfT))
if (!iface.IsGenericType)
{
return true;
continue;
}
foreach (var knownIface in DeclarativeWorkflowOfT)
{
if (SymbolEqualityComparer.Default.Equals(iface.ConstructedFrom, knownIface))
{
return true;
}
}
}
return false;
@@ -47,15 +91,18 @@ internal sealed class WorkflowWellKnownTypes
public bool IsSpecProperty(IPropertySymbol property)
{
if (property.Type is not INamedTypeSymbol typed)
if (property.Type is not INamedTypeSymbol typed || !typed.IsGenericType)
{
return false;
}
if (!typed.IsGenericType)
foreach (var knownSpec in WorkflowSpecOfT)
{
return false;
if (SymbolEqualityComparer.Default.Equals(typed.ConstructedFrom, knownSpec))
{
return true;
}
}
return SymbolEqualityComparer.Default.Equals(typed.ConstructedFrom, WorkflowSpecOfT);
return false;
}
public static bool IsTrustedAssembly(IAssemblySymbol? assembly)
@@ -65,8 +112,18 @@ internal sealed class WorkflowWellKnownTypes
return false;
}
var name = assembly.Name;
return name != null &&
(name.Equals(TrustedAssemblyPrefix, System.StringComparison.Ordinal) ||
name.StartsWith(TrustedAssemblyPrefix + ".", System.StringComparison.Ordinal));
if (string.IsNullOrEmpty(name))
{
return false;
}
foreach (var prefix in TrustedAssemblyPrefixes)
{
if (name.Equals(prefix, StringComparison.Ordinal) ||
name.StartsWith(prefix + ".", StringComparison.Ordinal))
{
return true;
}
}
return false;
}
}