From c01be48574319ce6e756e6192c776db38937479d Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 7 Jul 2020 12:31:47 +0100 Subject: [PATCH] handle actionDirectives add test add previous submission to previous project clean project fix duplication inherit from workspace and apply solution cahnges finally victory ensure diagnostic behavior with shadowing variables revert changes revert parser revert parser --- .../InteractiveWorkspace.cs | 204 +++++++++--------- .../LanguageKernelTests.cs | 102 ++++++++- .../LanguageServices/CompletionTests.cs | 26 +++ .../Commands/RequestDiagnostics.cs | 3 +- .../Commands/SubmitCode.cs | 3 +- .../Parsing/SubmissionParser.cs | 5 +- 6 files changed, 236 insertions(+), 107 deletions(-) diff --git a/src/Microsoft.DotNet.Interactive.CSharp/InteractiveWorkspace.cs b/src/Microsoft.DotNet.Interactive.CSharp/InteractiveWorkspace.cs index a9a8839e1d..3fa0e9a057 100644 --- a/src/Microsoft.DotNet.Interactive.CSharp/InteractiveWorkspace.cs +++ b/src/Microsoft.DotNet.Interactive.CSharp/InteractiveWorkspace.cs @@ -3,6 +3,7 @@ using System; using System.Reactive.Disposables; + using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Host.Mef; @@ -11,7 +12,7 @@ namespace Microsoft.DotNet.Interactive.CSharp { - internal class InteractiveWorkspace : IDisposable + internal class InteractiveWorkspace : Workspace { private ProjectId _previousSubmissionProjectId; private ProjectId _currentSubmissionProjectId; @@ -19,30 +20,18 @@ internal class InteractiveWorkspace : IDisposable private int _submissionCount; private readonly CSharpParseOptions _parseOptions; private Compilation _currentCompilation; - private Solution _solution; - private TextContainer _committedTextContainer; - private DocumentId _committedDocumentId; + private DocumentId _workingDocumentId; - public InteractiveWorkspace() + public InteractiveWorkspace() : base(MefHostServices.DefaultHost, WorkspaceKind.Interactive) { - var workspace = new AdhocWorkspace(MefHostServices.DefaultHost, WorkspaceKind.Interactive); - - _committedTextContainer = new TextContainer(); - - _solution = workspace.CurrentSolution; - _parseOptions = new CSharpParseOptions( LanguageVersion.Latest, DocumentationMode.None, SourceCodeKind.Script); - _disposables.Add(workspace); - _disposables.Add(Disposable.Create(() => { - _committedTextContainer = null; _currentCompilation = null; - _solution = null; })); } @@ -50,141 +39,152 @@ public void AddSubmission(ScriptState scriptState) { _currentCompilation = scriptState.Script.GetCompilation(); - _previousSubmissionProjectId = _currentSubmissionProjectId; + var solution = CurrentSolution; + if (_currentSubmissionProjectId != null) + { + solution = solution.RemoveProject(_currentSubmissionProjectId); + } - _committedTextContainer.AppendText(scriptState.Script.Code); + SetCurrentSolution(solution); - var assemblyName = $"Submission#{_submissionCount++}"; - var debugName = assemblyName; + _previousSubmissionProjectId = CreateProjectForPreviousSubmission(_currentCompilation, scriptState.Script.Code, _currentSubmissionProjectId, _previousSubmissionProjectId); -#if DEBUG - debugName += $": {scriptState.Script.Code}"; -#endif + (_currentSubmissionProjectId, _workingDocumentId) = CreateProjectForCurrentSubmission(_currentCompilation, _previousSubmissionProjectId); + } - _currentSubmissionProjectId = ProjectId.CreateNewId(debugName: debugName); + private (ProjectId projectId, DocumentId workingDocumentId) CreateProjectForCurrentSubmission(Compilation previousCompilation, ProjectId projectReferenceProjectId) + { + var submission = _submissionCount++; + var solution = CurrentSolution; + var assemblyName = $"Submission#{submission}"; + var compilationOptions = previousCompilation.Options.WithScriptClassName(assemblyName); + var debugName = assemblyName; + var projectId = ProjectId.CreateNewId(debugName: debugName); var projectInfo = ProjectInfo.Create( - _currentSubmissionProjectId, + projectId, VersionStamp.Create(), name: debugName, assemblyName: assemblyName, language: LanguageNames.CSharp, parseOptions: _parseOptions, - compilationOptions: _currentCompilation.Options, - metadataReferences: _currentCompilation.References); + compilationOptions: compilationOptions, + isSubmission: true); + + solution = solution.AddProject(projectInfo); - _solution = _solution.AddProject(projectInfo); + if (projectReferenceProjectId != null) + { + solution = solution.AddProjectReference( + projectId, + new ProjectReference(projectReferenceProjectId) + ); + } - var currentSubmissionDocumentId = DocumentId.CreateNewId( - _currentSubmissionProjectId, - debugName: debugName); - // add the code submission to the current project - var submissionSourceText = SourceText.From(scriptState.Script.Code); + var workingDocumentId = DocumentId.CreateNewId( + projectInfo.Id, + debugName: $"working document for {submission}"); - _solution = _solution.AddDocument( - currentSubmissionDocumentId, - debugName, - submissionSourceText); + solution = solution.AddDocument( + workingDocumentId, + $"working document for {submission}", + string.Empty); + + SetCurrentSolution(solution); + return (projectId, workingDocumentId); + } - if (_previousSubmissionProjectId != null) + private ProjectId CreateProjectForPreviousSubmission(Compilation compilation, string code, ProjectId projectId, ProjectId projectReferenceProjectId) + { + var solution = CurrentSolution; + + var compilationOptions = compilation.Options; + var assemblyName = compilationOptions.ScriptClassName; + if (string.IsNullOrWhiteSpace(assemblyName)) { - _solution = _solution.AddProjectReference( - _currentSubmissionProjectId, - new ProjectReference(_previousSubmissionProjectId)); + assemblyName = $"Submission#{_submissionCount}"; + compilationOptions = compilationOptions.WithScriptClassName(assemblyName); } - // remove rollup and working document from project - - if (_committedDocumentId != null) + var debugName = assemblyName; +#if DEBUG + debugName += $": {code}"; +#endif + if (projectId == null) { - _solution = _solution.RemoveDocument(_committedDocumentId); + projectId = ProjectId.CreateNewId(debugName: debugName); } - // create new ids and reuse buffers + var projectInfo = ProjectInfo.Create( + projectId, + VersionStamp.Create(), + name: debugName, + assemblyName: assemblyName, + language: LanguageNames.CSharp, + parseOptions: _parseOptions, + compilationOptions: compilationOptions, + metadataReferences: compilation.References, + isSubmission:true); - _committedTextContainer.AppendText(scriptState.Script.Code); - + var currentSubmissionDocumentId = DocumentId.CreateNewId( + projectInfo.Id, + debugName: assemblyName); - var workingProjectName = $"Rollup through #{_submissionCount - 1}"; + // add the code submission to the current project + var submissionSourceText = SourceText.From(code); - _committedDocumentId = DocumentId.CreateNewId( - _currentSubmissionProjectId, - workingProjectName); + solution = solution.AddProject(projectInfo); + + if (projectReferenceProjectId != null) + { + solution = solution.AddProjectReference( + projectId, + new ProjectReference(projectReferenceProjectId) + ); + } - _solution = _solution.AddDocument( - _committedDocumentId, - workingProjectName, - TextLoader.From(_committedTextContainer, new VersionStamp())); + solution = solution.AddDocument( + currentSubmissionDocumentId, + debugName, + submissionSourceText); + SetCurrentSolution(solution); + return projectId; } + public Document ForkDocument(string code) { - var solution = _solution; + var solution = CurrentSolution; + solution = solution.RemoveDocument(_workingDocumentId); - var workingDocumentName = $"Fork from #{_submissionCount - 1}"; - - var workingDocumentId = DocumentId.CreateNewId( + var workingDocumentName = $"Fork from #{_submissionCount}"; + + _workingDocumentId = DocumentId.CreateNewId( _currentSubmissionProjectId, workingDocumentName); solution = solution.AddDocument( - workingDocumentId, + _workingDocumentId, workingDocumentName, SourceText.From(code) ); var languageServicesDocument = - solution.GetDocument(workingDocumentId); - + solution.GetDocument(_workingDocumentId); + SetCurrentSolution(solution); return languageServicesDocument; } - public void Dispose() - { - _disposables.Dispose(); - } - } - - internal class TextContainer : SourceTextContainer - { - private SourceText _currentText; - - public TextContainer() - { - _currentText = SourceText.From(string.Empty); - } - - public void SetText(string text) + protected override void Dispose(bool finalize) { - var old = _currentText; - _currentText = SourceText.From(text); - if (TextChanged is { } e) + if (!finalize) { - e.Invoke(this, new TextChangeEventArgs(old, _currentText)); + _disposables.Dispose(); } + base.Dispose(finalize); } - - public void AppendText(string text) - { - var old = _currentText; - _currentText = _currentText.Replace(_currentText.Length,text.Length, text); - if (TextChanged is {} e) - { - e.Invoke(this, new TextChangeEventArgs(old, _currentText)); - } - } - - public override SourceText CurrentText => GetSourceText(); - - private SourceText GetSourceText() - { - return _currentText; - } - - public override event EventHandler TextChanged; } - } \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.Tests/LanguageKernelTests.cs b/src/Microsoft.DotNet.Interactive.Tests/LanguageKernelTests.cs index f69fe271c0..861d8ff4e4 100644 --- a/src/Microsoft.DotNet.Interactive.Tests/LanguageKernelTests.cs +++ b/src/Microsoft.DotNet.Interactive.Tests/LanguageKernelTests.cs @@ -248,7 +248,7 @@ void f() [Theory] [InlineData(Language.CSharp, "CS0103", "The name 'aaaadd' does not exist in the current context", "(1,1): error CS0103: The name 'aaaadd' does not exist in the current context")] [InlineData(Language.FSharp, "FS0039", "The value or constructor 'aaaadd' is not defined.", "input.fsx (1,1)-(1,7) typecheck error The value or constructor 'aaaadd' is not defined.")] - public async Task when_code_contains_compile_time_error_diagniostics_are_produced(Language language, string code, string diagnosticMessage, string errorMessage) + public async Task when_code_contains_compile_time_error_diagnostics_are_produced(Language language, string code, string diagnosticMessage, string errorMessage) { var kernel = CreateKernel(language); @@ -373,6 +373,106 @@ public async Task diagnostics_can_be_produced_from_multiple_subkernels() .ContainSingle(d => d.Code.StartsWith("FS")); } + [Theory] + [InlineData(Language.CSharp)] + [InlineData(Language.FSharp)] + public async Task shadowing_variable_does_not_produce_diagnostics(Language language) + { + var kernel = CreateKernel(language); + + var firstDeclaration = language switch + { + // null returned. + Language.FSharp => "let a = \"original\"", + Language.CSharp => "var a = \"original\";" + }; + + await SubmitCode(kernel, firstDeclaration); + + var shadowingDeclaration = language switch + { + // null returned. + Language.FSharp => "let a = 1", + Language.CSharp => "var a = 1;" + }; + + await SubmitCode(kernel, shadowingDeclaration); + + KernelEvents + .OfType() + .SelectMany(dp => dp.Diagnostics) + .Should() + .BeEmpty(); + } + + [Theory] + [InlineData(Language.CSharp)] + [InlineData(Language.FSharp)] + public async Task accessing_shadowed_variable_does_not_produce_diagnostics(Language language) + { + var kernel = CreateKernel(language); + + var firstDeclaration = language switch + { + // null returned. + Language.FSharp => "let a = \"original\"", + Language.CSharp => "var a = \"original\";" + }; + + await SubmitCode(kernel, firstDeclaration); + + var shadowingDeclaration = language switch + { + // null returned. + Language.FSharp => "let a = 1", + Language.CSharp => "var a = 1;" + }; + + await SubmitCode(kernel, shadowingDeclaration); + + await SubmitCode(kernel, "a"); + + KernelEvents + .OfType() + .SelectMany(dp => dp.Diagnostics) + .Should() + .BeEmpty(); + } + + [Theory] + [InlineData(Language.CSharp)] + [InlineData(Language.FSharp)] + public async Task typing_shadowed_variable_does_not_produce_diagnostics(Language language) + { + var kernel = CreateKernel(language); + + var firstDeclaration = language switch + { + // null returned. + Language.FSharp => "let a = \"original\"", + Language.CSharp => "var a = \"original\";" + }; + + await SubmitCode(kernel, firstDeclaration); + + var shadowingDeclaration = language switch + { + // null returned. + Language.FSharp => "let a = 1", + Language.CSharp => "var a = 1;" + }; + + await SubmitCode(kernel, shadowingDeclaration); + + await kernel.SendAsync(new RequestDiagnostics("a")); + + KernelEvents + .OfType() + .SelectMany(dp => dp.Diagnostics) + .Should() + .BeEmpty(); + } + [Theory] [InlineData(Language.CSharp, "Console.WritLin();")] [InlineData(Language.FSharp, "printfnnn \"\"")] diff --git a/src/Microsoft.DotNet.Interactive.Tests/LanguageServices/CompletionTests.cs b/src/Microsoft.DotNet.Interactive.Tests/LanguageServices/CompletionTests.cs index 11a40b650c..ea08c69391 100644 --- a/src/Microsoft.DotNet.Interactive.Tests/LanguageServices/CompletionTests.cs +++ b/src/Microsoft.DotNet.Interactive.Tests/LanguageServices/CompletionTests.cs @@ -89,6 +89,32 @@ public async Task Completions_are_available_for_symbols_declared_in_a_submission .Contain(item => item.DisplayText == variableName); } + [Theory] + [InlineData(Language.FSharp, Skip = "Compiler error")] + [InlineData(Language.CSharp)] + public async Task Completions_are_available_for_symbols_members(Language language) + { + var declaration = language switch + { + Language.CSharp => new SubmitCode("var fileInfo = new System.IO.FileInfo(\"temp.file\");"), + Language.FSharp => new SubmitCode("let fileInfo = new System.IO.FileInfo(\"temp.file\")") + }; + + var kernel = CreateKernel(language); + await kernel.SendAsync(declaration); + + MarkupTestFile.GetLineAndColumn("fileInfo.$$", out var useInput, out var line, out var column); + await kernel.SendAsync(new RequestCompletions(useInput, new LinePosition(line, column))); + + KernelEvents + .Should() + .ContainSingle() + .Which + .Completions + .Should() + .Contain(item => item.DisplayText == "AppendText"); + } + [Theory] [InlineData(Language.FSharp)] [InlineData(Language.CSharp)] diff --git a/src/Microsoft.DotNet.Interactive/Commands/RequestDiagnostics.cs b/src/Microsoft.DotNet.Interactive/Commands/RequestDiagnostics.cs index 6dd698601d..4a1addf79c 100644 --- a/src/Microsoft.DotNet.Interactive/Commands/RequestDiagnostics.cs +++ b/src/Microsoft.DotNet.Interactive/Commands/RequestDiagnostics.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; + using Microsoft.DotNet.Interactive.Parsing; namespace Microsoft.DotNet.Interactive.Commands @@ -28,4 +29,4 @@ internal RequestDiagnostics( internal LanguageNode LanguageNode { get; } } -} +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive/Commands/SubmitCode.cs b/src/Microsoft.DotNet.Interactive/Commands/SubmitCode.cs index 52744e0012..512e025d54 100644 --- a/src/Microsoft.DotNet.Interactive/Commands/SubmitCode.cs +++ b/src/Microsoft.DotNet.Interactive/Commands/SubmitCode.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; + using Microsoft.DotNet.Interactive.Parsing; namespace Microsoft.DotNet.Interactive.Commands @@ -37,4 +38,4 @@ internal SubmitCode( internal LanguageNode LanguageNode { get; } } -} +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive/Parsing/SubmissionParser.cs b/src/Microsoft.DotNet.Interactive/Parsing/SubmissionParser.cs index 3cfadbb630..c23dfc4c5c 100644 --- a/src/Microsoft.DotNet.Interactive/Parsing/SubmissionParser.cs +++ b/src/Microsoft.DotNet.Interactive/Parsing/SubmissionParser.cs @@ -10,6 +10,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Text; using Microsoft.DotNet.Interactive.Commands; using Microsoft.DotNet.Interactive.Events; @@ -132,7 +133,7 @@ private IReadOnlyList SplitSubmission(KernelCommand originalComma case LanguageNode languageNode: commands.Add(commandCreator(languageNode, originalCommand.Parent)); break; - + default: throw new ArgumentOutOfRangeException(nameof(node)); } @@ -142,7 +143,7 @@ private IReadOnlyList SplitSubmission(KernelCommand originalComma { var kernel = _kernel.FindKernel(kernelName); - if (kernel?.SubmissionParser.GetDirectiveParser() is {} parser) + if (kernel?.SubmissionParser.GetDirectiveParser() is { } parser) { var restore = new DirectiveCommand( parser.Parse("#!nuget-restore"),