diff --git a/eng/config/BannedSymbols.txt b/eng/config/BannedSymbols.txt index a3112c9c224c5..fab8af664bc9e 100644 --- a/eng/config/BannedSymbols.txt +++ b/eng/config/BannedSymbols.txt @@ -34,3 +34,4 @@ M:Microsoft.CodeAnalysis.Simplification.Simplifier.ReduceAsync(Microsoft.CodeAna M:Microsoft.CodeAnalysis.Simplification.Simplifier.ReduceAsync(Microsoft.CodeAnalysis.Document,Microsoft.CodeAnalysis.SyntaxAnnotation,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload that takes SimplifierOptions M:Microsoft.CodeAnalysis.Simplification.Simplifier.ReduceAsync(Microsoft.CodeAnalysis.Document,Microsoft.CodeAnalysis.Text.TextSpan,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload that takes SimplifierOptions M:Microsoft.CodeAnalysis.Simplification.Simplifier.ReduceAsync(Microsoft.CodeAnalysis.Document,System.Collections.Generic.IEnumerable{Microsoft.CodeAnalysis.Text.TextSpan},Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload that takes SimplifierOptions +M:Microsoft.CodeAnalysis.FileTextLoader.#ctor(System.String,System.Text.Encoding); Use overload with checksum algorithm \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index 22d36bd2cec07..d97e1f385446d 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -9,6 +9,8 @@ Microsoft.CodeAnalysis.CSharp.SyntaxKind.RequiredKeyword = 8447 -> Microsoft.Cod Microsoft.CodeAnalysis.CSharp.SyntaxKind.ScopedKeyword = 8448 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind Microsoft.CodeAnalysis.CSharp.SyntaxKind.FileKeyword = 8449 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind Microsoft.CodeAnalysis.CSharp.SyntaxKind.SingleLineRawStringLiteralToken = 8518 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind +static Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.Create(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode! root, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions? options, string? path, System.Text.Encoding? encoding) -> Microsoft.CodeAnalysis.SyntaxTree! +static Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.Create(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode! root, Microsoft.CodeAnalysis.Text.SourceHashAlgorithm checksumAlgorithm, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions? options = null, string? path = "", System.Text.Encoding? encoding = null) -> Microsoft.CodeAnalysis.SyntaxTree! static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ConstructorDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax! parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorInitializerSyntax? initializer, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax! body) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorDeclarationSyntax! *REMOVED*static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ConstructorDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax! parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorInitializerSyntax! initializer, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax! body) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorDeclarationSyntax! static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ConstructorDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax! parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorInitializerSyntax? initializer, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax? body, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorDeclarationSyntax! diff --git a/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxTree.cs b/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxTree.cs index 0310f5a0bc92a..e13c4f934e143 100644 --- a/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxTree.cs +++ b/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxTree.cs @@ -311,12 +311,9 @@ private void BuildPreprocessorStateChangeMap() /// /// Creates a new syntax tree from a syntax node. /// - public static SyntaxTree Create(CSharpSyntaxNode root, CSharpParseOptions? options = null, string? path = "", Encoding? encoding = null) - { -#pragma warning disable CS0618 // We are calling into the obsolete member as that's the one that still does the real work - return Create(root, options, path, encoding, diagnosticOptions: null); -#pragma warning restore CS0618 - } + [Obsolete("Specify checksumAlgorithm")] + public static SyntaxTree Create(CSharpSyntaxNode root, CSharpParseOptions? options, string? path, Encoding? encoding) + => Create(root, SourceHashAlgorithm.Sha1, options, path, encoding); #pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. @@ -332,15 +329,17 @@ public static SyntaxTree Create( CSharpParseOptions? options, string? path, Encoding? encoding, - // obsolete parameter -- unused - ImmutableDictionary? diagnosticOptions, - // obsolete parameter -- unused - bool? isGeneratedCode) + ImmutableDictionary? diagnosticOptions, // unused + bool? isGeneratedCode) // unused + => Create(root , SourceHashAlgorithm.Sha1, options, path, encoding); + + /// + /// Creates a new syntax tree from a syntax node. + /// + public static SyntaxTree Create(CSharpSyntaxNode root, SourceHashAlgorithm checksumAlgorithm, CSharpParseOptions? options = null, string? path = "", Encoding? encoding = null) { if (root == null) - { throw new ArgumentNullException(nameof(root)); - } var directives = root.Kind() == SyntaxKind.CompilationUnit ? ((CompilationUnitSyntax)root).GetConditionalDirectivesStack() : @@ -349,12 +348,12 @@ public static SyntaxTree Create( return new ParsedSyntaxTree( textOpt: null, encodingOpt: encoding, - checksumAlgorithm: SourceHashAlgorithm.Sha1, + checksumAlgorithm: checksumAlgorithm, path: path, options: options ?? CSharpParseOptions.Default, root: root, directives: directives, - diagnosticOptions, + diagnosticOptions: null, cloneRoot: true); } diff --git a/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt b/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt index 9a31aa7a58e6f..308fba91a7780 100644 --- a/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ *REMOVED*Overrides Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilationOptions.GetHashCode() -> Integer +Shared Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree.Create(root As Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxNode, checksumAlgorithm As Microsoft.CodeAnalysis.Text.SourceHashAlgorithm, options As Microsoft.CodeAnalysis.VisualBasic.VisualBasicParseOptions = Nothing, path As String = "", encoding As System.Text.Encoding = Nothing) -> Microsoft.CodeAnalysis.SyntaxTree diff --git a/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxTree.vb b/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxTree.vb index 0370605d0b81e..a87f03dc343ca 100644 --- a/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxTree.vb +++ b/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxTree.vb @@ -158,6 +158,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ''' Creates a new syntax tree from a syntax node. ''' ''' An obsolete parameter. Diagnostic options should now be passed with + + Public Shared Function Create(root As VisualBasicSyntaxNode, Optional options As VisualBasicParseOptions = Nothing, Optional path As String = "", @@ -178,6 +180,30 @@ Namespace Microsoft.CodeAnalysis.VisualBasic diagnosticOptions) End Function + ''' + ''' Creates a new syntax tree from a syntax node. + ''' + Public Shared Function Create(root As VisualBasicSyntaxNode, + checksumAlgorithm As SourceHashAlgorithm, + Optional options As VisualBasicParseOptions = Nothing, + Optional path As String = "", + Optional encoding As Encoding = Nothing) As SyntaxTree + If root Is Nothing Then + Throw New ArgumentNullException(NameOf(root)) + End If + + Return New ParsedSyntaxTree( + textOpt:=Nothing, + encodingOpt:=encoding, + checksumAlgorithm:=checksumAlgorithm, + path:=path, + options:=If(options, VisualBasicParseOptions.Default), + syntaxRoot:=root, + isMyTemplate:=False, + diagnosticOptions:=Nothing, + cloneRoot:=True) + End Function + ''' ''' ''' Internal helper for class to create a new syntax tree rooted at the given root node. diff --git a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs index cfdb436eefda5..273e9aa76d180 100644 --- a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs +++ b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs @@ -110,7 +110,7 @@ public static async Task FormatDocumentAsync(Document document, Syntax text += logger.ToString(); text += "#endif" + Environment.NewLine; - return document.WithText(SourceText.From(text)); + return document.WithText(SourceText.From(text, encoding: null, checksumAlgorithm: SourceHashAlgorithm.Sha256)); } private static async Task AddAssemblyInfoRegionAsync(Document document, ISymbol symbol, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/Core/Interactive/InteractiveSession.cs b/src/EditorFeatures/Core/Interactive/InteractiveSession.cs index 26cad7274c8fa..12aec96a8ca4c 100644 --- a/src/EditorFeatures/Core/Interactive/InteractiveSession.cs +++ b/src/EditorFeatures/Core/Interactive/InteractiveSession.cs @@ -233,7 +233,7 @@ private void AddSubmissionProjectNoLock(ITextBuffer submissionBuffer, string lan solution = initProject.Solution.AddDocument( DocumentId.CreateNewId(initializationScriptProjectId, debugName: initializationScriptPath), Path.GetFileName(initializationScriptPath), - new FileTextLoader(initializationScriptPath, defaultEncoding: null)); + new FileTextLoader(initializationScriptPath, defaultEncoding: null, initProject.State.ChecksumAlgorithm)); } var newSubmissionProject = CreateSubmissionProjectNoLock(solution, _currentSubmissionProjectId, _lastSuccessfulSubmissionProjectId, languageName, imports, references); @@ -289,18 +289,21 @@ private Project CreateSubmissionProjectNoLock(Solution solution, ProjectId newSu solution = solution.AddProject( ProjectInfo.Create( - newSubmissionProjectId, - VersionStamp.Create(), - name: name, - assemblyName: name, - language: languageName, + new ProjectInfo.ProjectAttributes( + id: newSubmissionProjectId, + version: VersionStamp.Create(), + name: name, + assemblyName: name, + language: languageName, + compilationOutputFilePaths: default, + checksumAlgorithm: SourceHashAlgorithm.Sha256, + isSubmission: true), compilationOptions: compilationOptions, parseOptions: _languageInfo.ParseOptions, documents: null, projectReferences: null, metadataReferences: references, - hostObjectType: typeof(InteractiveScriptGlobals), - isSubmission: true)); + hostObjectType: typeof(InteractiveScriptGlobals))); if (previousSubmissionProjectId != null) { diff --git a/src/EditorFeatures/Core/Workspaces/EditorTextFactoryService.cs b/src/EditorFeatures/Core/Workspaces/EditorTextFactoryService.cs index 287d60b9f9ead..23a4fcaec4e54 100644 --- a/src/EditorFeatures/Core/Workspaces/EditorTextFactoryService.cs +++ b/src/EditorFeatures/Core/Workspaces/EditorTextFactoryService.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Workspaces { [ExportWorkspaceService(typeof(ITextFactoryService), ServiceLayer.Editor), Shared] - internal class EditorTextFactoryService : ITextFactoryService + internal sealed class EditorTextFactoryService : ITextFactoryService { private readonly ITextBufferCloneService _textBufferCloneService; private readonly ITextBufferFactoryService _textBufferFactory; @@ -37,7 +37,7 @@ public EditorTextFactoryService( private static readonly Encoding s_throwingUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); - public SourceText CreateText(Stream stream, Encoding? defaultEncoding, CancellationToken cancellationToken = default) + public SourceText CreateText(Stream stream, Encoding? defaultEncoding, SourceHashAlgorithm checksumAlgorithm, CancellationToken cancellationToken) { // this API is for a case where user wants us to figure out encoding from the given stream. // if defaultEncoding is given, we will use it if we couldn't figure out encoding used in the stream ourselves. @@ -50,7 +50,7 @@ public SourceText CreateText(Stream stream, Encoding? defaultEncoding, Cancellat // Try UTF-8 try { - return CreateTextInternal(stream, s_throwingUtf8Encoding, cancellationToken); + return CreateTextInternal(stream, s_throwingUtf8Encoding, checksumAlgorithm, cancellationToken); } catch (DecoderFallbackException) { @@ -61,7 +61,7 @@ public SourceText CreateText(Stream stream, Encoding? defaultEncoding, Cancellat try { - return CreateTextInternal(stream, defaultEncoding, cancellationToken); + return CreateTextInternal(stream, defaultEncoding, checksumAlgorithm, cancellationToken); } catch (DecoderFallbackException) { @@ -70,19 +70,19 @@ public SourceText CreateText(Stream stream, Encoding? defaultEncoding, Cancellat } } - public SourceText CreateText(TextReader reader, Encoding? encoding, CancellationToken cancellationToken = default) + public SourceText CreateText(TextReader reader, Encoding? encoding, SourceHashAlgorithm checksumAlgorithm, CancellationToken cancellationToken) { // this API is for a case where user just wants to create a source text with explicit encoding. var buffer = CreateTextBuffer(reader); // use the given encoding as it is. - return buffer.CurrentSnapshot.AsRoslynText(_textBufferCloneService, encoding); + return buffer.CurrentSnapshot.AsRoslynText(_textBufferCloneService, encoding, checksumAlgorithm); } private ITextBuffer CreateTextBuffer(TextReader reader) => _textBufferFactory.CreateTextBuffer(reader, _unknownContentType); - private SourceText CreateTextInternal(Stream stream, Encoding encoding, CancellationToken cancellationToken) + private SourceText CreateTextInternal(Stream stream, Encoding encoding, SourceHashAlgorithm checksumAlgorithm, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); stream.Seek(0, SeekOrigin.Begin); @@ -90,7 +90,7 @@ private SourceText CreateTextInternal(Stream stream, Encoding encoding, Cancella using var reader = new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true); var buffer = CreateTextBuffer(reader); - return buffer.CurrentSnapshot.AsRoslynText(_textBufferCloneService, reader.CurrentEncoding ?? Encoding.UTF8); + return buffer.CurrentSnapshot.AsRoslynText(_textBufferCloneService, reader.CurrentEncoding ?? Encoding.UTF8, checksumAlgorithm); } } } diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index 4d09fd38f4d62..381e500b7b9cd 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -495,28 +495,28 @@ public async Task StartDebuggingSession_CapturingDocuments(bool captureAllDocume solution = solution.AddDocument(DocumentInfo.Create( id: documentIdA, name: "A", - loader: new FileTextLoader(sourceFileA.Path, encodingA), + loader: new FileTextLoader(sourceFileA.Path, encodingA, SourceHashAlgorithm.Sha256), filePath: sourceFileA.Path)); var documentIdB = DocumentId.CreateNewId(projectP.Id, debugName: "B"); solution = solution.AddDocument(DocumentInfo.Create( id: documentIdB, name: "B", - loader: new FileTextLoader(sourceFileB.Path, encodingB), + loader: new FileTextLoader(sourceFileB.Path, encodingB, SourceHashAlgorithm.Sha256), filePath: sourceFileB.Path)); var documentIdC = DocumentId.CreateNewId(projectP.Id, debugName: "C"); solution = solution.AddDocument(DocumentInfo.Create( id: documentIdC, name: "C", - loader: new FileTextLoader(sourceFileC.Path, encodingC), + loader: new FileTextLoader(sourceFileC.Path, encodingC, SourceHashAlgorithm.Sha256), filePath: sourceFileC.Path)); var documentIdE = DocumentId.CreateNewId(projectP.Id, debugName: "E"); solution = solution.AddDocument(DocumentInfo.Create( id: documentIdE, name: "E", - loader: new FileTextLoader(sourceFileE.Path, encodingE), + loader: new FileTextLoader(sourceFileE.Path, encodingE, SourceHashAlgorithm.Sha256), filePath: sourceFileE.Path)); // check that are testing documents whose hash algorithm does not match the PDB (but the hash itself does): @@ -556,7 +556,7 @@ public async Task StartDebuggingSession_CapturingDocuments(bool captureAllDocume // change content of B on disk again: sourceFileB.WriteAllText(sourceB3, encodingB); - solution = solution.WithDocumentTextLoader(documentIdB, new FileTextLoader(sourceFileB.Path, encodingB), PreservationMode.PreserveValue); + solution = solution.WithDocumentTextLoader(documentIdB, new FileTextLoader(sourceFileB.Path, encodingB, SourceHashAlgorithm.Sha256), PreservationMode.PreserveValue); EnterBreakState(debuggingSession); @@ -4257,7 +4257,7 @@ public async Task MultiSession() solution = solution.AddDocument(DocumentInfo.Create( id: documentIdA, name: "A", - loader: new FileTextLoader(sourceFileA.Path, Encoding.UTF8), + loader: new FileTextLoader(sourceFileA.Path, Encoding.UTF8, SourceHashAlgorithm.Sha256), filePath: sourceFileA.Path)); var tasks = Enumerable.Range(0, 10).Select(async i => @@ -4342,7 +4342,7 @@ public async Task WatchHotReloadServiceTest() solution = solution.AddDocument(DocumentInfo.Create( id: documentIdA, name: "A", - loader: new FileTextLoader(sourceFileA.Path, Encoding.UTF8), + loader: new FileTextLoader(sourceFileA.Path, Encoding.UTF8, SourceHashAlgorithm.Sha256), filePath: sourceFileA.Path)); var hotReload = new WatchHotReloadService(workspace.Services, ImmutableArray.Create("Baseline", "AddDefinitionToExistingType", "NewTypeDefinition")); @@ -4409,7 +4409,7 @@ public async Task UnitTestingHotReloadServiceTest() solution = solution.AddDocument(DocumentInfo.Create( id: documentIdA, name: "A", - loader: new FileTextLoader(sourceFileA.Path, Encoding.UTF8), + loader: new FileTextLoader(sourceFileA.Path, Encoding.UTF8, SourceHashAlgorithm.Sha256), filePath: sourceFileA.Path)); var hotReload = new UnitTestingHotReloadService(workspace.Services); diff --git a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs index 0968de1a3ea39..734bd9f71e206 100644 --- a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Implementation.Workspaces; using Microsoft.CodeAnalysis.Host; @@ -82,7 +83,7 @@ public async Task TestCreateFromTemporaryStorage() var text = SourceText.From("Hello, World!"); // Create a temporary storage location - using var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(System.Threading.CancellationToken.None); + using var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(CancellationToken.None); // Write text into it await temporaryStorage.WriteTextAsync(text); @@ -104,7 +105,7 @@ public async Task TestCreateFromTemporaryStorageWithEncoding() var text = SourceText.From("Hello, World!", Encoding.ASCII); // Create a temporary storage location - using var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(System.Threading.CancellationToken.None); + using var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(CancellationToken.None); // Write text into it await temporaryStorage.WriteTextAsync(text); @@ -119,7 +120,7 @@ public async Task TestCreateFromTemporaryStorageWithEncoding() private static void TestCreateTextInferredEncoding(ITextFactoryService textFactoryService, byte[] bytes, Encoding? defaultEncoding, Encoding expectedEncoding) { using var stream = new MemoryStream(bytes); - var text = textFactoryService.CreateText(stream, defaultEncoding); + var text = textFactoryService.CreateText(stream, defaultEncoding, SourceHashAlgorithm.Sha256, CancellationToken.None); Assert.Equal(expectedEncoding, text.Encoding); } } diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index 34e399d39809f..598d8dd34899d 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -375,7 +375,16 @@ private static void VerifySyntaxMap( private void CreateProjects(EditScript[] editScripts, AdhocWorkspace workspace, TargetFramework targetFramework, out Project oldProject, out Project newProject) { - var projectInfo = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), name: "project", assemblyName: "project", LanguageName, filePath: Path.Combine(TempRoot.Root, "project" + ProjectFileExtension)); + var projectInfo = ProjectInfo.Create( + new ProjectInfo.ProjectAttributes( + id: ProjectId.CreateNewId(), + version: VersionStamp.Create(), + name: "project", + assemblyName: "project", + language: LanguageName, + compilationOutputFilePaths: default, + filePath: Path.Combine(TempRoot.Root, "project" + ProjectFileExtension), + checksumAlgorithm: SourceHashAlgorithm.Sha256)); oldProject = workspace.AddProject(projectInfo).WithMetadataReferences(TargetFrameworkUtil.GetReferences(targetFramework)); foreach (var editScript in editScripts) diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestHostProject.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestHostProject.cs index 3876043e91aa8..da45fadb25d8d 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestHostProject.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestHostProject.cs @@ -344,24 +344,27 @@ internal HostLanguageServices LanguageServiceProvider public ProjectInfo ToProjectInfo() { return ProjectInfo.Create( - Id, - Version, - Name, - AssemblyName, - Language, - FilePath, - OutputFilePath, + new ProjectInfo.ProjectAttributes( + Id, + Version, + name: Name, + assemblyName: AssemblyName, + language: Language, + compilationOutputFilePaths: default, + checksumAlgorithm: Text.SourceHashAlgorithm.Sha256, + defaultNamespace: DefaultNamespace, + filePath: FilePath, + outputFilePath: OutputFilePath, + isSubmission: IsSubmission), CompilationOptions, ParseOptions, - Documents.Where(d => !d.IsSourceGenerated).Select(d => d.ToDocumentInfo()), + documents: Documents.Where(d => !d.IsSourceGenerated).Select(d => d.ToDocumentInfo()), ProjectReferences, MetadataReferences, AnalyzerReferences, - AdditionalDocuments.Select(d => d.ToDocumentInfo()), - IsSubmission, - HostObjectType) - .WithAnalyzerConfigDocuments(AnalyzerConfigDocuments.Select(d => d.ToDocumentInfo())) - .WithDefaultNamespace(DefaultNamespace); + additionalDocuments: AdditionalDocuments.Select(d => d.ToDocumentInfo()), + analyzerConfigDocuments: AnalyzerConfigDocuments.Select(d => d.ToDocumentInfo()), + HostObjectType); } // It is identical with the internal extension method 'GetDefaultExtension' defined in OutputKind.cs. diff --git a/src/EditorFeatures/Text/Extensions.SnapshotSourceText.cs b/src/EditorFeatures/Text/Extensions.SnapshotSourceText.cs index 10760d3d1b757..0eac21a082303 100644 --- a/src/EditorFeatures/Text/Extensions.SnapshotSourceText.cs +++ b/src/EditorFeatures/Text/Extensions.SnapshotSourceText.cs @@ -46,7 +46,8 @@ private SnapshotSourceText(ITextBufferCloneService? textBufferCloneService, ITex _container = container; } - public SnapshotSourceText(ITextBufferCloneService? textBufferCloneService, ITextImage textImage, Encoding? encoding, TextBufferContainer? container) + public SnapshotSourceText(ITextBufferCloneService? textBufferCloneService, ITextImage textImage, Encoding? encoding, SourceHashAlgorithm checksumAlgorithm, TextBufferContainer? container) + : base(checksumAlgorithm: checksumAlgorithm) { Contract.ThrowIfNull(textImage); @@ -263,8 +264,8 @@ private static ITextImage RecordReverseMapAndGetImage(ITextSnapshot editorSnapsh /// internal sealed class ClosedSnapshotSourceText : SnapshotSourceText { - public ClosedSnapshotSourceText(ITextBufferCloneService? textBufferCloneService, ITextImage textImage, Encoding? encoding) - : base(textBufferCloneService, textImage, encoding, container: null) + public ClosedSnapshotSourceText(ITextBufferCloneService? textBufferCloneService, ITextImage textImage, Encoding? encoding, SourceHashAlgorithm checksumAlgorithm) + : base(textBufferCloneService, textImage, encoding, checksumAlgorithm, container: null) { } } @@ -278,7 +279,7 @@ private class ChangedSourceText : SnapshotSourceText private readonly ITextImage _baseSnapshot; public ChangedSourceText(ITextBufferCloneService? textBufferCloneService, SnapshotSourceText baseText, ITextImage baseSnapshot, ITextImage currentSnapshot) - : base(textBufferCloneService, currentSnapshot, baseText.Encoding, container: null) + : base(textBufferCloneService, currentSnapshot, baseText.Encoding, baseText.ChecksumAlgorithm, container: null) { _baseText = baseText; _baseSnapshot = baseSnapshot; diff --git a/src/EditorFeatures/Text/Extensions.cs b/src/EditorFeatures/Text/Extensions.cs index c0abe4ace4d07..a4227135273b8 100644 --- a/src/EditorFeatures/Text/Extensions.cs +++ b/src/EditorFeatures/Text/Extensions.cs @@ -43,8 +43,8 @@ public static SourceText AsText(this ITextSnapshot textSnapshot) return SnapshotSourceText.From(textBufferCloneServiceOpt, textSnapshot); } - internal static SourceText AsRoslynText(this ITextSnapshot textSnapshot, ITextBufferCloneService textBufferCloneServiceOpt, Encoding? encoding) - => new SnapshotSourceText.ClosedSnapshotSourceText(textBufferCloneServiceOpt, ((ITextSnapshot2)textSnapshot).TextImage, encoding); + internal static SourceText AsRoslynText(this ITextSnapshot textSnapshot, ITextBufferCloneService textBufferCloneServiceOpt, Encoding? encoding, SourceHashAlgorithm checksumAlgorithm) + => new SnapshotSourceText.ClosedSnapshotSourceText(textBufferCloneServiceOpt, ((ITextSnapshot2)textSnapshot).TextImage, encoding, checksumAlgorithm); /// /// Gets the workspace corresponding to the text buffer. diff --git a/src/Features/Core/Portable/MetadataAsSource/DecompilationMetadataAsSourceFileProvider.cs b/src/Features/Core/Portable/MetadataAsSource/DecompilationMetadataAsSourceFileProvider.cs index cd9cc69fd9f60..e611a557f4410 100644 --- a/src/Features/Core/Portable/MetadataAsSource/DecompilationMetadataAsSourceFileProvider.cs +++ b/src/Features/Core/Portable/MetadataAsSource/DecompilationMetadataAsSourceFileProvider.cs @@ -264,7 +264,7 @@ private bool RemoveDocumentFromWorkspace(Workspace workspace, MetadataAsSourceGe var documentId = _openedDocumentIds.GetValueOrDefault(fileInfo); Contract.ThrowIfNull(documentId); - workspace.OnDocumentClosed(documentId, new FileTextLoader(fileInfo.TemporaryFilePath, MetadataAsSourceGeneratedFileInfo.Encoding)); + workspace.OnDocumentClosed(documentId, new FileTextLoader(fileInfo.TemporaryFilePath, MetadataAsSourceGeneratedFileInfo.Encoding, MetadataAsSourceGeneratedFileInfo.ChecksumAlgorithm)); workspace.OnProjectRemoved(documentId.ProjectId); _openedDocumentIds = _openedDocumentIds.RemoveKey(fileInfo); diff --git a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs index a182a2ba44989..a8f6558a68266 100644 --- a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs +++ b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs @@ -49,7 +49,9 @@ public MetadataAsSourceGeneratedFileInfo(string rootPath, Project sourceProject, this.TemporaryFilePath = Path.Combine(rootPath, directoryName, topLevelNamedType.Name + extension); } + // TODO: public static Encoding Encoding => Encoding.UTF8; + public static SourceHashAlgorithm ChecksumAlgorithm => SourceHashAlgorithm.Sha256; /// /// Creates a ProjectInfo to represent the fake project created for metadata as source documents. @@ -74,7 +76,7 @@ public Tuple GetProjectInfoAndDocumentId(Workspace work ? string.Format(@"[assembly: System.Reflection.AssemblyVersion(""{0}"")]", AssemblyIdentity.Version) : string.Format(@"", AssemblyIdentity.Version); - var assemblyInfoSourceTextContainer = SourceText.From(assemblyInfoString, Encoding).Container; + var assemblyInfoSourceTextContainer = SourceText.From(assemblyInfoString, Encoding, ChecksumAlgorithm).Container; var assemblyInfoDocument = DocumentInfo.Create( assemblyInfoDocumentId, @@ -86,14 +88,17 @@ public Tuple GetProjectInfoAndDocumentId(Workspace work generatedDocumentId, Path.GetFileName(TemporaryFilePath), filePath: TemporaryFilePath, - loader: loadFileFromDisk ? new FileTextLoader(TemporaryFilePath, Encoding) : null); + loader: loadFileFromDisk ? new FileTextLoader(TemporaryFilePath, Encoding, ChecksumAlgorithm) : null); var projectInfo = ProjectInfo.Create( - projectId, - VersionStamp.Default, - name: AssemblyIdentity.Name, - assemblyName: AssemblyIdentity.Name, - language: LanguageName, + new ProjectInfo.ProjectAttributes( + id: projectId, + version: VersionStamp.Default, + name: AssemblyIdentity.Name, + assemblyName: AssemblyIdentity.Name, + language: LanguageName, + compilationOutputFilePaths: default, + checksumAlgorithm: ChecksumAlgorithm), compilationOptions: compilationOptions, parseOptions: _parseOptions, documents: new[] { assemblyInfoDocument, generatedDocument }, diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index 661fa8469e3e2..36d4adbac35f5 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -119,6 +119,7 @@ + diff --git a/src/Features/Core/Portable/PdbSourceDocument/IPdbSourceDocumentLoaderService.cs b/src/Features/Core/Portable/PdbSourceDocument/IPdbSourceDocumentLoaderService.cs index caa34093c4bcc..4ebfe3dcc1a5a 100644 --- a/src/Features/Core/Portable/PdbSourceDocument/IPdbSourceDocumentLoaderService.cs +++ b/src/Features/Core/Portable/PdbSourceDocument/IPdbSourceDocumentLoaderService.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.PdbSourceDocument { @@ -16,6 +17,7 @@ internal interface IPdbSourceDocumentLoaderService /// The path to the source file on disk /// Localized description of where the file came from, for the document tab, eg. Source Link, Embedded, On Disk /// The text loader to use + /// Algorithm to use for content checksum. /// Whether the source files came from a remote location, and therefore their existence should be used to indicate that future requests can wait longer - internal sealed record SourceFileInfo(string FilePath, string SourceDescription, TextLoader Loader, bool FromRemoteLocation); + internal sealed record SourceFileInfo(string FilePath, string SourceDescription, TextLoader Loader, SourceHashAlgorithm ChecksumAlgorithm, bool FromRemoteLocation); } diff --git a/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentLoaderService.cs b/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentLoaderService.cs index 8101b2ff5aed2..6f3d4d6f6d913 100644 --- a/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentLoaderService.cs +++ b/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentLoaderService.cs @@ -197,14 +197,14 @@ public PdbSourceDocumentLoaderService( { using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete); - var sourceText = SourceText.From(stream, encoding, sourceDocument.HashAlgorithm, throwIfBinaryDetected: true); + var sourceText = SourceText.From(stream, encoding, sourceDocument.ChecksumAlgorithm, throwIfBinaryDetected: true); var fileChecksum = sourceText.GetChecksum(); if (ignoreChecksum || fileChecksum.SequenceEqual(sourceDocument.Checksum)) { var textAndVersion = TextAndVersion.Create(sourceText, VersionStamp.Default, filePath); var textLoader = TextLoader.From(textAndVersion); - return new SourceFileInfo(filePath, sourceDescription, textLoader, fromRemoteLocation); + return new SourceFileInfo(filePath, sourceDescription, textLoader, sourceDocument.ChecksumAlgorithm, fromRemoteLocation); } return null; diff --git a/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs b/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs index fa410592ec2a7..3331a7bfe8ecd 100644 --- a/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs +++ b/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -159,12 +160,15 @@ public PdbSourceDocumentMetadataAsSourceFileProvider( } } + Debug.Assert(!sourceDocuments.IsEmpty); + var checksumAlgorithm = sourceDocuments[0].ChecksumAlgorithm; + Encoding? defaultEncoding = null; - if (pdbCompilationOptions.TryGetValue("default-encoding", out var encodingString)) + if (pdbCompilationOptions.TryGetValue(Cci.CompilationOptionNames.DefaultEncoding, out var encodingString)) { defaultEncoding = Encoding.GetEncoding(encodingString); } - else if (pdbCompilationOptions.TryGetValue("fallback-encoding", out var fallbackEncodingString)) + else if (pdbCompilationOptions.TryGetValue(Cci.CompilationOptionNames.FallbackEncoding, out var fallbackEncodingString)) { defaultEncoding = Encoding.GetEncoding(fallbackEncodingString); } @@ -172,7 +176,7 @@ public PdbSourceDocumentMetadataAsSourceFileProvider( if (!_assemblyToProjectMap.TryGetValue(assemblyName, out var projectId)) { // Get the project info now, so we can dispose the documentDebugInfoReader sooner - var projectInfo = CreateProjectInfo(workspace, project, pdbCompilationOptions, assemblyName, assemblyVersion); + var projectInfo = CreateProjectInfo(workspace, project, pdbCompilationOptions, assemblyName, assemblyVersion, checksumAlgorithm); if (projectInfo is null) return null; @@ -232,11 +236,11 @@ public PdbSourceDocumentMetadataAsSourceFileProvider( return new MetadataAsSourceFile(documentPath, navigateLocation, documentName, documentTooltip); } - private ProjectInfo? CreateProjectInfo(Workspace workspace, Project project, ImmutableDictionary pdbCompilationOptions, string assemblyName, string assemblyVersion) + private ProjectInfo? CreateProjectInfo(Workspace workspace, Project project, ImmutableDictionary pdbCompilationOptions, string assemblyName, string assemblyVersion, SourceHashAlgorithm checksumAlgorithm) { // First we need the language name in order to get the services // TODO: Find language another way for non portable PDBs: https://github.com/dotnet/roslyn/issues/55834 - if (!pdbCompilationOptions.TryGetValue("language", out var languageName) || languageName is null) + if (!pdbCompilationOptions.TryGetValue(Cci.CompilationOptionNames.Language, out var languageName) || languageName is null) { _logger?.Log(FeaturesResources.Source_code_language_information_was_not_found_in_PDB); return null; @@ -250,11 +254,14 @@ public PdbSourceDocumentMetadataAsSourceFileProvider( var projectId = ProjectId.CreateNewId(); return ProjectInfo.Create( - projectId, - VersionStamp.Default, - name: $"{assemblyName} ({assemblyVersion})", - assemblyName: assemblyName, - language: languageName, + new ProjectInfo.ProjectAttributes( + id: projectId, + version: VersionStamp.Default, + name: $"{assemblyName} ({assemblyVersion})", + assemblyName: assemblyName, + language: languageName, + compilationOutputFilePaths: default, + checksumAlgorithm: checksumAlgorithm), compilationOptions: compilationOptions, parseOptions: parseOptions, metadataReferences: project.MetadataReferences.ToImmutableArray()); // TODO: Read references from PDB info: https://github.com/dotnet/roslyn/issues/55834 @@ -293,7 +300,7 @@ private ImmutableArray CreateDocumentInfos(SourceFileInfo?[] sourc } // In order to open documents in VS we need to understand the link from temp file to document and its encoding etc. - _fileToDocumentInfoMap[info.FilePath] = new(documentId, encoding, sourceProject.Id, sourceProject.Solution.Workspace); + _fileToDocumentInfoMap[info.FilePath] = new(documentId, encoding, info.ChecksumAlgorithm, sourceProject.Id, sourceProject.Solution.Workspace); } return documents.ToImmutable(); @@ -325,7 +332,7 @@ public bool TryRemoveDocumentFromWorkspace(Workspace workspace, string filePath) { if (_fileToDocumentInfoMap.TryGetValue(filePath, out var info)) { - workspace.OnDocumentClosed(info.DocumentId, new FileTextLoader(filePath, info.Encoding)); + workspace.OnDocumentClosed(info.DocumentId, new FileTextLoader(filePath, info.Encoding, info.ChecksumAlgorithm)); return true; } @@ -379,7 +386,7 @@ public void CleanupGeneratedFiles(Workspace? workspace) } } - internal sealed record SourceDocument(string FilePath, SourceHashAlgorithm HashAlgorithm, ImmutableArray Checksum, byte[]? EmbeddedTextBytes, string? SourceLinkUrl); + internal sealed record SourceDocument(string FilePath, SourceHashAlgorithm ChecksumAlgorithm, ImmutableArray Checksum, byte[]? EmbeddedTextBytes, string? SourceLinkUrl); - internal record struct SourceDocumentInfo(DocumentId DocumentId, Encoding Encoding, ProjectId SourceProjectId, Workspace SourceWorkspace); + internal record struct SourceDocumentInfo(DocumentId DocumentId, Encoding Encoding, SourceHashAlgorithm ChecksumAlgorithm, ProjectId SourceProjectId, Workspace SourceWorkspace); } diff --git a/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs b/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs index 73ce15439c8eb..f88cf1af61bec 100644 --- a/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs +++ b/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs @@ -8,6 +8,7 @@ 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 @@ -18,6 +19,7 @@ internal static ProjectInfo CreateMiscellaneousProjectInfoForDocument( string filePath, TextLoader textLoader, LanguageInformation languageInformation, + SourceHashAlgorithm checksumAlgorithm, HostWorkspaceServices services, ImmutableArray metadataReferences) { @@ -54,19 +56,23 @@ internal static ProjectInfo CreateMiscellaneousProjectInfoForDocument( var assemblyName = Guid.NewGuid().ToString("N"); var projectInfo = ProjectInfo.Create( - projectId, - VersionStamp.Create(), - name: FeaturesResources.Miscellaneous_Files, - assemblyName, - languageInformation.LanguageName, + new ProjectInfo.ProjectAttributes( + id: projectId, + version: VersionStamp.Create(), + name: FeaturesResources.Miscellaneous_Files, + assemblyName: assemblyName, + language: languageInformation.LanguageName, + compilationOutputFilePaths: 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: SpecializedCollections.SingletonEnumerable(documentInfo), metadataReferences: metadataReferences); - // 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. - return projectInfo.WithHasAllInformation(hasAllInformation: sourceCodeKind == SourceCodeKind.Script); + return projectInfo; } // Do not inline this to avoid loading Microsoft.CodeAnalysis.Scripting unless a script file is opened in the workspace. diff --git a/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidOpenHandler.cs b/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidOpenHandler.cs index eb02cb25745b5..0580bf3f16030 100644 --- a/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidOpenHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/DocumentChanges/DidOpenHandler.cs @@ -34,7 +34,7 @@ public DidOpenHandler() context.TraceInformation($"didOpen for {request.TextDocument.Uri}"); // Add the document and ensure the text we have matches whats on the client - var sourceText = SourceText.From(request.TextDocument.Text, System.Text.Encoding.UTF8); + var sourceText = SourceText.From(request.TextDocument.Text, System.Text.Encoding.UTF8, SourceHashAlgorithm.Sha256); context.StartTracking(request.TextDocument.Uri, sourceText); diff --git a/src/Features/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspace.cs b/src/Features/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspace.cs index b8ba6a8e3305d..46fd65f3a538f 100644 --- a/src/Features/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspace.cs +++ b/src/Features/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspace.cs @@ -63,7 +63,7 @@ public LspMiscellaneousFilesWorkspace() : base(MefHostServices.DefaultHost, Work var sourceTextLoader = new SourceTextLoader(documentText, uriAbsolutePath); - var projectInfo = MiscellaneousFileUtilities.CreateMiscellaneousProjectInfoForDocument(uri.AbsolutePath, sourceTextLoader, languageInformation, Services, ImmutableArray.Empty); + var projectInfo = MiscellaneousFileUtilities.CreateMiscellaneousProjectInfoForDocument(uri.AbsolutePath, sourceTextLoader, languageInformation, documentText.ChecksumAlgorithm, Services, ImmutableArray.Empty); OnProjectAdded(projectInfo); var id = projectInfo.Documents.Single().Id; diff --git a/src/Features/Lsif/Generator/CompilerInvocation.cs b/src/Features/Lsif/Generator/CompilerInvocation.cs index 444ffaacda939..28d8982b5e091 100644 --- a/src/Features/Lsif/Generator/CompilerInvocation.cs +++ b/src/Features/Lsif/Generator/CompilerInvocation.cs @@ -90,20 +90,25 @@ public static async Task CreateFromInvocationInfoAsync(Compi var projectId = ProjectId.CreateNewId(invocationInfo.ProjectFilePath); var projectInfo = ProjectInfo.Create( - projectId, - VersionStamp.Default, - name: Path.GetFileNameWithoutExtension(invocationInfo.ProjectFilePath), - assemblyName: parsedCommandLine.CompilationName!, - language: languageName, - filePath: invocationInfo.ProjectFilePath, - outputFilePath: parsedCommandLine.OutputFileName, + new ProjectInfo.ProjectAttributes( + id: projectId, + version: VersionStamp.Default, + name: Path.GetFileNameWithoutExtension(invocationInfo.ProjectFilePath), + assemblyName: parsedCommandLine.CompilationName!, + language: languageName, + compilationOutputFilePaths: default, + checksumAlgorithm: parsedCommandLine.ChecksumAlgorithm, + filePath: invocationInfo.ProjectFilePath, + outputFilePath: parsedCommandLine.OutputFileName), parsedCommandLine.CompilationOptions, parsedCommandLine.ParseOptions, parsedCommandLine.SourceFiles.Select(s => CreateDocumentInfo(unmappedPath: s.Path)), + projectReferences: null, metadataReferences: parsedCommandLine.MetadataReferences.Select(r => MetadataReference.CreateFromFile(mapPath(r.Reference), r.Properties)), additionalDocuments: parsedCommandLine.AdditionalFiles.Select(f => CreateDocumentInfo(unmappedPath: f.Path)), - analyzerReferences: parsedCommandLine.AnalyzerReferences.Select(r => new AnalyzerFileReference(r.FilePath, analyzerLoader))) - .WithAnalyzerConfigDocuments(parsedCommandLine.AnalyzerConfigPaths.Select(CreateDocumentInfo)); + analyzerReferences: parsedCommandLine.AnalyzerReferences.Select(r => new AnalyzerFileReference(r.FilePath, analyzerLoader)), + analyzerConfigDocuments: parsedCommandLine.AnalyzerConfigPaths.Select(CreateDocumentInfo), + hostObjectType: null); var solution = workspace.CurrentSolution.AddProject(projectInfo); var compilation = await solution.GetRequiredProject(projectId).GetRequiredCompilationAsync(CancellationToken.None); @@ -119,7 +124,7 @@ DocumentInfo CreateDocumentInfo(string unmappedPath) DocumentId.CreateNewId(projectId, mappedPath), name: mappedPath, filePath: mappedPath, - loader: new FileTextLoader(mappedPath, parsedCommandLine.Encoding)); + loader: new FileTextLoader(mappedPath, parsedCommandLine.Encoding, parsedCommandLine.ChecksumAlgorithm)); } } diff --git a/src/Scripting/CSharp/CSharpScript.cs b/src/Scripting/CSharp/CSharpScript.cs index 09df113dbf08c..ecbe6f2510c54 100644 --- a/src/Scripting/CSharp/CSharpScript.cs +++ b/src/Scripting/CSharp/CSharpScript.cs @@ -34,7 +34,7 @@ public static Script Create(string code, ScriptOptions options = null, Typ { if (code == null) throw new ArgumentNullException(nameof(code)); - return Script.CreateInitialScript(CSharpScriptCompiler.Instance, SourceText.From(code, options?.FileEncoding), options, globalsType, assemblyLoader); + return Script.CreateInitialScript(CSharpScriptCompiler.Instance, SourceText.From(code, options?.FileEncoding, SourceHashAlgorithm.Sha256), options, globalsType, assemblyLoader); } /// diff --git a/src/VisualStudio/Core/Def/Implementation/AbstractEditorFactory.cs b/src/VisualStudio/Core/Def/Implementation/AbstractEditorFactory.cs index ad19ee5b9a16e..a85f23db3a11b 100644 --- a/src/VisualStudio/Core/Def/Implementation/AbstractEditorFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/AbstractEditorFactory.cs @@ -325,7 +325,8 @@ private async Task FormatDocumentCreatedFromTemplateAsync(IVsHierarchy hierarchy var documentId = DocumentId.CreateNewId(projectToAddTo.Id); - var forkedSolution = projectToAddTo.Solution.AddDocument(DocumentInfo.Create(documentId, filePath, loader: new FileTextLoader(filePath, defaultEncoding: null), filePath: filePath)); + var fileLoader = new FileTextLoader(filePath, defaultEncoding: null, projectToAddTo.State.ChecksumAlgorithm); + var forkedSolution = projectToAddTo.Solution.AddDocument(DocumentInfo.Create(documentId, filePath, loader: fileLoader, filePath: filePath)); var addedDocument = forkedSolution.GetRequiredDocument(documentId); var globalOptions = _componentModel.GetService(); diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs b/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs index 981767452bd41..74358ba787813 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs @@ -292,7 +292,9 @@ private ProjectInfo CreateProjectInfoForDocument(string filePath) var languageInformation = TryGetLanguageInformation(filePath); Contract.ThrowIfNull(languageInformation); - return MiscellaneousFileUtilities.CreateMiscellaneousProjectInfoForDocument(filePath, new FileTextLoader(filePath, defaultEncoding: null), languageInformation, Services, _metadataReferences); + var checksumAlgorithm = SourceHashAlgorithm.Sha256; + var fileLoader = new FileTextLoader(filePath, defaultEncoding: null, checksumAlgorithm); + return MiscellaneousFileUtilities.CreateMiscellaneousProjectInfoForDocument(filePath, fileLoader, languageInformation, checksumAlgorithm, Services, _metadataReferences); } private void DetachFromDocument(string moniker) @@ -305,11 +307,12 @@ private void DetachFromDocument(string moniker) if (_monikersToProjectIdAndContainer.TryGetValue(moniker, out var projectIdAndContainer)) { - var document = this.CurrentSolution.GetProject(projectIdAndContainer.projectId).Documents.Single(); + var project = CurrentSolution.GetProject(projectIdAndContainer.projectId); + var document = project.Documents.Single(); // We must close the document prior to deleting the project - OnDocumentClosed(document.Id, new FileTextLoader(document.FilePath, defaultEncoding: null)); - OnProjectRemoved(document.Project.Id); + OnDocumentClosed(document.Id, new FileTextLoader(document.FilePath, defaultEncoding: null, project.State.ChecksumAlgorithm)); + OnProjectRemoved(project.Id); _monikersToProjectIdAndContainer.Remove(moniker); diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.BatchingDocumentCollection.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.BatchingDocumentCollection.cs index b3c174ecbce2d..0a3d6a5a53eeb 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.BatchingDocumentCollection.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.BatchingDocumentCollection.cs @@ -92,7 +92,7 @@ public DocumentId AddFile(string fullPath, SourceCodeKind sourceCodeKind, Immuta } var documentId = DocumentId.CreateNewId(_project.Id, fullPath); - var textLoader = new FileTextLoader(fullPath, defaultEncoding: null); + var textLoader = new FileTextLoader(fullPath, defaultEncoding: null, _project.ChecksumAlgorithm); var documentInfo = DocumentInfo.Create( documentId, FileNameUtilities.GetFileName(fullPath), @@ -401,7 +401,7 @@ public async ValueTask ProcessRegularFileChangesAsync(ImmutableSegmentedList d.Id == documentId)) { - documentsToChange.Add((documentId, new FileTextLoader(filePath, defaultEncoding: null))); + documentsToChange.Add((documentId, new FileTextLoader(filePath, defaultEncoding: null, _project.ChecksumAlgorithm))); } } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs index 310bd18639769..ff1de4f528950 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs @@ -69,6 +69,7 @@ internal sealed partial class VisualStudioProject private string? _filePath; private CompilationOptions? _compilationOptions; private ParseOptions? _parseOptions; + private SourceHashAlgorithm _checksumAlgorithm; private bool _hasAllInformation = true; private string? _compilationOutputAssemblyFilePath; private string? _outputFilePath; @@ -385,6 +386,12 @@ public string DisplayName set => ChangeProjectProperty(ref _displayName, value, s => s.WithProjectName(Id, value)); } + public SourceHashAlgorithm ChecksumAlgorithm + { + get => _checksumAlgorithm; + set => ChangeProjectProperty(ref _checksumAlgorithm, value, s => s.WithProjectChecksumAlgorithm(Id, value)); + } + // internal to match the visibility of the Workspace-level API -- this is something // we use but we haven't made officially public yet. internal bool HasAllInformation diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs index 1a0a8c2e8c398..cdb88b9095c2f 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServices.ExternalAccess.VSTypeScript.Api; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; using Microsoft.VisualStudio.Shell; @@ -105,15 +106,18 @@ await _visualStudioWorkspaceImpl.ApplyChangeToWorkspaceAsync(w => _visualStudioWorkspaceImpl.AddProjectToInternalMaps_NoLock(project, creationInfo.Hierarchy, creationInfo.ProjectGuid, projectSystemName); var projectInfo = ProjectInfo.Create( + new ProjectInfo.ProjectAttributes( id, versionStamp, name: projectSystemName, assemblyName: assemblyName, language: language, + checksumAlgorithm: SourceHashAlgorithm.Sha256, + compilationOutputFilePaths: default, filePath: creationInfo.FilePath, - compilationOptions: creationInfo.CompilationOptions, - parseOptions: creationInfo.ParseOptions) - .WithTelemetryId(creationInfo.ProjectGuid); + telemetryId: creationInfo.ProjectGuid), + compilationOptions: creationInfo.CompilationOptions, + parseOptions: creationInfo.ParseOptions); // If we don't have any projects and this is our first project being added, then we'll create a new SolutionId // and count this as the solution being added so that event is raised. diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectOptionsProcessor.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectOptionsProcessor.cs index adbffbc162490..a77426b81d16c 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectOptionsProcessor.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectOptionsProcessor.cs @@ -202,8 +202,9 @@ private void UpdateProjectOptions_NoLock() ? Path.Combine(_commandLineArgumentsForCommandLine.OutputDirectory, _commandLineArgumentsForCommandLine.OutputFileName) : _commandLineArgumentsForCommandLine.OutputFileName; - _project.CompilationOutputAssemblyFilePath = fullOutputFilePath ?? _project.CompilationOutputAssemblyFilePath; + _project.CompilationOutputAssemblyFilePath = fullOutputFilePath; _project.ParseOptions = parseOptions; + _project.ChecksumAlgorithm = _commandLineArgumentsForCommandLine.ChecksumAlgorithm; } private void RuleSetFile_UpdatedOnDisk(object sender, EventArgs e) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs index 9b28ed151b3e5..e0fe5845527b9 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.ComponentModelHost; @@ -317,18 +318,20 @@ private void TryClosingDocumentsForMoniker(string moniker) { if (w.IsDocumentOpen(documentId) && !_workspace._documentsNotFromFiles.Contains(documentId)) { - if (w.CurrentSolution.ContainsDocument(documentId)) + var solution = w.CurrentSolution; + + if (solution.GetDocument(documentId) is { } document) { - w.OnDocumentClosed(documentId, new FileTextLoader(moniker, defaultEncoding: null)); + w.OnDocumentClosed(documentId, new FileTextLoader(moniker, defaultEncoding: null, document.Project.State.ChecksumAlgorithm)); } - else if (w.CurrentSolution.ContainsAdditionalDocument(documentId)) + else if (solution.GetAdditionalDocument(documentId) is { } additionalDocument) { - w.OnAdditionalDocumentClosed(documentId, new FileTextLoader(moniker, defaultEncoding: null)); + w.OnAdditionalDocumentClosed(documentId, new FileTextLoader(moniker, defaultEncoding: null, additionalDocument.Project.State.ChecksumAlgorithm)); } else { - Debug.Assert(w.CurrentSolution.ContainsAnalyzerConfigDocument(documentId)); - w.OnAnalyzerConfigDocumentClosed(documentId, new FileTextLoader(moniker, defaultEncoding: null)); + var analyzerConfigDocument = solution.GetRequiredAnalyzerConfigDocument(documentId); + w.OnAnalyzerConfigDocumentClosed(documentId, new FileTextLoader(moniker, defaultEncoding: null, analyzerConfigDocument.Project.State.ChecksumAlgorithm)); } } } diff --git a/src/VisualStudio/Core/Impl/CodeModel/CodeModelProjectCache.cs b/src/VisualStudio/Core/Impl/CodeModel/CodeModelProjectCache.cs index e75b3f9d6c899..f3e95a7a2ab4c 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/CodeModelProjectCache.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/CodeModelProjectCache.cs @@ -108,8 +108,9 @@ internal CodeModelProjectCache( } // Check that we know about this file! - var documentId = State.Workspace.CurrentSolution.GetDocumentIdsWithFilePath(filePath).Where(id => id.ProjectId == _projectId).FirstOrDefault(); - if (documentId == null || State.Workspace.CurrentSolution.GetDocument(documentId) == null) + var solution = State.Workspace.CurrentSolution; + var documentId = solution.GetDocumentIdsWithFilePath(filePath).Where(id => id.ProjectId == _projectId).FirstOrDefault(); + if (documentId == null || solution.GetDocument(documentId) == null) { // Matches behavior of native (C#) implementation throw Exceptions.ThrowENotImpl(); diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs index 4f740db39e886..fc4d02db2032a 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs @@ -135,12 +135,6 @@ public void SetOptions(string commandLineForOptions) public void SetOptions(ImmutableArray arguments) => _visualStudioProjectOptionsProcessor?.SetCommandLine(arguments); - public string? DefaultNamespace - { - get => _visualStudioProject.DefaultNamespace; - private set => _visualStudioProject.DefaultNamespace = value; - } - public void SetProperty(string name, string? value) { if (name == AdditionalPropertyNames.RootNamespace) @@ -150,7 +144,7 @@ public void SetProperty(string name, string? value) // use it for their own purpose. // In the future, we might consider officially exposing "default namespace" for VB project // (e.g. through a msbuild property) - DefaultNamespace = value; + _visualStudioProject.DefaultNamespace = value; } else if (name == AdditionalPropertyNames.MaxSupportedLangVersion) { diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index e5817b8313fd2..d2cfd25b9cec9 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -172,6 +172,7 @@ static Solution SetProjectProperties(Solution solution, int version) .WithProjectOutputRefFilePath(projectId, "OutputRefFilePath" + version) .WithProjectCompilationOutputInfo(projectId, new CompilationOutputInfo("AssemblyPath" + version)) .WithProjectDefaultNamespace(projectId, "DefaultNamespace" + version) + .WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha1 + version) .WithHasAllInformation(projectId, (version % 2) != 0) .WithRunAnalyzers(projectId, (version % 2) != 0); } @@ -186,6 +187,7 @@ static void ValidateProperties(Solution solution, int version) Assert.Equal("OutputRefFilePath" + version, project.OutputRefFilePath); Assert.Equal("AssemblyPath" + version, project.CompilationOutputInfo.AssemblyPath); Assert.Equal("DefaultNamespace" + version, project.DefaultNamespace); + Assert.Equal(SourceHashAlgorithm.Sha1 + version, project.State.ChecksumAlgorithm); Assert.Equal((version % 2) != 0, project.State.HasAllInformation); Assert.Equal((version % 2) != 0, project.State.RunAnalyzers); } diff --git a/src/VisualStudio/LiveShare/Impl/Client/Projects/FileTextLoaderNoException.cs b/src/VisualStudio/LiveShare/Impl/Client/Projects/FileTextLoaderNoException.cs index 5b6a7551133aa..fac4dc918d6da 100644 --- a/src/VisualStudio/LiveShare/Impl/Client/Projects/FileTextLoaderNoException.cs +++ b/src/VisualStudio/LiveShare/Impl/Client/Projects/FileTextLoaderNoException.cs @@ -19,7 +19,8 @@ namespace Microsoft.VisualStudio.LanguageServices.LiveShare.Client.Projects /// internal class FileTextLoaderNoException : FileTextLoader { - public FileTextLoaderNoException(string path, Encoding defaultEncoding) : base(path, defaultEncoding) + public FileTextLoaderNoException(string path, Encoding defaultEncoding) + : base(path, defaultEncoding, SourceHashAlgorithm.Sha256) { } diff --git a/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs b/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs index d1bcdd785c163..8c24220991824 100644 --- a/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs +++ b/src/VisualStudio/LiveShare/Impl/Client/RemoteLanguageServiceWorkspace.cs @@ -336,7 +336,7 @@ private Document AddDocumentToProject(string filePath, string language, string p var docInfo = DocumentInfo.Create(DocumentId.CreateNewId(project.Id), name: Path.GetFileName(filePath), - loader: new FileTextLoader(filePath, null), + loader: new FileTextLoader(filePath, defaultEncoding: null, SourceHashAlgorithm.Sha256), filePath: filePath); OnDocumentAdded(docInfo); return CurrentSolution.GetDocument(docInfo.Id)!; diff --git a/src/Workspaces/CSharp/Portable/Workspace/LanguageServices/CSharpSyntaxTreeFactoryService.cs b/src/Workspaces/CSharp/Portable/Workspace/LanguageServices/CSharpSyntaxTreeFactoryService.cs index 5fe13d5036925..0cfe144137f30 100644 --- a/src/Workspaces/CSharp/Portable/Workspace/LanguageServices/CSharpSyntaxTreeFactoryService.cs +++ b/src/Workspaces/CSharp/Portable/Workspace/LanguageServices/CSharpSyntaxTreeFactoryService.cs @@ -69,10 +69,10 @@ public override bool OptionsDifferOnlyByPreprocessorDirectives(ParseOptions opti return csharpOptions1.WithPreprocessorSymbols(csharpOptions2.PreprocessorSymbolNames) == csharpOptions2; } - public override SyntaxTree CreateSyntaxTree(string filePath, ParseOptions options, Encoding encoding, SyntaxNode root) + public override SyntaxTree CreateSyntaxTree(string filePath, ParseOptions options, Encoding encoding, SourceHashAlgorithm checksumAlgorithm, SyntaxNode root) { options ??= GetDefaultParseOptions(); - return CSharpSyntaxTree.Create((CSharpSyntaxNode)root, (CSharpParseOptions)options, filePath, encoding); + return CSharpSyntaxTree.Create((CSharpSyntaxNode)root, checksumAlgorithm, (CSharpParseOptions)options, filePath, encoding); } public override SyntaxTree ParseSyntaxTree(string filePath, ParseOptions options, SourceText text, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/MSBuild/MSBuild/Constants/ItemNames.cs b/src/Workspaces/Core/MSBuild/MSBuild/Constants/ItemNames.cs index 2721447be4bc8..175c288e47c0d 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/Constants/ItemNames.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/Constants/ItemNames.cs @@ -17,5 +17,6 @@ internal static class ItemNames public const string Reference = nameof(Reference); public const string ReferencePath = nameof(ReferencePath); public const string VbcCommandLineArgs = nameof(VbcCommandLineArgs); + public const string IntermediateAssembly = nameof(IntermediateAssembly); } } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/Constants/PropertyNames.cs b/src/Workspaces/Core/MSBuild/MSBuild/Constants/PropertyNames.cs index edf8b2e1607a3..2691c30f7e5e5 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/Constants/PropertyNames.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/Constants/PropertyNames.cs @@ -67,5 +67,6 @@ internal static class PropertyNames public const string WarningLevel = nameof(WarningLevel); public const string WarningsAsErrors = nameof(WarningsAsErrors); public const string WarningsNotAsErrors = nameof(WarningsNotAsErrors); + public const string ChecksumAlgorithm = nameof(ChecksumAlgorithm); } } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs index e61eec56067ab..c9a83ccb0ee5a 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.MSBuild.Build; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.MSBuild @@ -302,23 +303,17 @@ private Task CreateProjectInfoAsync(ProjectFileInfo projectFileInfo return Task.FromResult( ProjectInfo.Create( - projectId, - version, - projectName, - assemblyName: assemblyName, - language: language, - filePath: projectPath, - outputFilePath: string.Empty, - outputRefFilePath: string.Empty, + new ProjectInfo.ProjectAttributes( + projectId, + version, + name: projectName, + assemblyName: assemblyName, + language: language, + compilationOutputFilePaths: new CompilationOutputInfo(projectFileInfo.IntermediateOutputFilePath), + checksumAlgorithm: SourceHashAlgorithm.Sha256, + filePath: projectPath), compilationOptions: compilationOptions, - parseOptions: parseOptions, - documents: SpecializedCollections.EmptyEnumerable(), - projectReferences: SpecializedCollections.EmptyEnumerable(), - metadataReferences: SpecializedCollections.EmptyEnumerable(), - analyzerReferences: SpecializedCollections.EmptyEnumerable(), - additionalDocuments: SpecializedCollections.EmptyEnumerable(), - isSubmission: false, - hostObjectType: null)); + parseOptions: parseOptions)); } return DoOperationAndReportProgressAsync(ProjectLoadOperation.Resolve, projectPath, projectFileInfo.TargetFramework, async () => @@ -366,9 +361,9 @@ private Task CreateProjectInfoAsync(ProjectFileInfo projectFileInfo .WithStrongNameProvider(new DesktopStrongNameProvider(commandLineArgs.KeyFileSearchPaths)) .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default); - var documents = CreateDocumentInfos(projectFileInfo.Documents, projectId, commandLineArgs.Encoding); - var additionalDocuments = CreateDocumentInfos(projectFileInfo.AdditionalDocuments, projectId, commandLineArgs.Encoding); - var analyzerConfigDocuments = CreateDocumentInfos(projectFileInfo.AnalyzerConfigDocuments, projectId, commandLineArgs.Encoding); + var documents = CreateDocumentInfos(projectFileInfo.Documents, projectId, commandLineArgs.Encoding, commandLineArgs.ChecksumAlgorithm); + var additionalDocuments = CreateDocumentInfos(projectFileInfo.AdditionalDocuments, projectId, commandLineArgs.Encoding, commandLineArgs.ChecksumAlgorithm); + var analyzerConfigDocuments = CreateDocumentInfos(projectFileInfo.AnalyzerConfigDocuments, projectId, commandLineArgs.Encoding, commandLineArgs.ChecksumAlgorithm); CheckForDuplicateDocuments(documents.Concat(additionalDocuments).Concat(analyzerConfigDocuments), projectPath, projectId); var analyzerReferences = ResolveAnalyzerReferences(commandLineArgs); @@ -376,14 +371,18 @@ private Task CreateProjectInfoAsync(ProjectFileInfo projectFileInfo var resolvedReferences = await ResolveReferencesAsync(projectId, projectFileInfo, commandLineArgs, cancellationToken).ConfigureAwait(false); return ProjectInfo.Create( - projectId, - version, - projectName, - assemblyName, - language, - projectPath, - outputFilePath: projectFileInfo.OutputFilePath, - outputRefFilePath: projectFileInfo.OutputRefFilePath, + new ProjectInfo.ProjectAttributes( + projectId, + version, + projectName, + assemblyName, + language, + compilationOutputFilePaths: new CompilationOutputInfo(projectFileInfo.IntermediateOutputFilePath), + checksumAlgorithm: commandLineArgs.ChecksumAlgorithm, + filePath: projectPath, + outputFilePath: projectFileInfo.OutputFilePath, + outputRefFilePath: projectFileInfo.OutputRefFilePath, + isSubmission: false), compilationOptions: compilationOptions, parseOptions: parseOptions, documents: documents, @@ -391,7 +390,6 @@ private Task CreateProjectInfoAsync(ProjectFileInfo projectFileInfo metadataReferences: resolvedReferences.MetadataReferences, analyzerReferences: analyzerReferences, additionalDocuments: additionalDocuments, - isSubmission: false, hostObjectType: null) .WithDefaultNamespace(projectFileInfo.DefaultNamespace) .WithAnalyzerConfigDocuments(analyzerConfigDocuments) @@ -441,7 +439,7 @@ private IEnumerable ResolveAnalyzerReferences(CommandLineArgu return commandLineArgs.ResolveAnalyzerReferences(analyzerLoader).Distinct(AnalyzerReferencePathComparer.Instance); } - private static ImmutableArray CreateDocumentInfos(IReadOnlyList documentFileInfos, ProjectId projectId, Encoding? encoding) + private static ImmutableArray CreateDocumentInfos(IReadOnlyList documentFileInfos, ProjectId projectId, Encoding? encoding, SourceHashAlgorithm checksumAlgorithm) { var results = ImmutableArray.CreateBuilder(); @@ -454,7 +452,7 @@ private static ImmutableArray CreateDocumentInfos(IReadOnlyList public string? FilePath { get; } + /// + /// The path to the intermediate output file this project generates. + /// + public string? IntermediateOutputFilePath { get; } + /// /// The path to the output file this project generates. /// @@ -99,6 +104,7 @@ private ProjectFileInfo( string? filePath, string? outputFilePath, string? outputRefFilePath, + string? intermediateOutputFilePath, string? defaultNamespace, string? targetFramework, ImmutableArray commandLineArgs, @@ -115,6 +121,7 @@ private ProjectFileInfo( this.FilePath = filePath; this.OutputFilePath = outputFilePath; this.OutputRefFilePath = outputRefFilePath; + this.IntermediateOutputFilePath = intermediateOutputFilePath; this.DefaultNamespace = defaultNamespace; this.TargetFramework = targetFramework; this.CommandLineArgs = commandLineArgs; @@ -130,6 +137,7 @@ public static ProjectFileInfo Create( string? filePath, string? outputFilePath, string? outputRefFilePath, + string? intermediateOutputFilePath, string? defaultNamespace, string? targetFramework, ImmutableArray commandLineArgs, @@ -144,6 +152,7 @@ public static ProjectFileInfo Create( filePath, outputFilePath, outputRefFilePath, + intermediateOutputFilePath, defaultNamespace, targetFramework, commandLineArgs, @@ -160,6 +169,7 @@ public static ProjectFileInfo CreateEmpty(string language, string? filePath, Dia filePath, outputFilePath: null, outputRefFilePath: null, + intermediateOutputFilePath: null, defaultNamespace: null, targetFramework: null, commandLineArgs: ImmutableArray.Empty, diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs index bc2e28422f81a..898b38d84eb42 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs @@ -26,10 +26,10 @@ public void SerializeSourceText(SerializableSourceText text, ObjectWriter writer { context.AddResource(text.Storage); - writer.WriteInt32((int)text.Storage.ChecksumAlgorithm); + writer.WriteByte(checked((byte)text.Storage.ChecksumAlgorithm)); writer.WriteEncoding(text.Storage.Encoding); + writer.WriteByte(checked((byte)SerializationKinds.MemoryMapFile)); - writer.WriteInt32((int)SerializationKinds.MemoryMapFile); writer.WriteString(text.Storage.Name); writer.WriteInt64(text.Storage.Offset); writer.WriteInt64(text.Storage.Size); @@ -38,9 +38,10 @@ public void SerializeSourceText(SerializableSourceText text, ObjectWriter writer { RoslynDebug.AssertNotNull(text.Text); - writer.WriteInt32((int)text.Text.ChecksumAlgorithm); + writer.WriteByte(checked((byte)text.Text.ChecksumAlgorithm)); writer.WriteEncoding(text.Text.Encoding); - writer.WriteInt32((int)SerializationKinds.Bits); + writer.WriteByte(checked((byte)SerializationKinds.Bits)); + text.Text.WriteTo(writer, cancellationToken); } } @@ -49,10 +50,10 @@ private SerializableSourceText DeserializeSerializableSourceText(ObjectReader re { cancellationToken.ThrowIfCancellationRequested(); - var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadInt32(); + var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadByte(); var encoding = (Encoding)reader.ReadValue(); + var kind = (SerializationKinds)reader.ReadByte(); - var kind = (SerializationKinds)reader.ReadInt32(); if (kind == SerializationKinds.MemoryMapFile) { var storage2 = (ITemporaryStorageService2)_storageService; @@ -73,7 +74,7 @@ private SerializableSourceText DeserializeSerializableSourceText(ObjectReader re } Contract.ThrowIfFalse(kind == SerializationKinds.Bits); - return new SerializableSourceText(SourceTextExtensions.ReadFrom(_textService, reader, encoding, cancellationToken)); + return new SerializableSourceText(SourceTextExtensions.ReadFrom(_textService, reader, encoding, checksumAlgorithm, cancellationToken)); } private SourceText DeserializeSourceText(ObjectReader reader, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/SourceTextExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/SourceTextExtensions.cs index 84e5de00012ef..187dee6cb5e3d 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/SourceTextExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/SourceTextExtensions.cs @@ -223,11 +223,11 @@ private static void WriteChunksTo(SourceText sourceText, ObjectWriter writer, in } } - public static SourceText ReadFrom(ITextFactoryService textService, ObjectReader reader, Encoding? encoding, CancellationToken cancellationToken) + public static SourceText ReadFrom(ITextFactoryService textService, ObjectReader reader, Encoding? encoding, SourceHashAlgorithm checksumAlgorithm, CancellationToken cancellationToken) { using var textReader = ObjectReaderTextReader.Create(reader); - return textService.CreateText(textReader, encoding, cancellationToken); + return textService.CreateText(textReader, encoding, checksumAlgorithm, cancellationToken); } private class ObjectReaderTextReader : TextReaderWithLength diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index fe9b687b0158e..48b157c13db16 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -224,7 +224,7 @@ public SourceText ReadText(CancellationToken cancellationToken) using var reader = CreateTextReaderFromTemporaryStorage(stream); // we pass in encoding we got from original source text even if it is null. - return _service._textFactory.CreateText(reader, _encoding, cancellationToken); + return _service._textFactory.CreateText(reader, _encoding, _checksumAlgorithm, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Workspace/CommandLineProject.cs b/src/Workspaces/Core/Portable/Workspace/CommandLineProject.cs index fb07382eded07..7f9695ac45925 100644 --- a/src/Workspaces/Core/Portable/Workspace/CommandLineProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/CommandLineProject.cs @@ -127,7 +127,7 @@ public static ProjectInfo CreateProjectInfo(string projectName, string language, name: name, folders: folders, sourceCodeKind: fileArg.IsScript ? SourceCodeKind.Script : SourceCodeKind.Regular, - loader: new FileTextLoader(absolutePath, commandLineArguments.Encoding), + loader: new FileTextLoader(absolutePath, commandLineArguments.Encoding, commandLineArguments.ChecksumAlgorithm), filePath: absolutePath); docs.Add(doc); @@ -154,7 +154,7 @@ public static ProjectInfo CreateProjectInfo(string projectName, string language, name: name, folders: folders, sourceCodeKind: SourceCodeKind.Regular, - loader: new FileTextLoader(absolutePath, commandLineArguments.Encoding), + loader: new FileTextLoader(absolutePath, commandLineArguments.Encoding, commandLineArguments.ChecksumAlgorithm), filePath: absolutePath); additionalDocs.Add(doc); @@ -170,11 +170,14 @@ public static ProjectInfo CreateProjectInfo(string projectName, string language, // TODO (tomat): what should be the assemblyName when compiling a netmodule? Should it be /moduleassemblyname var projectInfo = ProjectInfo.Create( - projectId, - VersionStamp.Create(), - projectName, - assemblyName, - language: language, + new ProjectInfo.ProjectAttributes( + id: projectId, + version: VersionStamp.Create(), + name: projectName, + assemblyName: assemblyName, + language: language, + compilationOutputFilePaths: new CompilationOutputInfo(commandLineArguments.GetOutputFilePath(commandLineArguments.OutputFileName)), + checksumAlgorithm: commandLineArguments.ChecksumAlgorithm), compilationOptions: commandLineArguments.CompilationOptions .WithXmlReferenceResolver(xmlFileResolver) .WithAssemblyIdentityComparer(assemblyIdentityComparer) @@ -183,9 +186,12 @@ public static ProjectInfo CreateProjectInfo(string projectName, string language, .WithMetadataReferenceResolver(new WorkspaceMetadataFileReferenceResolver(metadataService, new RelativePathResolver(ImmutableArray.Empty, projectDirectory))), parseOptions: commandLineArguments.ParseOptions, documents: docs, - additionalDocuments: additionalDocs, + projectReferences: null, metadataReferences: boundMetadataReferences, - analyzerReferences: boundAnalyzerReferences); + analyzerReferences: boundAnalyzerReferences, + additionalDocuments: additionalDocs, + analyzerConfigDocuments: null, + hostObjectType: null); return projectInfo; } diff --git a/src/Workspaces/Core/Portable/Workspace/FileTextLoader.cs b/src/Workspaces/Core/Portable/Workspace/FileTextLoader.cs index 40bd368fd8c6f..9fa6bb81c06cb 100644 --- a/src/Workspaces/Core/Portable/Workspace/FileTextLoader.cs +++ b/src/Workspaces/Core/Portable/Workspace/FileTextLoader.cs @@ -31,6 +31,11 @@ public class FileTextLoader : TextLoader /// public Encoding? DefaultEncoding { get; } + /// + /// Algorithm used to calculate content checksum. + /// + internal SourceHashAlgorithm ChecksumAlgorithm { get; } + /// /// Creates a content loader for specified file. /// @@ -43,11 +48,29 @@ public class FileTextLoader : TextLoader /// is null. /// is not an absolute path. public FileTextLoader(string path, Encoding? defaultEncoding) + : this(path, defaultEncoding, SourceHashAlgorithm.Sha1) + { + } + + /// + /// Creates a content loader for specified file. + /// + /// An absolute file path. + /// + /// Specifies an encoding to be used if the actual encoding can't be determined from the stream content (the stream doesn't start with Byte Order Mark). + /// If not specified auto-detect heuristics are used to determine the encoding. + /// Note that if the stream starts with Byte Order Mark the value of is ignored. + /// + /// Algorithm used to calculate content checksum. + /// is null. + /// is not an absolute path. + internal FileTextLoader(string path, Encoding? defaultEncoding, SourceHashAlgorithm checksumAlgorithm) { CompilerPathUtilities.RequireAbsolutePath(path, "path"); Path = path; DefaultEncoding = defaultEncoding; + ChecksumAlgorithm = checksumAlgorithm; } /// @@ -61,7 +84,7 @@ public FileTextLoader(string path, Encoding? defaultEncoding) protected virtual SourceText CreateText(Stream stream, Workspace workspace) { var factory = workspace.Services.GetRequiredService(); - return factory.CreateText(stream, DefaultEncoding); + return factory.CreateText(stream, DefaultEncoding, ChecksumAlgorithm, CancellationToken.None); } /// diff --git a/src/Workspaces/Core/Portable/Workspace/Host/SyntaxTreeFactory/AbstractSyntaxTreeFactoryService.AbstractRecoverableSyntaxRoot.cs b/src/Workspaces/Core/Portable/Workspace/Host/SyntaxTreeFactory/AbstractSyntaxTreeFactoryService.AbstractRecoverableSyntaxRoot.cs index fb9c2d864a6cb..0e60dee25c55c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/SyntaxTreeFactory/AbstractSyntaxTreeFactoryService.AbstractRecoverableSyntaxRoot.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/SyntaxTreeFactory/AbstractSyntaxTreeFactoryService.AbstractRecoverableSyntaxRoot.cs @@ -15,12 +15,13 @@ namespace Microsoft.CodeAnalysis.Host { internal abstract partial class AbstractSyntaxTreeFactoryService { - internal struct SyntaxTreeInfo + internal readonly struct SyntaxTreeInfo { public readonly string FilePath; public readonly ParseOptions Options; public readonly ValueSource TextSource; public readonly Encoding Encoding; + public readonly SourceHashAlgorithm ChecksumAlgorithm; public readonly int Length; public readonly bool ContainsDirectives; diff --git a/src/Workspaces/Core/Portable/Workspace/Host/SyntaxTreeFactory/AbstractSyntaxTreeFactoryService.cs b/src/Workspaces/Core/Portable/Workspace/Host/SyntaxTreeFactory/AbstractSyntaxTreeFactoryService.cs index 339942f3898c5..b2e17700bf442 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/SyntaxTreeFactory/AbstractSyntaxTreeFactoryService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/SyntaxTreeFactory/AbstractSyntaxTreeFactoryService.cs @@ -32,7 +32,7 @@ public AbstractSyntaxTreeFactoryService(HostLanguageServices languageServices) public abstract ParseOptions GetDefaultParseOptionsWithLatestLanguageVersion(); public abstract bool OptionsDifferOnlyByPreprocessorDirectives(ParseOptions options1, ParseOptions options2); public abstract ParseOptions TryParsePdbParseOptions(IReadOnlyDictionary metadata); - public abstract SyntaxTree CreateSyntaxTree(string filePath, ParseOptions options, Encoding encoding, SyntaxNode root); + public abstract SyntaxTree CreateSyntaxTree(string filePath, ParseOptions options, Encoding encoding, SourceHashAlgorithm checksumAlgorithm, SyntaxNode root); public abstract SyntaxTree ParseSyntaxTree(string filePath, ParseOptions options, SourceText text, CancellationToken cancellationToken); public abstract SyntaxTree CreateRecoverableTree(ProjectId cacheKey, string filePath, ParseOptions options, ValueSource text, Encoding encoding, SyntaxNode root); public abstract SyntaxNode DeserializeNodeFrom(Stream stream, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/SyntaxTreeFactory/ISyntaxTreeFactoryService.cs b/src/Workspaces/Core/Portable/Workspace/Host/SyntaxTreeFactory/ISyntaxTreeFactoryService.cs index d4e0f83d04d95..c862d75ffddf6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/SyntaxTreeFactory/ISyntaxTreeFactoryService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/SyntaxTreeFactory/ISyntaxTreeFactoryService.cs @@ -29,7 +29,7 @@ internal interface ISyntaxTreeFactoryService : ILanguageService bool OptionsDifferOnlyByPreprocessorDirectives(ParseOptions options1, ParseOptions options2); // new tree from root node - SyntaxTree CreateSyntaxTree(string? filePath, ParseOptions options, Encoding? encoding, SyntaxNode root); + SyntaxTree CreateSyntaxTree(string? filePath, ParseOptions options, Encoding? encoding, SourceHashAlgorithm checksumAlgorithm, SyntaxNode root); // new tree from text SyntaxTree ParseSyntaxTree(string? filePath, ParseOptions options, SourceText text, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TextFactory/ITextFactoryService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TextFactory/ITextFactoryService.cs index 6f555c5abd674..c9d93d35320cc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TextFactory/ITextFactoryService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TextFactory/ITextFactoryService.cs @@ -23,13 +23,14 @@ internal interface ITextFactoryService : IWorkspaceService /// If not specified auto-detect heuristics are used to determine the encoding. If these heuristics fail the decoding is assumed to be the system encoding. /// Note that if the stream starts with Byte Order Mark the value of is ignored. /// + /// Algorithm to calculate content checksum. /// Cancellation token. /// /// The stream content can't be decoded using the specified , or /// is null and the stream appears to be a binary file. /// /// An IO error occurred while reading from the stream. - SourceText CreateText(Stream stream, Encoding? defaultEncoding, CancellationToken cancellationToken = default); + SourceText CreateText(Stream stream, Encoding? defaultEncoding, SourceHashAlgorithm checksumAlgorithm, CancellationToken cancellationToken); /// /// Creates from a reader with given . @@ -37,8 +38,9 @@ internal interface ITextFactoryService : IWorkspaceService /// The to read the text from. /// Specifies an encoding for the SourceText. /// it could be null. but if null is given, it won't be able to calculate checksum + /// Algorithm to calculate content checksum. /// Cancellation token. - SourceText CreateText(TextReader reader, Encoding? encoding, CancellationToken cancellationToken = default); + SourceText CreateText(TextReader reader, Encoding? encoding, SourceHashAlgorithm checksumAlgorithm, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TextFactory/TextFactoryService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TextFactory/TextFactoryService.cs index ae3a638b03f00..ad36c04be9a20 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TextFactory/TextFactoryService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TextFactory/TextFactoryService.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.Host { [ExportWorkspaceService(typeof(ITextFactoryService), ServiceLayer.Default), Shared] - internal class TextFactoryService : ITextFactoryService + internal sealed class TextFactoryService : ITextFactoryService { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -22,23 +22,19 @@ public TextFactoryService() { } - public SourceText CreateText(Stream stream, Encoding? defaultEncoding, CancellationToken cancellationToken = default) + public SourceText CreateText(Stream stream, Encoding? defaultEncoding, SourceHashAlgorithm checksumAlgorithm, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - return EncodedStringText.Create(stream, defaultEncoding); + return EncodedStringText.Create(stream, defaultEncoding, checksumAlgorithm); } - public SourceText CreateText(TextReader reader, Encoding? encoding, CancellationToken cancellationToken = default) + public SourceText CreateText(TextReader reader, Encoding? encoding, SourceHashAlgorithm checksumAlgorithm, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var textReaderWithLength = reader as TextReaderWithLength; - if (textReaderWithLength != null) - { - return SourceText.From(textReaderWithLength, textReaderWithLength.Length, encoding); - } - - return SourceText.From(reader.ReadToEnd(), encoding); + return (reader is TextReaderWithLength textReaderWithLength) ? + SourceText.From(textReaderWithLength, textReaderWithLength.Length, encoding, checksumAlgorithm) : + SourceText.From(reader.ReadToEnd(), encoding, checksumAlgorithm); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index b7ec67e7680da..cf768e8a70977 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -370,7 +370,7 @@ private DocumentState SetParseOptions(ParseOptions options, bool onlyPreprocesso else if (existingTree.TryGetRoot(out var existingRoot) && !existingRoot.ContainsDirectives) { var treeFactory = _languageServices.GetRequiredService(); - newTree = treeFactory.CreateSyntaxTree(FilePath, options, existingTree.Encoding, existingRoot); + newTree = treeFactory.CreateSyntaxTree(FilePath, options, existingTree.Encoding, checksumAlgorithm, existingRoot); } if (newTree is not null) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs index 7932922e1a846..e5e764550992c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectInfo.cs @@ -5,10 +5,13 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Text.RegularExpressions; +using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis @@ -82,6 +85,11 @@ public sealed class ProjectInfo /// internal string? DefaultNamespace => Attributes.DefaultNamespace; + /// + /// Algorithm to calculate content checksum for debugging purposes. + /// + internal SourceHashAlgorithm ChecksumAlgorithm => Attributes.ChecksumAlgorithm; + /// /// True if this is a submission project for interactive sessions. /// @@ -219,22 +227,48 @@ public static ProjectInfo Create( Type? hostObjectType = null, string? outputRefFilePath = null) { - return new ProjectInfo( + return Create( new ProjectAttributes( id ?? throw new ArgumentNullException(nameof(id)), version, name ?? throw new ArgumentNullException(nameof(name)), assemblyName ?? throw new ArgumentNullException(nameof(assemblyName)), language ?? throw new ArgumentNullException(nameof(language)), - filePath, - outputFilePath, - outputRefFilePath, compilationOutputFilePaths: default, + checksumAlgorithm: SourceHashAlgorithm.Sha1, defaultNamespace: null, + filePath: filePath, + outputFilePath: outputFilePath, + outputRefFilePath: outputRefFilePath, + telemetryId: default, isSubmission, hasAllInformation: true, - runAnalyzers: true, - telemetryId: default), + runAnalyzers: true), + compilationOptions, + parseOptions, + documents, + projectReferences, + metadataReferences, + analyzerReferences, + additionalDocuments, + analyzerConfigDocuments: SpecializedCollections.EmptyBoxedImmutableArray(), + hostObjectType); + } + + internal static ProjectInfo Create( + ProjectAttributes attributes, + CompilationOptions? compilationOptions = null, + ParseOptions? parseOptions = null, + IEnumerable? documents = null, + IEnumerable? projectReferences = null, + IEnumerable? metadataReferences = null, + IEnumerable? analyzerReferences = null, + IEnumerable? additionalDocuments = null, + IEnumerable? analyzerConfigDocuments = null, + Type? hostObjectType = null) + { + return new ProjectInfo( + attributes, compilationOptions, parseOptions, PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(documents, nameof(documents)), @@ -242,7 +276,7 @@ public static ProjectInfo Create( PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(metadataReferences, nameof(metadataReferences)), PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(analyzerReferences, nameof(analyzerReferences)), PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(additionalDocuments, nameof(additionalDocuments)), - analyzerConfigDocuments: SpecializedCollections.EmptyBoxedImmutableArray(), + PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(analyzerConfigDocuments, nameof(analyzerConfigDocuments)), hostObjectType); } @@ -320,6 +354,9 @@ public ProjectInfo WithCompilationOutputInfo(in CompilationOutputInfo info) public ProjectInfo WithDefaultNamespace(string? defaultNamespace) => With(attributes: Attributes.With(defaultNamespace: defaultNamespace)); + internal ProjectInfo WithChecksumAlgorithm(SourceHashAlgorithm checksumAlgorithm) + => With(attributes: Attributes.With(checksumAlgorithm: checksumAlgorithm)); + internal ProjectInfo WithHasAllInformation(bool hasAllInformation) => With(attributes: Attributes.With(hasAllInformation: hasAllInformation)); @@ -367,9 +404,7 @@ internal sealed class ProjectAttributes : IChecksummedObject, IObjectWritable /// /// Matches names like: Microsoft.CodeAnalysis.Features (netcoreapp3.1) /// - private static readonly Regex s_projectNameAndFlavor = new Regex(@"^(?.*?)\s*\((?.*?)\)$", RegexOptions.Compiled); - - private Checksum? _lazyChecksum; + private static readonly Regex s_projectNameAndFlavor = new(@"^(?.*?)\s*\((?.*?)\)$", RegexOptions.Compiled); /// /// The unique Id of the project. @@ -386,14 +421,6 @@ internal sealed class ProjectAttributes : IChecksummedObject, IObjectWritable /// public string Name { get; } - /// - /// The name and flavor portions of the project broken out. For example, the project - /// Microsoft.CodeAnalysis.Workspace (netcoreapp3.1) would have the name - /// Microsoft.CodeAnalysis.Workspace and the flavor netcoreapp3.1. Values may be null if the name does not contain a flavor. - /// - public (string? name, string? flavor) NameAndFlavor { get; } - /// /// The name of the assembly that this project will create, without file extension. /// , @@ -429,6 +456,11 @@ internal sealed class ProjectAttributes : IChecksummedObject, IObjectWritable /// public string? DefaultNamespace { get; } + /// + /// Algorithm to calculate content checksum for debugging purposes. + /// + public SourceHashAlgorithm ChecksumAlgorithm { get; } + /// /// True if this is a submission project for interactive sessions. /// @@ -451,21 +483,45 @@ internal sealed class ProjectAttributes : IChecksummedObject, IObjectWritable /// public Guid TelemetryId { get; } + private StrongBox<(string?, string?)>? _lazyNameAndFlavor; + private Checksum? _lazyChecksum; + + /// + /// The name and flavor portions of the project broken out. For example, the project + /// Microsoft.CodeAnalysis.Workspace (netcoreapp3.1) would have the name + /// Microsoft.CodeAnalysis.Workspace and the flavor netcoreapp3.1. Values may be null if the name does not contain a flavor. + /// + public (string? name, string? flavor) NameAndFlavor + { + get + { + if (_lazyNameAndFlavor == null) + { + var match = s_projectNameAndFlavor.Match(Name); + _lazyNameAndFlavor = new StrongBox<(string?, string?)>(match.Success ? (match.Groups["name"].Value, match.Groups["flavor"].Value) : default); + } + + return _lazyNameAndFlavor.Value; + } + } + public ProjectAttributes( ProjectId id, VersionStamp version, string name, string assemblyName, string language, - string? filePath, - string? outputFilePath, - string? outputRefFilePath, CompilationOutputInfo compilationOutputFilePaths, - string? defaultNamespace, - bool isSubmission, - bool hasAllInformation, - bool runAnalyzers, - Guid telemetryId) + SourceHashAlgorithm checksumAlgorithm, + string? defaultNamespace = null, + string? filePath = null, + string? outputFilePath = null, + string? outputRefFilePath = null, + Guid telemetryId = default, + bool isSubmission = false, + bool hasAllInformation = true, + bool runAnalyzers = true) { Id = id; Name = name; @@ -478,14 +534,11 @@ public ProjectAttributes( OutputRefFilePath = outputRefFilePath; CompilationOutputInfo = compilationOutputFilePaths; DefaultNamespace = defaultNamespace; + ChecksumAlgorithm = checksumAlgorithm; IsSubmission = isSubmission; HasAllInformation = hasAllInformation; RunAnalyzers = runAnalyzers; TelemetryId = telemetryId; - - var match = s_projectNameAndFlavor.Match(Name); - if (match?.Success == true) - NameAndFlavor = (match.Groups["name"].Value, match.Groups["flavor"].Value); } public ProjectAttributes With( @@ -498,6 +551,7 @@ public ProjectAttributes With( Optional outputRefPath = default, Optional compilationOutputInfo = default, Optional defaultNamespace = default, + Optional checksumAlgorithm = default, Optional isSubmission = default, Optional hasAllInformation = default, Optional runAnalyzers = default, @@ -507,11 +561,12 @@ public ProjectAttributes With( var newName = name ?? Name; var newAssemblyName = assemblyName ?? AssemblyName; var newLanguage = language ?? Language; - var newFilepath = filePath.HasValue ? filePath.Value : FilePath; + var newFilePath = filePath.HasValue ? filePath.Value : FilePath; var newOutputPath = outputPath.HasValue ? outputPath.Value : OutputFilePath; var newOutputRefPath = outputRefPath.HasValue ? outputRefPath.Value : OutputRefFilePath; var newCompilationOutputPaths = compilationOutputInfo.HasValue ? compilationOutputInfo.Value : CompilationOutputInfo; var newDefaultNamespace = defaultNamespace.HasValue ? defaultNamespace.Value : DefaultNamespace; + var newChecksumAlgorithm = checksumAlgorithm.HasValue ? checksumAlgorithm.Value : ChecksumAlgorithm; var newIsSubmission = isSubmission.HasValue ? isSubmission.Value : IsSubmission; var newHasAllInformation = hasAllInformation.HasValue ? hasAllInformation.Value : HasAllInformation; var newRunAnalyzers = runAnalyzers.HasValue ? runAnalyzers.Value : RunAnalyzers; @@ -521,11 +576,12 @@ public ProjectAttributes With( newName == Name && newAssemblyName == AssemblyName && newLanguage == Language && - newFilepath == FilePath && + newFilePath == FilePath && newOutputPath == OutputFilePath && newOutputRefPath == OutputRefFilePath && newCompilationOutputPaths == CompilationOutputInfo && newDefaultNamespace == DefaultNamespace && + newChecksumAlgorithm == ChecksumAlgorithm && newIsSubmission == IsSubmission && newHasAllInformation == HasAllInformation && newRunAnalyzers == RunAnalyzers && @@ -540,15 +596,16 @@ public ProjectAttributes With( newName, newAssemblyName, newLanguage, - newFilepath, - newOutputPath, - newOutputRefPath, newCompilationOutputPaths, - newDefaultNamespace, + newChecksumAlgorithm, + defaultNamespace: newDefaultNamespace, + filePath: newFilePath, + outputFilePath: newOutputPath, + outputRefFilePath: newOutputRefPath, + newTelemetryId, newIsSubmission, newHasAllInformation, - newRunAnalyzers, - newTelemetryId); + newRunAnalyzers); } bool IObjectWritable.ShouldReuseInSerialization => true; @@ -568,6 +625,7 @@ public void WriteTo(ObjectWriter writer) writer.WriteString(OutputRefFilePath); CompilationOutputInfo.WriteTo(writer); writer.WriteString(DefaultNamespace); + writer.WriteByte(checked((byte)ChecksumAlgorithm)); writer.WriteBoolean(IsSubmission); writer.WriteBoolean(HasAllInformation); writer.WriteBoolean(RunAnalyzers); @@ -590,6 +648,7 @@ public static ProjectAttributes ReadFrom(ObjectReader reader) var outputRefFilePath = reader.ReadString(); var compilationOutputFilePaths = CompilationOutputInfo.ReadFrom(reader); var defaultNamespace = reader.ReadString(); + var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadByte(); var isSubmission = reader.ReadBoolean(); var hasAllInformation = reader.ReadBoolean(); var runAnalyzers = reader.ReadBoolean(); @@ -598,18 +657,19 @@ public static ProjectAttributes ReadFrom(ObjectReader reader) return new ProjectAttributes( projectId, VersionStamp.Create(), - name, - assemblyName, - language, - filePath, - outputFilePath, - outputRefFilePath, + name: name, + assemblyName: assemblyName, + language: language, compilationOutputFilePaths, - defaultNamespace, - isSubmission, - hasAllInformation, - runAnalyzers, - telemetryId); + checksumAlgorithm, + defaultNamespace: defaultNamespace, + filePath: filePath, + outputFilePath: outputFilePath, + outputRefFilePath: outputRefFilePath, + telemetryId, + isSubmission: isSubmission, + hasAllInformation: hasAllInformation, + runAnalyzers: runAnalyzers); } Checksum IChecksummedObject.Checksum diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs index 9fb208910e552..e7e124af93259 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -19,6 +20,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Serialization; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis @@ -613,6 +615,9 @@ public async Task GetSemanticVersionAsync(CancellationToken cancel [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] public string? DefaultNamespace => this.ProjectInfo.DefaultNamespace; + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] + public SourceHashAlgorithm ChecksumAlgorithm => this.ProjectInfo.ChecksumAlgorithm; + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] public HostLanguageServices LanguageServices => _languageServices; @@ -686,38 +691,44 @@ private ProjectState With( analyzerConfigSet ?? _lazyAnalyzerConfigOptions); } - private ProjectInfo.ProjectAttributes Attributes + internal ProjectInfo.ProjectAttributes Attributes => ProjectInfo.Attributes; - private ProjectState WithAttributes(ProjectInfo.ProjectAttributes attributes) + private ProjectState WithRawAttributes(ProjectInfo.ProjectAttributes attributes) => With(projectInfo: ProjectInfo.With(attributes: attributes)); + public ProjectState WithAttributes(ProjectInfo.ProjectAttributes attributes) + => (attributes == Attributes) ? this : WithRawAttributes(attributes.With(version: Version.GetNewerVersion())); + public ProjectState WithName(string name) - => (name == Name) ? this : WithAttributes(Attributes.With(name: name, version: Version.GetNewerVersion())); + => (name == Name) ? this : WithRawAttributes(Attributes.With(name: name, version: Version.GetNewerVersion())); public ProjectState WithFilePath(string? filePath) - => (filePath == FilePath) ? this : WithAttributes(Attributes.With(filePath: filePath, version: Version.GetNewerVersion())); + => (filePath == FilePath) ? this : WithRawAttributes(Attributes.With(filePath: filePath, version: Version.GetNewerVersion())); public ProjectState WithAssemblyName(string assemblyName) - => (assemblyName == AssemblyName) ? this : WithAttributes(Attributes.With(assemblyName: assemblyName, version: Version.GetNewerVersion())); + => (assemblyName == AssemblyName) ? this : WithRawAttributes(Attributes.With(assemblyName: assemblyName, version: Version.GetNewerVersion())); public ProjectState WithOutputFilePath(string? outputFilePath) - => (outputFilePath == OutputFilePath) ? this : WithAttributes(Attributes.With(outputPath: outputFilePath, version: Version.GetNewerVersion())); + => (outputFilePath == OutputFilePath) ? this : WithRawAttributes(Attributes.With(outputPath: outputFilePath, version: Version.GetNewerVersion())); public ProjectState WithOutputRefFilePath(string? outputRefFilePath) - => (outputRefFilePath == OutputRefFilePath) ? this : WithAttributes(Attributes.With(outputRefPath: outputRefFilePath, version: Version.GetNewerVersion())); + => (outputRefFilePath == OutputRefFilePath) ? this : WithRawAttributes(Attributes.With(outputRefPath: outputRefFilePath, version: Version.GetNewerVersion())); public ProjectState WithCompilationOutputInfo(in CompilationOutputInfo info) - => (info == CompilationOutputInfo) ? this : WithAttributes(Attributes.With(compilationOutputInfo: info, version: Version.GetNewerVersion())); + => (info == CompilationOutputInfo) ? this : WithRawAttributes(Attributes.With(compilationOutputInfo: info, version: Version.GetNewerVersion())); public ProjectState WithDefaultNamespace(string? defaultNamespace) - => (defaultNamespace == DefaultNamespace) ? this : WithAttributes(Attributes.With(defaultNamespace: defaultNamespace, version: Version.GetNewerVersion())); + => (defaultNamespace == DefaultNamespace) ? this : WithRawAttributes(Attributes.With(defaultNamespace: defaultNamespace, version: Version.GetNewerVersion())); + + public ProjectState WithChecksumAlgorithm(SourceHashAlgorithm checksumAlgorithm) + => (checksumAlgorithm == ChecksumAlgorithm) ? this : WithRawAttributes(Attributes.With(checksumAlgorithm: checksumAlgorithm, version: Version.GetNewerVersion())); public ProjectState WithHasAllInformation(bool hasAllInformation) - => (hasAllInformation == HasAllInformation) ? this : WithAttributes(Attributes.With(hasAllInformation: hasAllInformation, version: Version.GetNewerVersion())); + => (hasAllInformation == HasAllInformation) ? this : WithRawAttributes(Attributes.With(hasAllInformation: hasAllInformation, version: Version.GetNewerVersion())); public ProjectState WithRunAnalyzers(bool runAnalyzers) - => (runAnalyzers == RunAnalyzers) ? this : WithAttributes(Attributes.With(runAnalyzers: runAnalyzers, version: Version.GetNewerVersion())); + => (runAnalyzers == RunAnalyzers) ? this : WithRawAttributes(Attributes.With(runAnalyzers: runAnalyzers, version: Version.GetNewerVersion())); public ProjectState WithCompilationOptions(CompilationOptions options) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index c45395fe1b769..b552833fac743 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -419,6 +419,22 @@ public Solution WithProjectDefaultNamespace(ProjectId projectId, string? default return new Solution(newState); } + /// + /// Creates a new solution instance with the project specified updated to have the specified attributes. + /// + internal Solution WithProjectChecksumAlgorithm(ProjectId projectId, SourceHashAlgorithm checksumAlgorithm) + { + CheckContainsProject(projectId); + + var newState = _state.WithProjectChecksumAlgorithm(projectId, checksumAlgorithm); + if (newState == _state) + { + return this; + } + + return new Solution(newState); + } + /// /// Creates a new solution instance with the project specified updated to have the name. /// @@ -536,6 +552,23 @@ internal Solution WithRunAnalyzers(ProjectId projectId, bool runAnalyzers) return new Solution(newState); } + /// + /// Creates a new solution instance with the project specified updated to have the attributes, + /// except for which is auto-incremented. + /// + internal Solution WithProjectAttributes(ProjectId projectId, ProjectInfo.ProjectAttributes attributes) + { + CheckContainsProject(projectId); + + var newState = _state.WithProjectAttributes(projectId, attributes); + if (newState == _state) + { + return this; + } + + return new Solution(newState); + } + /// /// Creates a new solution instance with the project documents in the order by the specified document ids. /// The specified document ids must be the same as what is already in the project; no adding or removing is allowed. @@ -962,7 +995,18 @@ private static SourceCodeKind GetSourceCodeKind(ProjectState project) /// document instance defined by its name and text. /// public Solution AddDocument(DocumentId documentId, string name, string text, IEnumerable? folders = null, string? filePath = null) - => this.AddDocument(documentId, name, SourceText.From(text), folders, filePath); + { + if (documentId == null) + throw new ArgumentNullException(nameof(documentId)); + + if (name == null) + throw new ArgumentNullException(nameof(name)); + + var project = GetRequiredProjectState(documentId.ProjectId); + var sourceText = SourceText.From(text, encoding: null, checksumAlgorithm: project.ChecksumAlgorithm); + + return AddDocumentImpl(project, documentId, name, sourceText, folders, filePath, isGenerated: false); + } /// /// Creates a new solution instance with the corresponding project updated to include a new @@ -971,48 +1015,46 @@ public Solution AddDocument(DocumentId documentId, string name, string text, IEn public Solution AddDocument(DocumentId documentId, string name, SourceText text, IEnumerable? folders = null, string? filePath = null, bool isGenerated = false) { if (documentId == null) - { throw new ArgumentNullException(nameof(documentId)); - } if (name == null) - { throw new ArgumentNullException(nameof(name)); - } if (text == null) - { throw new ArgumentNullException(nameof(text)); - } - var project = _state.GetProjectState(documentId.ProjectId); + var project = GetRequiredProjectState(documentId.ProjectId); + return AddDocumentImpl(project, documentId, name, text, folders, filePath, isGenerated); + } - if (project == null) - { - throw new InvalidOperationException(string.Format(WorkspacesResources._0_is_not_part_of_the_workspace, documentId.ProjectId)); - } + /// + /// Creates a new solution instance with the corresponding project updated to include a new + /// document instance defined by its name and root . + /// + public Solution AddDocument(DocumentId documentId, string name, SyntaxNode syntaxRoot, IEnumerable? folders = null, string? filePath = null, bool isGenerated = false, PreservationMode preservationMode = PreservationMode.PreserveValue) + { + if (documentId == null) + throw new ArgumentNullException(nameof(documentId)); - var version = VersionStamp.Create(); - var loader = TextLoader.From(TextAndVersion.Create(text, version, name)); + if (name == null) + throw new ArgumentNullException(nameof(name)); + + var project = GetRequiredProjectState(documentId.ProjectId); + var sourceText = SourceText.From(string.Empty, encoding: null, project.ChecksumAlgorithm); + + return AddDocumentImpl(project, documentId, name, sourceText, folders, filePath, isGenerated). + WithDocumentSyntaxRoot(documentId, syntaxRoot, preservationMode); + } - var info = DocumentInfo.Create( + private Solution AddDocumentImpl(ProjectState project, DocumentId documentId, string name, SourceText text, IEnumerable? folders, string? filePath, bool isGenerated) + => AddDocument(DocumentInfo.Create( documentId, name: name, folders: folders, sourceCodeKind: GetSourceCodeKind(project), - loader: loader, + loader: TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create(), name)), filePath: filePath, - isGenerated: isGenerated); - - return this.AddDocument(info); - } - - /// - /// Creates a new solution instance with the corresponding project updated to include a new - /// document instance defined by its name and root . - /// - public Solution AddDocument(DocumentId documentId, string name, SyntaxNode syntaxRoot, IEnumerable? folders = null, string? filePath = null, bool isGenerated = false, PreservationMode preservationMode = PreservationMode.PreserveValue) - => this.AddDocument(documentId, name, SourceText.From(string.Empty), folders, filePath, isGenerated).WithDocumentSyntaxRoot(documentId, syntaxRoot, preservationMode); + isGenerated: isGenerated)); /// /// Creates a new solution instance with the project updated to include a new document with @@ -1021,35 +1063,17 @@ public Solution AddDocument(DocumentId documentId, string name, SyntaxNode synta public Solution AddDocument(DocumentId documentId, string name, TextLoader loader, IEnumerable? folders = null) { if (documentId == null) - { throw new ArgumentNullException(nameof(documentId)); - } if (name == null) - { throw new ArgumentNullException(nameof(name)); - } if (loader == null) - { throw new ArgumentNullException(nameof(loader)); - } - - var project = _state.GetProjectState(documentId.ProjectId); - - if (project == null) - { - throw new InvalidOperationException(string.Format(WorkspacesResources._0_is_not_part_of_the_workspace, documentId.ProjectId)); - } - var info = DocumentInfo.Create( - documentId, - name: name, - folders: folders, - sourceCodeKind: GetSourceCodeKind(project), - loader: loader); + var project = GetRequiredProjectState(documentId.ProjectId); - return this.AddDocument(info); + return AddDocument(DocumentInfo.Create(documentId, name, folders, GetSourceCodeKind(project), loader)); } /// @@ -1151,13 +1175,7 @@ public Solution AddAnalyzerConfigDocument(DocumentId documentId, string name, So private DocumentInfo CreateDocumentInfo(DocumentId documentId, string name, SourceText text, IEnumerable? folders, string? filePath) { - var project = _state.GetProjectState(documentId.ProjectId); - - if (project is null) - { - throw new InvalidOperationException(string.Format(WorkspacesResources._0_is_not_part_of_the_workspace, documentId.ProjectId)); - } - + var project = GetRequiredProjectState(documentId.ProjectId); var version = VersionStamp.Create(); var loader = TextLoader.From(TextAndVersion.Create(text, version, name)); @@ -1170,6 +1188,9 @@ private DocumentInfo CreateDocumentInfo(DocumentId documentId, string name, Sour filePath: filePath); } + private ProjectState GetRequiredProjectState(ProjectId projectId) + => _state.GetProjectState(projectId) ?? throw new InvalidOperationException(string.Format(WorkspacesResources._0_is_not_part_of_the_workspace, projectId)); + /// /// Creates a new Solution instance that contains a new compiler configuration document like a .editorconfig file. /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index ad8f1c40010ef..be181cda09564 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -726,6 +726,22 @@ public SolutionState WithProjectDefaultNamespace(ProjectId projectId, string? de return ForkProject(newProject); } + /// + /// Creates a new solution instance with the project specified updated to have the name. + /// + public SolutionState WithProjectChecksumAlgorithm(ProjectId projectId, SourceHashAlgorithm checksumAlgorithm) + { + var oldProject = GetRequiredProjectState(projectId); + var newProject = oldProject.WithChecksumAlgorithm(checksumAlgorithm); + + if (oldProject == newProject) + { + return this; + } + + return ForkProject(newProject); + } + /// /// Creates a new solution instance with the project specified updated to have the name. /// @@ -837,6 +853,23 @@ public SolutionState WithRunAnalyzers(ProjectId projectId, bool runAnalyzers) return ForkProject(newProject); } + /// + /// Create a new solution instance with the project specified updated to have + /// the specified attributes, except for which is auto-incremented. + /// + public SolutionState WithProjectAttributes(ProjectId projectId, ProjectInfo.ProjectAttributes attributes) + { + var oldProject = GetRequiredProjectState(projectId); + var newProject = oldProject.WithAttributes(attributes); + + if (oldProject == newProject) + { + return this; + } + + return ForkProject(newProject); + } + /// /// Create a new solution instance with the project specified updated to include /// the specified project references. diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 8fd316e7f0aaf..7015aaeb65ef2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -1647,25 +1647,16 @@ private static void CheckNoChanges(Solution oldSolution, Solution newSolution) private static ProjectInfo CreateProjectInfo(Project project) { return ProjectInfo.Create( - project.Id, - VersionStamp.Create(), - project.Name, - project.AssemblyName, - project.Language, - project.FilePath, - project.OutputFilePath, + project.State.Attributes.With(version: VersionStamp.Create()), project.CompilationOptions, project.ParseOptions, project.Documents.Select(CreateDocumentInfoWithText), project.ProjectReferences, project.MetadataReferences, project.AnalyzerReferences, - project.AdditionalDocuments.Select(CreateDocumentInfoWithText), - project.IsSubmission, - project.State.HostObjectType, - project.OutputRefFilePath) - .WithDefaultNamespace(project.DefaultNamespace) - .WithAnalyzerConfigDocuments(project.AnalyzerConfigDocuments.Select(CreateDocumentInfoWithText)); + additionalDocuments: project.AdditionalDocuments.Select(CreateDocumentInfoWithText), + analyzerConfigDocuments: project.AnalyzerConfigDocuments.Select(CreateDocumentInfoWithText), + hostObjectType: project.State.HostObjectType); } private static DocumentInfo CreateDocumentInfoWithText(TextDocument doc) diff --git a/src/Workspaces/CoreTest/SolutionTests/DocumentInfoTests.cs b/src/Workspaces/CoreTest/SolutionTests/DocumentInfoTests.cs index 572bb50a9abf4..0a160d0b4602a 100644 --- a/src/Workspaces/CoreTest/SolutionTests/DocumentInfoTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/DocumentInfoTests.cs @@ -8,6 +8,7 @@ using System.Collections.Immutable; using System.IO; using System.Linq; +using Microsoft.CodeAnalysis.Text; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests @@ -28,7 +29,7 @@ public void Create_Errors() [Fact] public void Create() { - var loader = new FileTextLoader(Path.GetTempPath(), defaultEncoding: null); + var loader = new FileTextLoader(Path.GetTempPath(), defaultEncoding: null, SourceHashAlgorithm.Sha256); var id = DocumentId.CreateNewId(ProjectId.CreateNewId()); var info = DocumentInfo.Create( @@ -83,7 +84,7 @@ public void TestProperties() SolutionTestHelpers.TestProperty(instance, (old, value) => old.WithId(value), opt => opt.Id, documentId, defaultThrows: true); SolutionTestHelpers.TestProperty(instance, (old, value) => old.WithName(value), opt => opt.Name, "New", defaultThrows: true); SolutionTestHelpers.TestProperty(instance, (old, value) => old.WithSourceCodeKind(value), opt => opt.SourceCodeKind, SourceCodeKind.Script); - SolutionTestHelpers.TestProperty(instance, (old, value) => old.WithTextLoader(value), opt => opt.TextLoader, (TextLoader)new FileTextLoader(Path.GetTempPath(), defaultEncoding: null)); + SolutionTestHelpers.TestProperty(instance, (old, value) => old.WithTextLoader(value), opt => opt.TextLoader, (TextLoader)new FileTextLoader(Path.GetTempPath(), defaultEncoding: null, SourceHashAlgorithm.Sha256)); SolutionTestHelpers.TestListProperty(instance, (old, value) => old.WithFolders(value), opt => opt.Folders, "folder", allowDuplicates: true); } diff --git a/src/Workspaces/CoreTest/SolutionTests/ProjectInfoTests.cs b/src/Workspaces/CoreTest/SolutionTests/ProjectInfoTests.cs index 22ee39756ca65..cacdf41fffdcb 100644 --- a/src/Workspaces/CoreTest/SolutionTests/ProjectInfoTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/ProjectInfoTests.cs @@ -8,6 +8,7 @@ using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; @@ -189,8 +190,9 @@ public void TestProperties() SolutionTestHelpers.TestProperty(instance, (old, value) => old.WithOutputRefFilePath(value), opt => opt.OutputRefFilePath, "New"); SolutionTestHelpers.TestProperty(instance, (old, value) => old.WithCompilationOutputInfo(value), opt => opt.CompilationOutputInfo, new CompilationOutputInfo("NewPath")); SolutionTestHelpers.TestProperty(instance, (old, value) => old.WithDefaultNamespace(value), opt => opt.DefaultNamespace, "New"); - SolutionTestHelpers.TestProperty(instance, (old, value) => old.WithHasAllInformation(value), opt => opt.HasAllInformation, true); - SolutionTestHelpers.TestProperty(instance, (old, value) => old.WithRunAnalyzers(value), opt => opt.RunAnalyzers, true); + SolutionTestHelpers.TestProperty(instance, (old, value) => old.WithChecksumAlgorithm(value), opt => opt.ChecksumAlgorithm, SourceHashAlgorithm.None); + SolutionTestHelpers.TestProperty(instance, (old, value) => old.WithHasAllInformation(value), opt => opt.HasAllInformation, false); + SolutionTestHelpers.TestProperty(instance, (old, value) => old.WithRunAnalyzers(value), opt => opt.RunAnalyzers, false); SolutionTestHelpers.TestListProperty(instance, (old, value) => old.WithDocuments(value), opt => opt.Documents, documentInfo, allowDuplicates: false); SolutionTestHelpers.TestListProperty(instance, (old, value) => old.WithAdditionalDocuments(value), opt => opt.AdditionalDocuments, documentInfo, allowDuplicates: false); diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs index c5b03dfeefee6..c2eb301cc8756 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs @@ -37,7 +37,7 @@ public static Workspace CreateWorkspaceWithPartialSemanticsAndWeakCompilations() public static void TestProperty(T instance, Func factory, Func getter, TValue validNonDefaultValue, bool defaultThrows = false) where T : class { - Assert.NotEqual(default, validNonDefaultValue); + Assert.NotEqual(getter(instance), validNonDefaultValue); var instanceWithValue = factory(instance, validNonDefaultValue); Assert.Equal(validNonDefaultValue, getter(instanceWithValue)); diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index b1638bd41cbfb..6ad33315983b6 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -36,6 +36,8 @@ using CS = Microsoft.CodeAnalysis.CSharp; using static Microsoft.CodeAnalysis.UnitTests.SolutionTestHelpers; using Microsoft.CodeAnalysis.Indentation; +using System.Reflection.Metadata; +using Microsoft.VisualStudio.Experimentation; namespace Microsoft.CodeAnalysis.UnitTests { @@ -555,7 +557,7 @@ public void WithProjectOutputFilePath() SolutionTestHelpers.TestProperty( solution, (s, value) => s.WithProjectOutputFilePath(projectId, value), - s => s.GetProject(projectId)!.OutputFilePath, + s => s.GetRequiredProject(projectId).OutputFilePath, (string?)path, defaultThrows: false); @@ -578,7 +580,7 @@ public void WithProjectOutputRefFilePath() SolutionTestHelpers.TestProperty( solution, (s, value) => s.WithProjectOutputRefFilePath(projectId, value), - s => s.GetProject(projectId)!.OutputRefFilePath, + s => s.GetRequiredProject(projectId).OutputRefFilePath, (string?)path, defaultThrows: false); @@ -601,7 +603,7 @@ public void WithProjectCompilationOutputInfo() SolutionTestHelpers.TestProperty( solution, (s, value) => s.WithProjectCompilationOutputInfo(projectId, value), - s => s.GetProject(projectId)!.CompilationOutputInfo, + s => s.GetRequiredProject(projectId).CompilationOutputInfo, new CompilationOutputInfo(path), defaultThrows: false); @@ -615,8 +617,8 @@ public void WithProjectDefaultNamespace() var projectId = ProjectId.CreateNewId(); using var workspace = CreateWorkspace(); - var solution = workspace.CurrentSolution - .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp); + var solution = workspace.CurrentSolution. + AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp); // any character is allowed var defaultNamespace = "\0<>a/b/*"; @@ -624,7 +626,7 @@ public void WithProjectDefaultNamespace() SolutionTestHelpers.TestProperty( solution, (s, value) => s.WithProjectDefaultNamespace(projectId, value), - s => s.GetProject(projectId)!.DefaultNamespace, + s => s.GetRequiredProject(projectId).DefaultNamespace, (string?)defaultNamespace, defaultThrows: false); @@ -632,6 +634,23 @@ public void WithProjectDefaultNamespace() Assert.Throws(() => solution.WithProjectDefaultNamespace(ProjectId.CreateNewId(), "x")); } + [Fact] + public void WithProjectChecksumAlgorithm() + { + var projectId = ProjectId.CreateNewId(); + + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution. + AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp); + + SolutionTestHelpers.TestProperty( + solution, + (s, value) => s.WithProjectChecksumAlgorithm(projectId, value), + s => s.GetRequiredProject(projectId).State.ChecksumAlgorithm, + SourceHashAlgorithm.Sha256, + defaultThrows: false); + } + [Fact] public void WithProjectName() { @@ -647,7 +666,7 @@ public void WithProjectName() SolutionTestHelpers.TestProperty( solution, (s, value) => s.WithProjectName(projectId, value), - s => s.GetProject(projectId)!.Name, + s => s.GetRequiredProject(projectId).Name, projectName, defaultThrows: true); @@ -670,7 +689,7 @@ public void WithProjectFilePath() SolutionTestHelpers.TestProperty( solution, (s, value) => s.WithProjectFilePath(projectId, value), - s => s.GetProject(projectId)!.FilePath, + s => s.GetRequiredProject(projectId).FilePath, (string?)path, defaultThrows: false); @@ -734,7 +753,7 @@ public void WithProjectParseOptions() SolutionTestHelpers.TestProperty( solution, (s, value) => s.WithProjectParseOptions(projectId, value), - s => s.GetProject(projectId)!.ParseOptions!, + s => s.GetRequiredProject(projectId).ParseOptions!, (ParseOptions)options, defaultThrows: true); @@ -1177,6 +1196,120 @@ public void RemoveAnalyzerReference() Assert.Throws(() => solution.RemoveAnalyzerReference(new TestAnalyzerReference())); } + [Fact] + public void AddDocument_Loader() + { + var projectId = ProjectId.CreateNewId(); + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution.AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp). + WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha256). + WithProjectParseOptions(projectId, new CSharpParseOptions(kind: SourceCodeKind.Script)); + + var loader = new TestTextLoader(); + var documentId = DocumentId.CreateNewId(projectId); + var folders = new[] { "folder1", "folder2" }; + + var solution2 = solution.AddDocument(documentId, "name", loader, folders); + var document = solution2.GetRequiredDocument(documentId); + AssertEx.Equal(folders, document.Folders); + Assert.Equal(SourceCodeKind.Script, document.SourceCodeKind); + + Assert.Throws("documentId", () => solution.AddDocument(documentId: null!, "name", loader)); + Assert.Throws("name", () => solution.AddDocument(documentId, name: null!, loader)); + Assert.Throws("loader", () => solution.AddDocument(documentId, "name", loader: null!)); + Assert.Throws(() => solution.AddDocument(documentId: DocumentId.CreateNewId(ProjectId.CreateNewId()), "name", loader)); + } + + [Fact] + public void AddDocument_Text() + { + var projectId = ProjectId.CreateNewId(); + using var workspace = CreateWorkspace(); + + var solution = workspace.CurrentSolution.AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp). + WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha256). + WithProjectParseOptions(projectId, new CSharpParseOptions(kind: SourceCodeKind.Script)); + + var documentId = DocumentId.CreateNewId(projectId); + var filePath = Path.Combine(TempRoot.Root, "x.cs"); + var folders = new[] { "folder1", "folder2" }; + + var solution2 = solution.AddDocument(documentId, "name", "text", folders, filePath); + var document = solution2.GetRequiredDocument(documentId); + var sourceText = document.GetTextSynchronously(default); + + Assert.Equal("text", sourceText.ToString()); + Assert.Equal(SourceHashAlgorithm.Sha256, sourceText.ChecksumAlgorithm); + AssertEx.Equal(folders, document.Folders); + Assert.Equal(filePath, document.FilePath); + Assert.False(document.State.Attributes.IsGenerated); + Assert.Equal(SourceCodeKind.Script, document.SourceCodeKind); + + Assert.Throws("documentId", () => solution.AddDocument(documentId: null!, "name", "text")); + Assert.Throws("name", () => solution.AddDocument(documentId, name: null!, "text")); + Assert.Throws("text", () => solution.AddDocument(documentId, "name", text: (string)null!)); + Assert.Throws(() => solution.AddDocument(documentId: DocumentId.CreateNewId(ProjectId.CreateNewId()), "name", "text")); + } + + [Fact] + public void AddDocument_SourceText() + { + var projectId = ProjectId.CreateNewId(); + using var workspace = CreateWorkspace(); + var solution = workspace.CurrentSolution.AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp). + WithProjectParseOptions(projectId, new CSharpParseOptions(kind: SourceCodeKind.Script)); + + var documentId = DocumentId.CreateNewId(projectId); + var sourceText = SourceText.From("text"); + var filePath = Path.Combine(TempRoot.Root, "x.cs"); + var folders = new[] { "folder1", "folder2" }; + + var solution2 = solution.AddDocument(documentId, "name", sourceText, folders, filePath, isGenerated: true); + var document = solution2.GetRequiredDocument(documentId); + + AssertEx.Equal(folders, document.Folders); + Assert.Equal(filePath, document.FilePath); + Assert.True(document.State.Attributes.IsGenerated); + Assert.Equal(SourceCodeKind.Script, document.SourceCodeKind); + + Assert.Throws("documentId", () => solution.AddDocument(documentId: null!, "name", sourceText)); + Assert.Throws("name", () => solution.AddDocument(documentId, name: null!, sourceText)); + Assert.Throws("text", () => solution.AddDocument(documentId, "name", text: (SourceText)null!)); + Assert.Throws(() => solution.AddDocument(documentId: DocumentId.CreateNewId(ProjectId.CreateNewId()), "name", sourceText)); + } + + [Fact] + public void AddDocument_SyntaxRoot() + { + var projectId = ProjectId.CreateNewId(); + using var workspace = CreateWorkspace(); + + var solution = workspace.CurrentSolution.AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp). + WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha256). + WithProjectParseOptions(projectId, new CSharpParseOptions(kind: SourceCodeKind.Script)); + + var documentId = DocumentId.CreateNewId(projectId); + var root = CSharp.SyntaxFactory.ParseCompilationUnit("class C {}"); + var filePath = Path.Combine(TempRoot.Root, "x.cs"); + var folders = new[] { "folder1", "folder2" }; + + var solution2 = solution.AddDocument(documentId, "name", root, folders, filePath); + var document = solution2.GetRequiredDocument(documentId); + var sourceText = document.GetTextSynchronously(default); + + Assert.Equal("class C {}", sourceText.ToString()); + Assert.Equal(SourceHashAlgorithm.Sha256, sourceText.ChecksumAlgorithm); + AssertEx.Equal(folders, document.Folders); + Assert.Equal(filePath, document.FilePath); + Assert.False(document.State.Attributes.IsGenerated); + Assert.Equal(SourceCodeKind.Script, document.SourceCodeKind); + + Assert.Throws("documentId", () => solution.AddDocument(documentId: null!, "name", root)); + Assert.Throws("name", () => solution.AddDocument(documentId, name: null!, root)); + Assert.Throws("syntaxRoot", () => solution.AddDocument(documentId, "name", syntaxRoot: null!)); + Assert.Throws(() => solution.AddDocument(documentId: DocumentId.CreateNewId(ProjectId.CreateNewId()), "name", syntaxRoot: root)); + } + #nullable disable [Fact] public void TestAddProject() @@ -1931,7 +2064,7 @@ public void TestDocumentChangedOnDiskIsNotObserved() var did = DocumentId.CreateNewId(pid); sol = sol.AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp) - .AddDocument(did, "x", new FileTextLoader(file.Path, Encoding.UTF8)); + .AddDocument(did, "x", new FileTextLoader(file.Path, Encoding.UTF8, SourceHashAlgorithm.Sha256)); var observedText = GetObservedText(sol, did, text1); @@ -1990,7 +2123,7 @@ public void TestGetLoadedTextAsync() using var workspace = CreateWorkspace(); var sol = workspace.CurrentSolution .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp) - .AddDocument(did, "x", new FileTextLoader(file.Path, Encoding.UTF8)); + .AddDocument(did, "x", new FileTextLoader(file.Path, Encoding.UTF8, SourceHashAlgorithm.Sha256)); var doc = sol.GetDocument(did); @@ -2057,7 +2190,7 @@ public void TestGetSyntaxTreeFromLoadedTextAsync() using var workspace = CreateWorkspace(); var sol = workspace.CurrentSolution .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp) - .AddDocument(did, "x", new FileTextLoader(file.Path, Encoding.UTF8)); + .AddDocument(did, "x", new FileTextLoader(file.Path, Encoding.UTF8, SourceHashAlgorithm.Sha256)); var doc = sol.GetDocument(did); var docTree = doc.GetSyntaxTreeAsync().Result; @@ -2511,7 +2644,7 @@ public async Task TestDocumentFileAccessFailureMissingFile() var did = DocumentId.CreateNewId(pid); solution = solution.AddProject(pid, "goo", "goo", LanguageNames.CSharp) - .AddDocument(did, "x", new FileTextLoader(@"C:\doesnotexist.cs", Encoding.UTF8)) + .AddDocument(did, "x", new FileTextLoader(@"C:\doesnotexist.cs", Encoding.UTF8, SourceHashAlgorithm.Sha256)) .WithDocumentFilePath(did, "document path"); var doc = solution.GetDocument(did); @@ -2743,7 +2876,7 @@ public void TestProjectCompletenessWithMultipleProjects() private class TestSmallFileTextLoader : FileTextLoader { public TestSmallFileTextLoader(string path, Encoding encoding) - : base(path, encoding) + : base(path, encoding, SourceHashAlgorithm.Sha256) { } diff --git a/src/Workspaces/CoreTest/UtilityTest/SourceTextSerializationTests.cs b/src/Workspaces/CoreTest/UtilityTest/SourceTextSerializationTests.cs index 3f036a0f32c64..d6bb8d2708632 100644 --- a/src/Workspaces/CoreTest/UtilityTest/SourceTextSerializationTests.cs +++ b/src/Workspaces/CoreTest/UtilityTest/SourceTextSerializationTests.cs @@ -41,7 +41,7 @@ public void TestSourceTextSerialization() stream.Position = 0; using var reader = ObjectReader.TryGetReader(stream); - var recovered = SourceTextExtensions.ReadFrom(textService, reader, originalText.Encoding, CancellationToken.None); + var recovered = SourceTextExtensions.ReadFrom(textService, reader, originalText.Encoding, originalText.ChecksumAlgorithm, CancellationToken.None); Assert.Equal(originalText.ToString(), recovered.ToString()); } diff --git a/src/Workspaces/MSBuildTest/Resources/ProjectFiles/CSharp/AdditionalFile.csproj b/src/Workspaces/MSBuildTest/Resources/ProjectFiles/CSharp/AdditionalFile.csproj index 1bb40b6b4f396..67d850f7b397f 100644 --- a/src/Workspaces/MSBuildTest/Resources/ProjectFiles/CSharp/AdditionalFile.csproj +++ b/src/Workspaces/MSBuildTest/Resources/ProjectFiles/CSharp/AdditionalFile.csproj @@ -12,7 +12,8 @@ Properties CSharpProject CSharpProject - v4.8 + v4.8 + SHA1 512 snKey.snk diff --git a/src/Workspaces/MSBuildTest/Utilities/VisualStudioMSBuildInstalled.cs b/src/Workspaces/MSBuildTest/Utilities/VisualStudioMSBuildInstalled.cs index 3e0f0819c4b85..96aed348776b8 100644 --- a/src/Workspaces/MSBuildTest/Utilities/VisualStudioMSBuildInstalled.cs +++ b/src/Workspaces/MSBuildTest/Utilities/VisualStudioMSBuildInstalled.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics; using System.Linq; using Microsoft.Build.Locator; using Roslyn.Test.Utilities; @@ -17,16 +18,20 @@ internal class VisualStudioMSBuildInstalled : ExecutionCondition static VisualStudioMSBuildInstalled() { - var installedVisualStudios = MSBuildLocator.QueryVisualStudioInstances().ToArray(); - foreach (var visualStudioInstall in installedVisualStudios) + var latestInstalledInstance = (VisualStudioInstance?)null; + foreach (var visualStudioInstance in MSBuildLocator.QueryVisualStudioInstances()) { - if (visualStudioInstall.Version.Major == 17 && - visualStudioInstall.Version.Minor == 0) + if (latestInstalledInstance == null || visualStudioInstance.Version > latestInstalledInstance.Version) { - MSBuildLocator.RegisterInstance(visualStudioInstall); - s_instance = visualStudioInstall; + latestInstalledInstance = visualStudioInstance; } } + + if (latestInstalledInstance != null) + { + MSBuildLocator.RegisterInstance(latestInstalledInstance); + s_instance = latestInstalledInstance; + } } #endif diff --git a/src/Workspaces/MSBuildTest/VisualStudioMSBuildWorkspaceTests.cs b/src/Workspaces/MSBuildTest/VisualStudioMSBuildWorkspaceTests.cs index 28f20e42d8cb3..b9f79190af24e 100644 --- a/src/Workspaces/MSBuildTest/VisualStudioMSBuildWorkspaceTests.cs +++ b/src/Workspaces/MSBuildTest/VisualStudioMSBuildWorkspaceTests.cs @@ -247,6 +247,40 @@ public async Task TestCompilationOutputInfo() Assert.Equal("VisualBasicProject.dll", Path.GetFileName(p2.CompilationOutputInfo.AssemblyPath)); } + [ConditionalFact(typeof(VisualStudioMSBuildInstalled)), Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)] + public async Task TestChecksumAlgorithm() + { + CreateFiles(GetSimpleCSharpSolutionWithAdditionaFile()); + var solutionFilePath = GetSolutionFileName("TestSolution.sln"); + + using var workspace = CreateMSBuildWorkspace(); + var solution = await workspace.OpenSolutionAsync(solutionFilePath); + var project = solution.Projects.Single(); + + Assert.Equal(SourceHashAlgorithm.Sha1, project.State.ChecksumAlgorithm); + + Assert.All(project.Documents, d => Assert.Equal(SourceHashAlgorithm.Sha1, d.GetTextSynchronously(default).ChecksumAlgorithm)); + Assert.All(project.AdditionalDocuments, d => Assert.Equal(SourceHashAlgorithm.Sha1, d.GetTextSynchronously(default).ChecksumAlgorithm)); + } + + [ConditionalFact(typeof(VisualStudioMSBuildInstalled)), Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)] + public async Task TestChecksumAlgorithm_Default() + { + CreateFiles(GetMultiProjectSolutionFiles()); + var solutionFilePath = GetSolutionFileName("TestSolution.sln"); + + using var workspace = CreateMSBuildWorkspace(); + var sol = await workspace.OpenSolutionAsync(solutionFilePath); + var csProject = sol.Projects.First(p => p.Language == LanguageNames.CSharp); + var vbProject = sol.Projects.First(p => p.Language == LanguageNames.VisualBasic); + + Assert.Equal(SourceHashAlgorithm.Sha256, csProject.State.ChecksumAlgorithm); + Assert.Equal(SourceHashAlgorithm.Sha256, vbProject.State.ChecksumAlgorithm); + + Assert.All(csProject.Documents, d => Assert.Equal(SourceHashAlgorithm.Sha256, d.GetTextSynchronously(default).ChecksumAlgorithm)); + Assert.All(vbProject.Documents, d => Assert.Equal(SourceHashAlgorithm.Sha256, d.GetTextSynchronously(default).ChecksumAlgorithm)); + } + [ConditionalFact(typeof(VisualStudioMSBuildInstalled)), Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)] public async Task TestCrossLanguageReferencesUsesInMemoryGeneratedMetadata() { diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 33ff3b49a2a4f..1520ea1adcfda 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -48,15 +48,15 @@ public async Task CreateProjectInfoAsync(Checksum projectChecksum, { var projectChecksums = await GetAssetAsync(projectChecksum, cancellationToken).ConfigureAwait(false); - var projectInfo = await GetAssetAsync(projectChecksums.Info, cancellationToken).ConfigureAwait(false); - if (!RemoteSupportedLanguages.IsSupported(projectInfo.Language)) + var attributes = await GetAssetAsync(projectChecksums.Info, cancellationToken).ConfigureAwait(false); + if (!RemoteSupportedLanguages.IsSupported(attributes.Language)) { // only add project our workspace supports. // workspace doesn't allow creating project with unknown languages return null; } - var compilationOptions = projectInfo.FixUpCompilationOptions( + var compilationOptions = attributes.FixUpCompilationOptions( await GetAssetAsync(projectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false)); var parseOptions = await GetAssetAsync(projectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false); @@ -70,13 +70,7 @@ public async Task CreateProjectInfoAsync(Checksum projectChecksum, var analyzerConfigDocumentInfos = await CreateDocumentInfosAsync(projectChecksums.AnalyzerConfigDocuments, cancellationToken).ConfigureAwait(false); return ProjectInfo.Create( - projectInfo.Id, - projectInfo.Version, - projectInfo.Name, - projectInfo.AssemblyName, - projectInfo.Language, - projectInfo.FilePath, - projectInfo.OutputFilePath, + attributes, compilationOptions, parseOptions, documentInfos, @@ -84,14 +78,8 @@ public async Task CreateProjectInfoAsync(Checksum projectChecksum, metadataReferences, analyzerReferences, additionalDocumentInfos, - projectInfo.IsSubmission) - .WithOutputRefFilePath(projectInfo.OutputRefFilePath) - .WithCompilationOutputInfo(projectInfo.CompilationOutputInfo) - .WithHasAllInformation(projectInfo.HasAllInformation) - .WithRunAnalyzers(projectInfo.RunAnalyzers) - .WithDefaultNamespace(projectInfo.DefaultNamespace) - .WithAnalyzerConfigDocuments(analyzerConfigDocumentInfos) - .WithTelemetryId(projectInfo.TelemetryId); + analyzerConfigDocumentInfos, + hostObjectType: null); // TODO: https://github.com/dotnet/roslyn/issues/62804 } public async Task CreateDocumentInfoAsync(Checksum documentChecksum, CancellationToken cancellationToken) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 5485e3977233d..6510a1e73afa6 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -300,54 +300,7 @@ private async Task UpdateProjectInfoAsync(Project project, Checksum inf Contract.ThrowIfFalse(project.State.ProjectInfo.Attributes.Language == newProjectAttributes.Language); Contract.ThrowIfFalse(project.State.ProjectInfo.Attributes.IsSubmission == newProjectAttributes.IsSubmission); - var projectId = project.Id; - - if (project.State.ProjectInfo.Attributes.Name != newProjectAttributes.Name) - { - project = project.Solution.WithProjectName(projectId, newProjectAttributes.Name).GetProject(projectId)!; - } - - if (project.State.ProjectInfo.Attributes.AssemblyName != newProjectAttributes.AssemblyName) - { - project = project.Solution.WithProjectAssemblyName(projectId, newProjectAttributes.AssemblyName).GetProject(projectId)!; - } - - if (project.State.ProjectInfo.Attributes.FilePath != newProjectAttributes.FilePath) - { - project = project.Solution.WithProjectFilePath(projectId, newProjectAttributes.FilePath).GetProject(projectId)!; - } - - if (project.State.ProjectInfo.Attributes.OutputFilePath != newProjectAttributes.OutputFilePath) - { - project = project.Solution.WithProjectOutputFilePath(projectId, newProjectAttributes.OutputFilePath).GetProject(projectId)!; - } - - if (project.State.ProjectInfo.Attributes.OutputRefFilePath != newProjectAttributes.OutputRefFilePath) - { - project = project.Solution.WithProjectOutputRefFilePath(projectId, newProjectAttributes.OutputRefFilePath).GetProject(projectId)!; - } - - if (project.State.ProjectInfo.Attributes.CompilationOutputInfo != newProjectAttributes.CompilationOutputInfo) - { - project = project.Solution.WithProjectCompilationOutputInfo(project.Id, newProjectAttributes.CompilationOutputInfo).GetProject(project.Id)!; - } - - if (project.State.ProjectInfo.Attributes.DefaultNamespace != newProjectAttributes.DefaultNamespace) - { - project = project.Solution.WithProjectDefaultNamespace(projectId, newProjectAttributes.DefaultNamespace).GetProject(projectId)!; - } - - if (project.State.ProjectInfo.Attributes.HasAllInformation != newProjectAttributes.HasAllInformation) - { - project = project.Solution.WithHasAllInformation(projectId, newProjectAttributes.HasAllInformation).GetProject(projectId)!; - } - - if (project.State.ProjectInfo.Attributes.RunAnalyzers != newProjectAttributes.RunAnalyzers) - { - project = project.Solution.WithRunAnalyzers(projectId, newProjectAttributes.RunAnalyzers).GetProject(projectId)!; - } - - return project; + return project.Solution.WithProjectAttributes(project.Id, newProjectAttributes).GetRequiredProject(project.Id); } private async Task UpdateDocumentsAsync( diff --git a/src/Workspaces/VisualBasic/Portable/Workspace/LanguageServices/VisualBasicSyntaxTreeFactoryService.vb b/src/Workspaces/VisualBasic/Portable/Workspace/LanguageServices/VisualBasicSyntaxTreeFactoryService.vb index fda6820f6b8c2..f2c359195c1f6 100644 --- a/src/Workspaces/VisualBasic/Portable/Workspace/LanguageServices/VisualBasicSyntaxTreeFactoryService.vb +++ b/src/Workspaces/VisualBasic/Portable/Workspace/LanguageServices/VisualBasicSyntaxTreeFactoryService.vb @@ -84,12 +84,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return SyntaxFactory.ParseSyntaxTree(text, options, filePath, cancellationToken) End Function - Public Overrides Function CreateSyntaxTree(filePath As String, options As ParseOptions, encoding As Encoding, root As SyntaxNode) As SyntaxTree + Public Overrides Function CreateSyntaxTree(filePath As String, options As ParseOptions, encoding As Encoding, checksumAlgorithm As SourceHashAlgorithm, root As SyntaxNode) As SyntaxTree If options Is Nothing Then options = GetDefaultParseOptions() End If - Return VisualBasicSyntaxTree.Create(DirectCast(root, VisualBasicSyntaxNode), DirectCast(options, VisualBasicParseOptions), filePath, encoding) + Return VisualBasicSyntaxTree.Create(DirectCast(root, VisualBasicSyntaxNode), checksumAlgorithm, DirectCast(options, VisualBasicParseOptions), filePath, encoding) End Function Public Overrides Function CanCreateRecoverableTree(root As SyntaxNode) As Boolean