File: Workspace\MiscellaneousFileUtilities.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Scripting.Hosting;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Features.Workspaces;
 
internal static class MiscellaneousFileUtilities
{
    internal static ProjectInfo CreateMiscellaneousProjectInfoForDocument(
        Workspace workspace,
        string filePath,
        TextLoader textLoader,
        LanguageInformation languageInformation,
        SourceHashAlgorithm checksumAlgorithm,
        SolutionServices services,
        ImmutableArray<MetadataReference> metadataReferences)
    {
        var fileExtension = PathUtilities.GetExtension(filePath);
        var fileName = PathUtilities.GetFileName(filePath);
 
        var languageServices = services.GetLanguageServices(languageInformation.LanguageName);
        var compilationOptions = languageServices.GetService<ICompilationFactoryService>()?.GetDefaultCompilationOptions();
 
        // Use latest language version which is more permissive, as we cannot find out language version of the project which the file belongs to
        // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/575761
        var parseOptions = languageServices.GetService<ISyntaxTreeFactoryService>()?.GetDefaultParseOptionsWithLatestLanguageVersion();
 
        if (parseOptions != null &&
            compilationOptions != null &&
            fileExtension == languageInformation.ScriptExtension)
        {
            parseOptions = parseOptions.WithKind(SourceCodeKind.Script);
            compilationOptions = GetCompilationOptionsWithScriptReferenceResolvers(services, compilationOptions, filePath);
        }
 
        var projectId = ProjectId.CreateNewId(debugName: $"{workspace.GetType().Name} Files Project for {filePath}");
        var documentId = DocumentId.CreateNewId(projectId, debugName: filePath);
 
        var sourceCodeKind = parseOptions?.Kind ?? SourceCodeKind.Regular;
        var documentInfo = DocumentInfo.Create(
            id: documentId,
            name: fileName,
            loader: textLoader,
            filePath: filePath,
            sourceCodeKind: sourceCodeKind);
 
        // The assembly name must be unique for each collection of loose files. Since the name doesn't matter
        // a random GUID can be used.
        var assemblyName = Guid.NewGuid().ToString("N");
 
        var projectInfo = ProjectInfo.Create(
            new ProjectInfo.ProjectAttributes(
                id: projectId,
                version: VersionStamp.Create(),
                name: FeaturesResources.Miscellaneous_Files,
                assemblyName: assemblyName,
                language: languageInformation.LanguageName,
                compilationOutputInfo: default,
                checksumAlgorithm: checksumAlgorithm,
                // Miscellaneous files projects are never fully loaded since, by definition, it won't know
                // what the full set of information is except when the file is script code.
                hasAllInformation: sourceCodeKind == SourceCodeKind.Script),
            compilationOptions: compilationOptions,
            parseOptions: parseOptions,
            documents: [documentInfo],
            metadataReferences: metadataReferences);
 
        return projectInfo;
    }
 
    // Do not inline this to avoid loading Microsoft.CodeAnalysis.Scripting unless a script file is opened in the workspace.
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static CompilationOptions GetCompilationOptionsWithScriptReferenceResolvers(SolutionServices services, CompilationOptions compilationOptions, string filePath)
    {
        var metadataService = services.GetRequiredService<IMetadataService>();
 
        var baseDirectory = PathUtilities.GetDirectoryName(filePath);
 
        // TODO (https://github.com/dotnet/roslyn/issues/5325, https://github.com/dotnet/roslyn/issues/13886):
        // - Need to have a way to specify these somewhere in VS options.
        // - Add default namespace imports, default metadata references to match csi.rsp
        // - Add default script globals available in 'csi goo.csx' environment: CommandLineScriptGlobals
 
        var referenceResolver = RuntimeMetadataReferenceResolver.CreateCurrentPlatformResolver(
            searchPaths: [RuntimeEnvironment.GetRuntimeDirectory()],
            baseDirectory: baseDirectory,
            createFromFileFunc: metadataService.GetReference);
 
        return compilationOptions
            .WithMetadataReferenceResolver(referenceResolver)
            .WithSourceReferenceResolver(new SourceFileResolver(searchPaths: [], baseDirectory));
    }
}
 
internal sealed class LanguageInformation(string languageName, string scriptExtension)
{
    public string LanguageName { get; } = languageName;
    public string ScriptExtension { get; } = scriptExtension;
}