public record NamespaceResult()

in src/Bicep.Core/Semantics/Namespaces/NamespaceProvider.cs [17:201]


public record NamespaceResult(
    string Name,
    TypeSymbol Type,
    ExtensionDeclarationSyntax? Origin);

public class NamespaceProvider : INamespaceProvider
{
    private readonly IResourceTypeProviderFactory resourceTypeProviderFactory;

    public NamespaceProvider(IResourceTypeProviderFactory resourceTypeProviderFactory)
    {
        this.resourceTypeProviderFactory = resourceTypeProviderFactory;
    }

    public IEnumerable<NamespaceResult> GetNamespaces(
        IArtifactFileLookup artifactFileLookup,
        BicepSourceFile sourceFile,
        ResourceScope targetScope)
    {
        var extensions = SyntaxAggregator.AggregateByType<ExtensionDeclarationSyntax>(sourceFile.ProgramSyntax).ToImmutableArray();
        var implicitExtensions = artifactFileLookup.ImplicitExtensions[sourceFile].ToDictionary(x => x.Name, LanguageConstants.IdentifierComparer);

        if (implicitExtensions.TryGetValue(SystemNamespaceType.BuiltInName, out var sysProvider))
        {
            // TODO proper diag here
            var nsType = ErrorType.Create(DiagnosticBuilder.ForDocumentStart().ExtensionsAreDisabled());
            yield return new(sysProvider.Name, nsType, null);
        }

        var assignedProviders = new HashSet<string>(LanguageConstants.IdentifierComparer);
        foreach (var extension in extensions)
        {
            var type = GetNamespaceType(artifactFileLookup, sourceFile, targetScope, extension);
            if (type is NamespaceType validType)
            {
                assignedProviders.Add(validType.ExtensionName);
            }

            yield return new(extension.TryGetSymbolName() ?? type.Name, type, extension);
        }

        // sys isn't included in the implicit extensions config, because we don't want users to customize it.
        // for the purposes of this logic, it's simpler to treat it as if it is.
        implicitExtensions[SystemNamespaceType.BuiltInName] = new ImplicitExtension(SystemNamespaceType.BuiltInName, new("builtin:"), null);

        foreach (var (extensionName, implicitExtension) in implicitExtensions)
        {
            if (assignedProviders.Contains(extensionName))
            {
                // if an implicit extension has been explicitly registered in a file, it shouldn't also be registered as implicit
                continue;
            }

            var nsType = GetNamespaceTypeForImplicitExtension(sourceFile, targetScope, implicitExtension, null);
            yield return new(extensionName, nsType, null);
        }
    }

    private TypeSymbol GetNamespaceTypeForImplicitExtension(
        BicepSourceFile sourceFile,
        ResourceScope targetScope,
        ImplicitExtension extension,
        ExtensionDeclarationSyntax? syntax)
    {
        if (extension.Config is null)
        {
            return ErrorType.Create(DiagnosticBuilder.ForDocumentStart().InvalidExtension_ImplicitExtensionMissingConfig(sourceFile.Configuration.ConfigFileUri, extension.Name));
        }

        return GetNamespaceTypeForConfigManagedExtension(sourceFile, targetScope, extension.Artifact, syntax, extension.Name);
    }

    private TypeSymbol GetNamespaceType(
        IArtifactFileLookup artifactFileLookup,
        BicepSourceFile sourceFile,
        ResourceScope targetScope,
        ExtensionDeclarationSyntax syntax)
    {
        if (!sourceFile.Features.ExtensibilityEnabled)
        {
            return ErrorType.Create(DiagnosticBuilder.ForPosition(syntax).ExtensionsAreDisabled());
        }

        if (syntax.SpecificationString.IsSkipped)
        {
            // this will have raised a parsing diagnostic
            return ErrorType.Empty();
        }

        if (artifactFileLookup.ArtifactLookup.TryGetValue(syntax, out var artifact))
        {
            if (GetNamespaceTypeForArtifact(artifact, sourceFile, targetScope, syntax.TryGetSymbolName()).IsSuccess(out var namespaceType, out var errorBuilder))
            {
                return namespaceType;
            }

            return ErrorType.Create(errorBuilder(DiagnosticBuilder.ForPosition(syntax.SpecificationString)));
        }

        if (syntax.SpecificationString is not IdentifierSyntax identifier)
        {
            // this will have raised a parsing diagnostic
            return ErrorType.Empty();
        }

        return GetNamespaceTypeForConfigManagedExtension(sourceFile, targetScope, null, syntax, identifier.IdentifierName);
    }

    protected virtual TypeSymbol GetNamespaceTypeForConfigManagedExtension(
        BicepSourceFile sourceFile,
        ResourceScope targetScope,
        ArtifactResolutionInfo? artifact,
        ExtensionDeclarationSyntax? syntax,
        string extensionName)
    {
        var aliasName = syntax?.TryGetSymbolName() ?? extensionName;
        var diagBuilder = syntax is { } ? DiagnosticBuilder.ForPosition(syntax) : DiagnosticBuilder.ForDocumentStart();

        if (artifact is { })
        {
            // not a built-in extension
            if (GetNamespaceTypeForArtifact(artifact, sourceFile, targetScope, aliasName).IsSuccess(out var namespaceType, out var errorBuilder))
            {
                return namespaceType;
            }

            return ErrorType.Create(errorBuilder(diagBuilder));
        }

        // built-in extension
        if (LanguageConstants.IdentifierComparer.Equals(extensionName, SystemNamespaceType.BuiltInName))
        {
            return SystemNamespaceType.Create(aliasName, sourceFile.Features, sourceFile.FileKind);
        }

        if (LanguageConstants.IdentifierComparer.Equals(extensionName, AzNamespaceType.BuiltInName))
        {
            var typeProvider = targetScope switch
            {
                ResourceScope.Local => new EmptyResourceTypeProvider(),
                _ => resourceTypeProviderFactory.GetBuiltInAzResourceTypesProvider(),
            };

            return AzNamespaceType.Create(aliasName, targetScope, typeProvider, sourceFile.FileKind);
        }

        if (LanguageConstants.IdentifierComparer.Equals(extensionName, K8sNamespaceType.BuiltInName))
        {
            return K8sNamespaceType.Create(aliasName, sourceFile.Features);
        }

        // microsoftGraph built-in extension is no longer supported.
        if (LanguageConstants.IdentifierComparer.Equals(extensionName, MicrosoftGraphNamespaceType.BuiltInName))
        {
            return ErrorType.Create(diagBuilder.MicrosoftGraphBuiltinRetired(syntax));
        }

        return ErrorType.Create(diagBuilder.InvalidExtension_NotABuiltInExtension(sourceFile.Configuration.ConfigFileUri, extensionName));
    }

    private ResultWithDiagnosticBuilder<NamespaceType> GetNamespaceTypeForArtifact(ArtifactResolutionInfo artifact, BicepSourceFile sourceFile, ResourceScope targetScope, string? aliasName)
    {
        if (!artifact.Result.IsSuccess(out var typesTgzUri, out var errorBuilder))
        {
            return new(errorBuilder);
        }

        if (!resourceTypeProviderFactory.GetResourceTypeProvider(artifact.Reference, typesTgzUri).IsSuccess(out var typeProvider, out errorBuilder))
        {
            return new(errorBuilder);
        }

        if (typeProvider is AzResourceTypeProvider)
        {
            return new(AzNamespaceType.Create(aliasName, targetScope, typeProvider, sourceFile.FileKind));
        }

        if (typeProvider is MicrosoftGraphResourceTypeProvider)
        {
            return new(MicrosoftGraphNamespaceType.Create(aliasName, typeProvider, artifact.Reference));
        }

        return new(ThirdPartyNamespaceType.Create(aliasName, typeProvider, artifact.Reference, sourceFile.Features));
    }
}