From 3bf941144247ee074e729d25b751f7c58c09914b Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 4 Jun 2019 14:22:51 +0100 Subject: [PATCH 1/7] ncrunch settings --- MLS.Agent/MLS.Agent.csproj | 11 +++++++---- MLS.Agent/MLS.Agent.v3.ncrunchproject | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/MLS.Agent/MLS.Agent.csproj b/MLS.Agent/MLS.Agent.csproj index 3911a7402..452b99c54 100644 --- a/MLS.Agent/MLS.Agent.csproj +++ b/MLS.Agent/MLS.Agent.csproj @@ -129,7 +129,8 @@ + BeforeTargets="BeforeBuild" + Condition="'$(NCrunch)' != '1'"> <_TryDotNetCssExists Condition="Exists('$(MSBuildThisFileDirectory)wwwroot\css\trydotnet.css')">true @@ -147,7 +148,7 @@ - + $(MSBuildThisFileDirectory)..\Microsoft.DotNet.Try.Client $(MSBuildThisFileDirectory)wwwroot/client @@ -167,7 +168,8 @@ Inputs="@(TryDotNetJsInput)" Outputs="$(TryDotNetJsFile);$(TryDotNetJsMap)" DependsOnTargets="GatherInputs" - BeforeTargets="BeforeBuild"> + BeforeTargets="BeforeBuild" + Condition="'$(NCrunch)' != '1'"> <_TryDotNetMinJsExists Condition="Exists('$(TryDotNetJsFile)')">true @@ -189,7 +191,8 @@ Inputs="@(ClientInputFiles)" Outputs="$(ClientOutputFile)" DependsOnTargets="GatherInputs" - BeforeTargets="BeforeBuild"> + BeforeTargets="BeforeBuild" + Condition="'$(NCrunch)' != '1'"> <_TryDotNetClientExists Condition="Exists('$(ClientOutputFile)')">true diff --git a/MLS.Agent/MLS.Agent.v3.ncrunchproject b/MLS.Agent/MLS.Agent.v3.ncrunchproject index eb119af1c..069f496c5 100644 --- a/MLS.Agent/MLS.Agent.v3.ncrunchproject +++ b/MLS.Agent/MLS.Agent.v3.ncrunchproject @@ -5,6 +5,7 @@ ..\Microsoft.DotNet.Try.Client\**.* ..\Microsoft.DotNet.Try.js\**.* ..\Microsoft.DotNet.Try.Styles\**.* + wwwroot\**.* \ No newline at end of file From 4a91aaf231f6dd50ed594ffb654a0b9059e41d15 Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 4 Jun 2019 15:41:59 +0100 Subject: [PATCH 2/7] code fence parsing supports fsharp language --- DotNetTry.sln.DotSettings | 3 +- .../CodeBlockAnnotationExtensionTests.cs | 59 ++++++++-- .../AnnotatedCodeBlock.cs | 2 +- .../AnnotatedCodeBlockParser.cs | 6 +- .../CodeBlockAnnotations.cs | 7 ++ .../CodeFenceAnnotationsParser.cs | 110 ++++++++++++------ 6 files changed, 137 insertions(+), 50 deletions(-) diff --git a/DotNetTry.sln.DotSettings b/DotNetTry.sln.DotSettings index 7fa67efcf..c30c28c31 100644 --- a/DotNetTry.sln.DotSettings +++ b/DotNetTry.sln.DotSettings @@ -1,2 +1,3 @@  - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/MLS.Agent.Tests/Markdown/CodeBlockAnnotationExtensionTests.cs b/MLS.Agent.Tests/Markdown/CodeBlockAnnotationExtensionTests.cs index 879dd7bca..4361fc778 100644 --- a/MLS.Agent.Tests/Markdown/CodeBlockAnnotationExtensionTests.cs +++ b/MLS.Agent.Tests/Markdown/CodeBlockAnnotationExtensionTests.cs @@ -67,14 +67,14 @@ static void MyProgram(string[] args) var document = $@"```{language} --source-file Program.cs ```"; - string html = (await pipeline.RenderHtmlAsync(document)).EnforceLF(); + var html = (await pipeline.RenderHtmlAsync(document)).EnforceLF(); html.Should().Contain(fileContent.HtmlEncode().ToString()); } [Fact] - public async Task Does_not_insert_code_when_specified_language_is_not_csharp() + public async Task Does_not_insert_code_when_specified_language_is_not_supported() { - string expectedValue = + var expectedValue = @"
console.log("Hello World");
 
".EnforceLF(); @@ -90,27 +90,34 @@ public async Task Does_not_insert_code_when_specified_language_is_not_csharp() html.Should().Contain(expectedValue); } - [Fact] - public async Task Does_not_insert_code_when_csharp_is_specified_but_no_additional_options() + [Theory] + [InlineData("cs", "language-cs")] + [InlineData("csharp", "language-csharp")] + [InlineData("c#", "language-c#")] + [InlineData("fs", "language-fs")] + [InlineData("fsharp", "language-fsharp")] + [InlineData("f#", "language-f#")] + public async Task Does_not_insert_code_when_supported_language_is_specified_but_no_additional_options(string fenceLanguage, string expectedClass) { - string expectedValue = -@"
Console.WriteLine("Hello World");
+            var expectedValue =
+$@"
Console.WriteLine("Hello World");
 
".EnforceLF(); var testDir = TestAssets.SampleConsole; var directoryAccessor = new InMemoryDirectoryAccessor(testDir); var pipeline = new MarkdownPipelineBuilder().UseCodeBlockAnnotations(directoryAccessor, Default.PackageFinder).Build(); - var document = @" -```cs + var document = $@" +```{fenceLanguage} Console.WriteLine(""Hello World""); ```"; var html = (await pipeline.RenderHtmlAsync(document)).EnforceLF(); html.Should().Contain(expectedValue); } + [Fact] - public async Task Error_messsage_is_displayed_when_the_linked_file_does_not_exist() + public async Task Error_message_is_displayed_when_the_linked_file_does_not_exist() { var testDir = TestAssets.SampleConsole; var directoryAccessor = new InMemoryDirectoryAccessor(testDir) @@ -190,6 +197,38 @@ public async Task Sets_the_trydotnet_package_attribute_using_the_passed_project_ output.Value.Should().Be(fullProjectPath.FullName); } + [Theory] + [InlineData("cs", "Program.cs", "sample.csproj", "csharp")] + [InlineData("c#", "Program.cs", "sample.csproj", "csharp")] + [InlineData("fs", "Program.fs", "sample.fsproj", "fsharp")] + [InlineData("f#", "Program.fs", "sample.fsproj", "fsharp")] + public async Task Sets_the_trydotnet_language_attribute_using_the_fence_command(string fenceLanguage, string fileName, string projectName, string expectedLanguage) + { + var rootDirectory = TestAssets.SampleConsole; + var currentDir = new DirectoryInfo(Path.Combine(rootDirectory.FullName, "docs")); + var directoryAccessor = new InMemoryDirectoryAccessor(currentDir, rootDirectory) + { + ($"src/sample/{fileName}", ""), + ($"src/sample/{projectName}", "") + }; + + var pipeline = new MarkdownPipelineBuilder().UseCodeBlockAnnotations(directoryAccessor, Default.PackageFinder).Build(); + + var package = $"../src/sample/{projectName}"; + var document = + $@"```{fenceLanguage} --project {package} --source-file ../src/sample/{fileName} +```"; + + var html = (await pipeline.RenderHtmlAsync(document)).EnforceLF(); + + var htmlDocument = new HtmlDocument(); + htmlDocument.LoadHtml(html); + var trydotnetLanguage = htmlDocument.DocumentNode + .SelectSingleNode("//pre/code").Attributes["data-trydotnet-language"]; + + trydotnetLanguage.Value.Should().Be(expectedLanguage); + } + [Fact] public async Task Sets_the_trydotnet_package_attribute_using_the_passed_package_option() { diff --git a/Microsoft.DotNet.Try.Markdown/AnnotatedCodeBlock.cs b/Microsoft.DotNet.Try.Markdown/AnnotatedCodeBlock.cs index f4cf3196c..488704eef 100644 --- a/Microsoft.DotNet.Try.Markdown/AnnotatedCodeBlock.cs +++ b/Microsoft.DotNet.Try.Markdown/AnnotatedCodeBlock.cs @@ -15,7 +15,7 @@ namespace Microsoft.DotNet.Try.Markdown { public class AnnotatedCodeBlock : FencedCodeBlock { - protected readonly List _diagnostics = new List(); + private readonly List _diagnostics = new List(); private string _sourceCode; private bool _initialized; diff --git a/Microsoft.DotNet.Try.Markdown/AnnotatedCodeBlockParser.cs b/Microsoft.DotNet.Try.Markdown/AnnotatedCodeBlockParser.cs index 6acbf3b98..6d13fa73b 100644 --- a/Microsoft.DotNet.Try.Markdown/AnnotatedCodeBlockParser.cs +++ b/Microsoft.DotNet.Try.Markdown/AnnotatedCodeBlockParser.cs @@ -10,12 +10,12 @@ namespace Microsoft.DotNet.Try.Markdown { public class AnnotatedCodeBlockParser : FencedBlockParserBase { - private readonly CodeFenceAnnotationsParser codeFenceAnnotationsParser; + private readonly CodeFenceAnnotationsParser _codeFenceAnnotationsParser; private int _order; public AnnotatedCodeBlockParser(CodeFenceAnnotationsParser codeFenceAnnotationsParser) { - this.codeFenceAnnotationsParser = codeFenceAnnotationsParser ?? throw new ArgumentNullException(nameof(codeFenceAnnotationsParser)); + _codeFenceAnnotationsParser = codeFenceAnnotationsParser ?? throw new ArgumentNullException(nameof(codeFenceAnnotationsParser)); OpeningCharacters = new[] { '`' }; InfoParser = ParseCodeOptions; } @@ -30,7 +30,7 @@ protected bool ParseCodeOptions(BlockProcessor state, ref StringSlice line, IFen return false; } - var result = codeFenceAnnotationsParser.TryParseCodeFenceOptions(line.ToString(), + var result = _codeFenceAnnotationsParser.TryParseCodeFenceOptions(line.ToString(), state.Context); switch (result) diff --git a/Microsoft.DotNet.Try.Markdown/CodeBlockAnnotations.cs b/Microsoft.DotNet.Try.Markdown/CodeBlockAnnotations.cs index 3b03f7175..03966bbef 100644 --- a/Microsoft.DotNet.Try.Markdown/CodeBlockAnnotations.cs +++ b/Microsoft.DotNet.Try.Markdown/CodeBlockAnnotations.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.Linq; using System.Threading.Tasks; +using System.Xml; namespace Microsoft.DotNet.Try.Markdown { @@ -49,6 +50,7 @@ public CodeBlockAnnotations( public bool Editable { get; } public bool Hidden { get; } public string Language { get; set; } + public string NormalizedLanguage { get; set; } public virtual Task TryGetExternalContent() => Task.FromResult(CodeBlockContentFetchResult.None); @@ -65,6 +67,11 @@ public virtual Task AddAttributes(AnnotatedCodeBlock block) block.AddAttribute("data-trydotnet-package-version", PackageVersion); } + if (!string.IsNullOrWhiteSpace(NormalizedLanguage)) + { + block.AddAttribute("data-trydotnet-language", NormalizedLanguage); + } + return Task.CompletedTask; } } diff --git a/Microsoft.DotNet.Try.Markdown/CodeFenceAnnotationsParser.cs b/Microsoft.DotNet.Try.Markdown/CodeFenceAnnotationsParser.cs index 049126ddc..bc66c6b77 100644 --- a/Microsoft.DotNet.Try.Markdown/CodeFenceAnnotationsParser.cs +++ b/Microsoft.DotNet.Try.Markdown/CodeFenceAnnotationsParser.cs @@ -13,17 +13,18 @@ namespace Microsoft.DotNet.Try.Markdown { public class CodeFenceAnnotationsParser { - private readonly IDefaultCodeBlockAnnotations defaultAnnotations; + private readonly IDefaultCodeBlockAnnotations _defaultAnnotations; private readonly Parser _parser; private readonly Lazy _modelBinder; - private string packageOptionName = "--package"; - private string packageVersionOptionName = "--package-version"; + private HashSet _supportedLanguages; + private const string PackageOptionName = "--package"; + private const string PackageVersionOptionName = "--package-version"; public CodeFenceAnnotationsParser( IDefaultCodeBlockAnnotations defaultAnnotations = null, Action configureCsharpCommand = null) { - this.defaultAnnotations = defaultAnnotations; + _defaultAnnotations = defaultAnnotations; _parser = CreateOptionsParser(configureCsharpCommand); _modelBinder = new Lazy(CreateModelBinder); } @@ -37,21 +38,21 @@ public virtual CodeFenceOptionsParseResult TryParseCodeFenceOptions( if (parserContext.TryGetDefaultCodeBlockAnnotations(out var defaults)) { if (defaults.Package != null && - !line.Contains(packageOptionName)) + !line.Contains(PackageOptionName)) { - line += $" {packageOptionName} {defaults.Package}"; + line += $" {PackageOptionName} {defaults.Package}"; } if (defaults.PackageVersion != null && - !line.Contains(packageVersionOptionName)) + !line.Contains(PackageVersionOptionName)) { - line += $" {packageVersionOptionName} {defaults.PackageVersion}"; + line += $" {PackageVersionOptionName} {defaults.PackageVersion}"; } } var result = _parser.Parse(line); - if (result.CommandResult.Name != "csharp" || + if (!_supportedLanguages.Contains( result.CommandResult.Name) || result.Tokens.Count == 1) { return CodeFenceOptionsParseResult.None; @@ -61,15 +62,14 @@ public virtual CodeFenceOptionsParseResult TryParseCodeFenceOptions( { return CodeFenceOptionsParseResult.Failed(new List(result.Errors.Select(e => e.Message))); } - else - { - var annotations = (CodeBlockAnnotations) _modelBinder.Value.CreateInstance(new BindingContext(result)); - annotations.Language = result.Tokens.First().Value; - annotations.RunArgs = Untokenize(result); + var annotations = (CodeBlockAnnotations)_modelBinder.Value.CreateInstance(new BindingContext(result)); - return CodeFenceOptionsParseResult.Succeeded(annotations); - } + annotations.Language = result.Tokens.First().Value; + annotations.NormalizedLanguage = result.CommandResult.Name; + annotations.RunArgs = Untokenize(result); + + return CodeFenceOptionsParseResult.Succeeded(annotations); } private static string Untokenize(ParseResult result) => @@ -82,37 +82,49 @@ private static string Untokenize(ParseResult result) => private Parser CreateOptionsParser(Action configureCsharpCommand = null) { - var packageOption = new Option(packageOptionName, + var packageOption = new Option(PackageOptionName, argument: new Argument()); - if (defaultAnnotations?.Package is string defaultPackage) + if (_defaultAnnotations?.Package is string defaultPackage) { packageOption.Argument.SetDefaultValue(defaultPackage); } - var packageVersionOption = new Option(packageVersionOptionName, + var packageVersionOption = new Option(PackageVersionOptionName, argument: new Argument()); - if (defaultAnnotations?.PackageVersion is string defaultPackageVersion) + if (_defaultAnnotations?.PackageVersion is string defaultPackageVersion) { packageVersionOption.Argument.SetDefaultValue(defaultPackageVersion); } + var languageCommands = new[] + { + CreateCsharpCommand(configureCsharpCommand, packageOption, packageVersionOption), + CreateFsharpCommand(configureCsharpCommand, packageOption, packageVersionOption) + }; + _supportedLanguages = new HashSet(languageCommands.Select(c => c.Name)); + return new Parser(new RootCommand( symbols: languageCommands)); + } + + private static Command CreateCsharpCommand(Action configureCsharpCommand, Option packageOption, + Option packageVersionOption) + { var csharp = new Command("csharp") - { - new Option("--destination-file", - argument: new Argument()), - new Option("--editable", - argument: new Argument(defaultValue: true)), - new Option("--hidden", - argument: new Argument(defaultValue: false)), - new Option("--region", - argument: new Argument()), - packageOption, - packageVersionOption, - new Option("--session", - argument: new Argument()) - }; + { + new Option("--destination-file", + argument: new Argument()), + new Option("--editable", + argument: new Argument(defaultValue: true)), + new Option("--hidden", + argument: new Argument(defaultValue: false)), + new Option("--region", + argument: new Argument()), + packageOption, + packageVersionOption, + new Option("--session", + argument: new Argument()) + }; configureCsharpCommand?.Invoke(csharp); @@ -121,8 +133,36 @@ private Parser CreateOptionsParser(Action configureCsharpCommand = null csharp.AddAlias("CSHARP"); csharp.AddAlias("cs"); csharp.AddAlias("c#"); + return csharp; + } - return new Parser(new RootCommand { csharp }); + private static Command CreateFsharpCommand(Action configureCsharpCommand, Option packageOption, + Option packageVersionOption) + { + var fsharp = new Command("fsharp") + { + new Option("--destination-file", + argument: new Argument()), + new Option("--editable", + argument: new Argument(defaultValue: true)), + new Option("--hidden", + argument: new Argument(defaultValue: false)), + new Option("--region", + argument: new Argument()), + packageOption, + packageVersionOption, + new Option("--session", + argument: new Argument()) + }; + + configureCsharpCommand?.Invoke(fsharp); + + fsharp.AddAlias("FS"); + fsharp.AddAlias("F#"); + fsharp.AddAlias("FSHARP"); + fsharp.AddAlias("fs"); + fsharp.AddAlias("f#"); + return fsharp; } } } \ No newline at end of file From 4ad0b00c4d95ae25d6223c99658a7f04795373b6 Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 4 Jun 2019 17:27:44 +0100 Subject: [PATCH 3/7] add langauge to protocol objects --- MLS.Agent/Markdown/AnnotatedCodeBlockExtensions.cs | 5 +++++ Microsoft.DotNet.Try.Protocol.ClientApi/Project.cs | 9 ++++++++- Microsoft.DotNet.Try.Protocol/Workspace.cs | 14 +++++++++++++- .../Models/Execution/WorkspaceExtensions.cs | 4 ++++ 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/MLS.Agent/Markdown/AnnotatedCodeBlockExtensions.cs b/MLS.Agent/Markdown/AnnotatedCodeBlockExtensions.cs index 7531c6268..02b257746 100644 --- a/MLS.Agent/Markdown/AnnotatedCodeBlockExtensions.cs +++ b/MLS.Agent/Markdown/AnnotatedCodeBlockExtensions.cs @@ -30,5 +30,10 @@ public static string ProjectOrPackageName(this AnnotatedCodeBlock block) (block.Annotations as LocalCodeBlockAnnotations)?.Project?.FullName ?? block.Annotations?.Package; } + public static string Language(this AnnotatedCodeBlock block) + { + return + (block.Annotations as LocalCodeBlockAnnotations)?.NormalizedLanguage; + } } } \ No newline at end of file diff --git a/Microsoft.DotNet.Try.Protocol.ClientApi/Project.cs b/Microsoft.DotNet.Try.Protocol.ClientApi/Project.cs index 27ed27b8c..b16dbfcca 100644 --- a/Microsoft.DotNet.Try.Protocol.ClientApi/Project.cs +++ b/Microsoft.DotNet.Try.Protocol.ClientApi/Project.cs @@ -12,12 +12,16 @@ namespace Microsoft.DotNet.Try.Protocol.ClientApi [JsonConverter(typeof(ProjectJsonConverter))] public class Project : FeatureContainer { + private const string DefaultLanguage = "csharp"; + public string ProjectTemplate { get; } public SourceFile[] Files { get; } + public string Language { get; } + - public Project(string projectTemplate, IEnumerable files) + public Project(string projectTemplate, IEnumerable files, string language = DefaultLanguage) { if (string.IsNullOrWhiteSpace(projectTemplate)) { @@ -39,6 +43,8 @@ public Project(string projectTemplate, IEnumerable files) ProjectTemplate = projectTemplate; + Language = string.IsNullOrWhiteSpace(language) ? DefaultLanguage : language; + } private class ProjectJsonConverter : FeatureContainerConverter @@ -46,6 +52,7 @@ private class ProjectJsonConverter : FeatureContainerConverter protected override void AddProperties(Project container, JObject o) { o.Add(new JProperty("projectTemplate", container.ProjectTemplate)); + o.Add(new JProperty("language", container.Language)); o.Add(new JProperty("files", JArray.FromObject(container.Files))); } } diff --git a/Microsoft.DotNet.Try.Protocol/Workspace.cs b/Microsoft.DotNet.Try.Protocol/Workspace.cs index d0d6d3ca9..f963c3f7c 100644 --- a/Microsoft.DotNet.Try.Protocol/Workspace.cs +++ b/Microsoft.DotNet.Try.Protocol/Workspace.cs @@ -4,21 +4,25 @@ using System; using System.ComponentModel.DataAnnotations; using System.Linq; +using Newtonsoft.Json; namespace Microsoft.DotNet.Try.Protocol { public class Workspace { private const string DefaultWorkspaceType = "script"; + private const string DefaultLanguage = "csharp"; public Workspace( string[] usings = null, File[] files = null, Buffer[] buffers = null, string workspaceType = DefaultWorkspaceType, + string language = DefaultLanguage, bool includeInstrumentation = false) { - WorkspaceType = workspaceType ?? DefaultWorkspaceType; + WorkspaceType = string.IsNullOrWhiteSpace(workspaceType) ? DefaultWorkspaceType : workspaceType; + Language = string.IsNullOrWhiteSpace(language) ? DefaultLanguage : language; Usings = usings ?? Array.Empty(); Usings = usings ?? Array.Empty(); Files = files ?? Array.Empty(); @@ -37,16 +41,24 @@ public Workspace( } } + [JsonProperty("language")] + public string Language { get; } + + [JsonProperty("files")] public File[] Files { get; } + [JsonProperty("usings")] public string[] Usings { get; } + [JsonProperty("workspaceType")] public string WorkspaceType { get; } + [JsonProperty("includeInstrumentation")] public bool IncludeInstrumentation { get; } [Required] [MinLength(1)] + [JsonProperty("buffers")] public Buffer[] Buffers { get; } public static Workspace FromSource( diff --git a/WorkspaceServer/Models/Execution/WorkspaceExtensions.cs b/WorkspaceServer/Models/Execution/WorkspaceExtensions.cs index 2f6c794aa..1c31e95cf 100644 --- a/WorkspaceServer/Models/Execution/WorkspaceExtensions.cs +++ b/WorkspaceServer/Models/Execution/WorkspaceExtensions.cs @@ -56,6 +56,7 @@ public static Workspace AddBuffer( workspace.Files, workspace.Buffers.Concat(new[] { new Buffer(BufferId.Parse(id), text) }).ToArray(), workspace.WorkspaceType, + workspace.Language, workspace.IncludeInstrumentation); public static Workspace RemoveBuffer( @@ -66,6 +67,7 @@ public static Workspace RemoveBuffer( workspace.Files, workspace.Buffers.Where(b => b.Id.ToString() != id).ToArray(), workspace.WorkspaceType, + workspace.Language, workspace.IncludeInstrumentation); public static Workspace ReplaceBuffer( @@ -85,6 +87,7 @@ public static Workspace AddFile( .ToArray(), workspace.Buffers, workspace.WorkspaceType, + workspace.Language, workspace.IncludeInstrumentation); public static Workspace ReplaceFile( @@ -99,6 +102,7 @@ public static Workspace ReplaceFile( .ToArray(), workspace.Buffers, workspace.WorkspaceType, + workspace.Language, workspace.IncludeInstrumentation); } From 76708b74e21da0b825bfbcb8c5a8d4558f1e9c97 Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 4 Jun 2019 17:28:13 +0100 Subject: [PATCH 4/7] language support in verify command --- .../CommandLine/VerifyCommandTests.cs | 35 +++++++++++++++++ MLS.Agent/CommandLine/VerifyCommand.cs | 39 ++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/MLS.Agent.Tests/CommandLine/VerifyCommandTests.cs b/MLS.Agent.Tests/CommandLine/VerifyCommandTests.cs index 2be4a5b2b..deda10de2 100644 --- a/MLS.Agent.Tests/CommandLine/VerifyCommandTests.cs +++ b/MLS.Agent.Tests/CommandLine/VerifyCommandTests.cs @@ -135,6 +135,41 @@ await VerifyCommand.Do( $"{root}{Path.DirectorySeparatorChar}doc.md*Line 2:*{root}{Path.DirectorySeparatorChar}Program.cs (in project {root}{Path.DirectorySeparatorChar}some.csproj)*".EnforceLF()); } + [Fact] + public async Task Fails_if_language_is_not_compatible_with_backing_project() + { + var root = Create.EmptyWorkspace(isRebuildablePackage: true).Directory; + + var directoryAccessor = new InMemoryDirectoryAccessor(root, root) + { + ("some.csproj", CsprojContents), + ("Program.cs", CompilingProgramCs), + ("support.fs", "let a = 0"), + ("doc.md", @" +```fs --source-file support.fs +``` +") + }.CreateFiles(); + + var console = new TestConsole(); + + await VerifyCommand.Do( + new VerifyOptions(root), + console, + () => directoryAccessor, + PackageRegistry.CreateForTryMode(root)); + + _output.WriteLine(console.Out.ToString()); + + console.Out + .ToString() + .EnforceLF() + .Trim() + .Should() + .Match( + $"*Build failed as project {root}{Path.DirectorySeparatorChar}some.csproj is not compatible with language fsharp*".EnforceLF()); + } + [Fact] public async Task When_non_editable_code_blocks_do_not_contain_errors_then_validation_succeeds() { diff --git a/MLS.Agent/CommandLine/VerifyCommand.cs b/MLS.Agent/CommandLine/VerifyCommand.cs index fc67c19b3..ecfacba79 100644 --- a/MLS.Agent/CommandLine/VerifyCommand.cs +++ b/MLS.Agent/CommandLine/VerifyCommand.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.CommandLine; +using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.DotNet.Try.Markdown; @@ -13,6 +14,7 @@ using WorkspaceServer; using WorkspaceServer.Servers.Roslyn; using Buffer = Microsoft.DotNet.Try.Protocol.Buffer; +using File = Microsoft.DotNet.Try.Protocol.File; namespace MLS.Agent.CommandLine { @@ -135,6 +137,17 @@ async Task ReportCompileResults( .Select(b => b.ProjectOrPackageName()) .FirstOrDefault(name => !string.IsNullOrWhiteSpace(name)); + var language = session + .Select(b => b.Language()) + .FirstOrDefault(name => !string.IsNullOrWhiteSpace(name)); + + if (!ProjectIsCompatibleWithLanguage(projectOrPackageName, language)) + { + SetError(); + + console.Out.WriteLine($" Build failed as project {projectOrPackageName} is not compatible with language {language}"); + } + var editableCodeBlocks = session.Where(b => b.Annotations.Editable).ToList(); var buffers = editableCodeBlocks @@ -165,6 +178,7 @@ async Task ReportCompileResults( var workspace = new Workspace( workspaceType: projectOrPackageName, + language: language, files: files.ToArray(), buffers: buffers.ToArray()); @@ -173,7 +187,7 @@ async Task ReportCompileResults( var processed = await mergeTransformer.TransformAsync(workspace); processed = await inliningTransformer.TransformAsync(processed); - processed = new Workspace(usings: processed.Usings, workspaceType: processed.WorkspaceType, files: processed.Files); + processed = new Workspace(usings: processed.Usings, workspaceType: processed.WorkspaceType, language:processed.Language, files: processed.Files); var result = await workspaceServer.Value.Compile(new WorkspaceRequest(processed)); @@ -257,5 +271,28 @@ void ReportCodeLinkageResults( } } } + + private static bool ProjectIsCompatibleWithLanguage(string projectOrPackageName, string language) + { + var extenstion = Path.GetExtension(projectOrPackageName)?.ToLowerInvariant(); + var supported = true; + if (!string.IsNullOrWhiteSpace(extenstion)) + { + switch (extenstion) + { + case ".csproj": + supported = StringComparer.OrdinalIgnoreCase.Compare(language, "csharp") == 0; + break; + + case ".fsproj": + supported = StringComparer.OrdinalIgnoreCase.Compare(language, "fsharp") == 0; + break; + default: + supported = false; + break; + } + } + return supported; + } } } \ No newline at end of file From ceb0e4aec746cc8048a8c18a51f69d5b5353e58d Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 4 Jun 2019 18:30:50 +0100 Subject: [PATCH 5/7] add language support in project and workspace objects in js --- Microsoft.DotNet.Try.Client/src/IState.ts | 1 + .../src/domInjection/autoEnable.ts | 15 ++- .../src/internals/workspace.ts | 7 +- Microsoft.DotNet.Try.js/src/project.ts | 25 +++-- .../test/internals/workspace.specs.ts | 91 ++++++++++++------- .../test/session.compileapi.specs.ts | 2 +- .../test/session.documentapi.specs.ts | 36 ++++---- .../test/session.intellisense.specs.ts | 8 +- .../test/session.projectapi.specs.ts | 2 +- 9 files changed, 122 insertions(+), 65 deletions(-) diff --git a/Microsoft.DotNet.Try.Client/src/IState.ts b/Microsoft.DotNet.Try.Client/src/IState.ts index 4339a799d..99befadf7 100644 --- a/Microsoft.DotNet.Try.Client/src/IState.ts +++ b/Microsoft.DotNet.Try.Client/src/IState.ts @@ -140,6 +140,7 @@ export interface IWorkspaceState export interface IWorkspace { workspaceType: string; + langauge?: string; files?: IWorkspaceFile[]; buffers: IWorkspaceBuffer[]; usings?: string[]; diff --git a/Microsoft.DotNet.Try.js/src/domInjection/autoEnable.ts b/Microsoft.DotNet.Try.js/src/domInjection/autoEnable.ts index 2d7893036..95addbf03 100644 --- a/Microsoft.DotNet.Try.js/src/domInjection/autoEnable.ts +++ b/Microsoft.DotNet.Try.js/src/domInjection/autoEnable.ts @@ -213,8 +213,10 @@ function internalAutoEnable( let files: SourceFile[] = []; let packageName: string = null; let packageVersion: string = null; + let language: string = "csharp"; let documentsToOpen: DocumentsToOpen = {}; let editorCount = -1; + for (let codeSource of session.codeSources) { editorCount++; @@ -222,7 +224,6 @@ function internalAutoEnable( let code = getCode(codeSource); let pacakgeAttribute = codeSource.dataset.trydotnetPackage; - if (!isNullOrUndefinedOrWhitespace(pacakgeAttribute)) { packageName = pacakgeAttribute; } @@ -233,6 +234,12 @@ function internalAutoEnable( packageVersion = packageVersionAttribute; } + + let languageAttribute = codeSource.dataset.trydotnetLanguage; + if (!isNullOrUndefinedOrWhitespace(languageAttribute)) { + language = languageAttribute; + } + let editorId = getTrydotnetEditorId(codeSource); if (!packageName) { @@ -282,7 +289,7 @@ function internalAutoEnable( } let prj: Project = { - package: packageName, + package: packageName, files: mergeFiles(files, includes, sessionId) }; @@ -290,6 +297,10 @@ function internalAutoEnable( prj.packageVersion = packageVersion; } + if (!isNullOrUndefinedOrWhitespace(language)) { + prj.language = language; + } + let documentsToInclude = getDocumentsToInclude(includes, sessionId); let config: Configuration = { debug: configuration.debug, diff --git a/Microsoft.DotNet.Try.js/src/internals/workspace.ts b/Microsoft.DotNet.Try.js/src/internals/workspace.ts index 23c0716d8..94b550065 100644 --- a/Microsoft.DotNet.Try.js/src/internals/workspace.ts +++ b/Microsoft.DotNet.Try.js/src/internals/workspace.ts @@ -13,6 +13,7 @@ import { isNullOrUndefined, isNullOrUndefinedOrWhitespace } from "../stringExten export interface IWorkspace { workspaceType: string; + langauge?: string; files?: IWorkspaceFile[]; buffers?: IWorkspaceBuffer[]; usings?: string[]; @@ -66,9 +67,13 @@ export class Workspace { this.openDocuments = {}; this.workspace = { - workspaceType: project.package + workspaceType: project.package }; + if(!isNullOrUndefinedOrWhitespace(project.language)){ + this.workspace.langauge = project.language; + } + if (project.usings) { this.workspace.usings = JSON.parse(JSON.stringify(project.usings)); } diff --git a/Microsoft.DotNet.Try.js/src/project.ts b/Microsoft.DotNet.Try.js/src/project.ts index 100e87b5e..4054ee0f6 100644 --- a/Microsoft.DotNet.Try.js/src/project.ts +++ b/Microsoft.DotNet.Try.js/src/project.ts @@ -1,9 +1,12 @@ +import { isNullOrUndefinedOrWhitespace } from "./stringExtensions"; + // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. export type Project = { package: string, - packageVersion?:string, + packageVersion?: string, + language?: string, files: SourceFile[], [key: string]: any }; @@ -18,22 +21,28 @@ export type SourceFileRegion = { content: string, }; -export function createProject(packageName: string, files: SourceFile[], usings?: string[]): Promise { - if (!packageName || packageName.length === 0) { +export function createProject(args: { packageName: string, files: SourceFile[], usings?: string[], language?: string }): Promise { + if (isNullOrUndefinedOrWhitespace(args.packageName)) { throw new Error("packageName can not be null or empty"); } - if (!files || files.length === 0) { + if (!args.files || args.files.length === 0) { throw new Error("at least a file is required"); } let project: Project = { - package: packageName, - files: JSON.parse(JSON.stringify(files)) + package: args.packageName, + files: JSON.parse(JSON.stringify(args.files)) }; - if (usings) { - project.usings = JSON.parse(JSON.stringify(usings)); + if (isNullOrUndefinedOrWhitespace(args.language)) { + project.language = "csharp"; + } else { + project.language = args.language; + } + + if (args.usings) { + project.usings = JSON.parse(JSON.stringify(args.usings)); } return Promise.resolve(project); diff --git a/Microsoft.DotNet.Try.js/test/internals/workspace.specs.ts b/Microsoft.DotNet.Try.js/test/internals/workspace.specs.ts index 8ccdca541..091e99519 100644 --- a/Microsoft.DotNet.Try.js/test/internals/workspace.specs.ts +++ b/Microsoft.DotNet.Try.js/test/internals/workspace.specs.ts @@ -19,14 +19,32 @@ describe("a workspace", () => { it("is marked as modified when propulated from a project", async () => { let ws = new Workspace(new FakeMessageBus("0"), new FakeIdGenerator()); - let project = await createProject("console", [{ name: "program.cs", content: "the program" }, { name: "otherFile.cs", content: "other file content" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "the program" }, { name: "otherFile.cs", content: "other file content" }] }); ws.fromProject(project); ws.isModified().should.be.true; }); + it("is should have default language", async () => { + let ws = new Workspace(new FakeMessageBus("0"), new FakeIdGenerator()); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "the program" }, { name: "otherFile.cs", content: "other file content" }] }); + ws.fromProject(project); + ws.isModified().should.be.true; + let request = ws.toSetWorkspaceRequests(); + request.workspace.langauge.should.be.equal("csharp"); + }); + + it("is retains the language of hte project", async () => { + let ws = new Workspace(new FakeMessageBus("0"), new FakeIdGenerator()); + let project = await createProject({ packageName: "console", language: "fsharp", files: [{ name: "program.cs", content: "the program" }, { name: "otherFile.cs", content: "other file content" }] }); + ws.fromProject(project); + ws.isModified().should.be.true; + let request = ws.toSetWorkspaceRequests(); + request.workspace.langauge.should.be.equal("fsharp"); + }); + it("is marked as modified when a document is opened", async () => { let ws = new Workspace(new FakeMessageBus("0"), new FakeIdGenerator()); - let project = await createProject("console", [{ name: "program.cs", content: "the program" }, { name: "otherFile.cs", content: "other file content" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "the program" }, { name: "otherFile.cs", content: "other file content" }] }); ws.fromProject(project); await ws.openDocument({ fileName: "program.cs" }); ws.isModified().should.be.true; @@ -34,7 +52,7 @@ describe("a workspace", () => { it("is not marked as modified when is exported as setWorkspaceRequest object", async () => { let ws = new Workspace(new FakeMessageBus("0"), new FakeIdGenerator()); - let project = await createProject("console", [{ name: "program.cs", content: "the program" }, { name: "otherFile.cs", content: "other file content" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "the program" }, { name: "otherFile.cs", content: "other file content" }] }); ws.fromProject(project); ws.isModified().should.be.true; ws.toSetWorkspaceRequests(); @@ -43,7 +61,7 @@ describe("a workspace", () => { it("is not marked as modified when a document is opened again", async () => { let ws = new Workspace(new FakeMessageBus("0"), new FakeIdGenerator()); - let project = await createProject("console", [{ name: "program.cs", content: "the program" }, { name: "otherFile.cs", content: "other file content" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "the program" }, { name: "otherFile.cs", content: "other file content" }] }); ws.fromProject(project); ws.isModified().should.be.true; let doc = await ws.openDocument({ fileName: "program.cs" }); @@ -54,7 +72,7 @@ describe("a workspace", () => { it("is marked as modified when a document is opened and the content is changed", async () => { let ws = new Workspace(new FakeMessageBus("0"), new FakeIdGenerator()); - let project = await createProject("console", [{ name: "program.cs", content: "the program" }, { name: "otherFile.cs", content: "other file content" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "the program" }, { name: "otherFile.cs", content: "other file content" }] }); ws.fromProject(project); let doc = await ws.openDocument({ fileName: "program.cs" }); doc.setContent("modified content"); @@ -63,7 +81,7 @@ describe("a workspace", () => { it("generates set workspace request object", async () => { let ws = new Workspace(new FakeMessageBus("0"), new FakeIdGenerator()); - let project = await createProject("console", [{ name: "program.cs", content: "the program" }, { name: "otherFile.cs", content: "other file content" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "the program" }, { name: "otherFile.cs", content: "other file content" }] }); ws.fromProject(project); let doc = await ws.openDocument({ fileName: "program.cs" }); doc.setContent("modified content"); @@ -76,11 +94,14 @@ describe("a workspace", () => { it("generates set workspace request object with active bufferId equal to the document currently open in an editor", async () => { let ws = new Workspace(new FakeMessageBus("0"), new FakeIdGenerator()); let project = await createProject( - "console", - [ - { name: "program.cs", content: "the program" }, - { name: "otherFile.cs", content: "other file content" } - ]); + { + packageName: "console", + files: + [ + { name: "program.cs", content: "the program" }, + { name: "otherFile.cs", content: "other file content" } + ] + }); ws.fromProject(project); let editorZero = new FakeMonacoTextEditor("0"); @@ -100,11 +121,13 @@ describe("a workspace", () => { it("can open a document and set its content", async () => { let ws = new Workspace(new FakeMessageBus("0"), new FakeIdGenerator()); let project = await createProject( - "console", - [ - { name: "program.cs", content: "the program" }, - { name: "otherFile.cs", content: "other file content" } - ]); + { + packageName: "console", + files: [ + { name: "program.cs", content: "the program" }, + { name: "otherFile.cs", content: "other file content" } + ] + }); ws.fromProject(project); let doc = await ws.openDocument({ fileName: "program.cs", content: "content override" }); @@ -115,11 +138,13 @@ describe("a workspace", () => { it("can open a document in the editor and set its content", async () => { let ws = new Workspace(new FakeMessageBus("0"), new FakeIdGenerator()); let project = await createProject( - "console", - [ - { name: "program.cs", content: "the program" }, - { name: "otherFile.cs", content: "other file content" } - ]); + { + packageName: "console", + files: [ + { name: "program.cs", content: "the program" }, + { name: "otherFile.cs", content: "other file content" } + ] + }); let editorZero = new FakeMonacoTextEditor("0"); ws.fromProject(project); @@ -134,11 +159,13 @@ describe("a workspace", () => { it("can open a document in one edtior at a time", async () => { let ws = new Workspace(new FakeMessageBus("0"), new FakeIdGenerator()); let project = await createProject( - "console", - [ - { name: "program.cs", content: "the program" }, - { name: "otherFile.cs", content: "other file content" } - ]); + { + packageName: "console", + files: [ + { name: "program.cs", content: "the program" }, + { name: "otherFile.cs", content: "other file content" } + ] + }); ws.fromProject(project); let editorZero = new FakeMonacoTextEditor("0"); @@ -158,11 +185,13 @@ describe("a workspace", () => { it("can open documents in different edtiors at same time", async () => { let ws = new Workspace(new FakeMessageBus("0"), new FakeIdGenerator()); let project = await createProject( - "console", - [ - { name: "program.cs", content: "the program" }, - { name: "otherFile.cs", content: "other file content" } - ]); + { + packageName: "console", + files: [ + { name: "program.cs", content: "the program" }, + { name: "otherFile.cs", content: "other file content" } + ] + }); ws.fromProject(project); let editorZero = new FakeMonacoTextEditor("0"); diff --git a/Microsoft.DotNet.Try.js/test/session.compileapi.specs.ts b/Microsoft.DotNet.Try.js/test/session.compileapi.specs.ts index 14b26fd1d..a9c49467a 100644 --- a/Microsoft.DotNet.Try.js/test/session.compileapi.specs.ts +++ b/Microsoft.DotNet.Try.js/test/session.compileapi.specs.ts @@ -38,7 +38,7 @@ describe("a user", () => { registerForRequestIdGeneration(configuration, editorIFrame, dom.window, (_rid) => "TestRun"); - let project = await createProject("console", [{ name: "program.cs", content: "" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "" }] }); session.openProject(project); let result = await session.compile(); diff --git a/Microsoft.DotNet.Try.js/test/session.documentapi.specs.ts b/Microsoft.DotNet.Try.js/test/session.documentapi.specs.ts index 7752fb638..37741bcbd 100644 --- a/Microsoft.DotNet.Try.js/test/session.documentapi.specs.ts +++ b/Microsoft.DotNet.Try.js/test/session.documentapi.specs.ts @@ -32,7 +32,7 @@ describe("A user", () => { describe("with a trydotnet session", () => { it("can open a document", async () => { let session = await createReadySession(configuration, editorIFrame, dom.window); - let project = await createProject("console", [{ name: "program.cs", content: "file content" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "file content" }] }); await session.openProject(project); let document = await session.openDocument({ fileName: "program.cs" }); @@ -42,7 +42,7 @@ describe("A user", () => { it("creates a empty document when the project does not have a matching file", async () => { let session = await createReadySession(configuration, editorIFrame, dom.window); - let project = await createProject("console", [{ name: "program.cs", content: "file content" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "file content" }] }); await session.openProject(project); let document = await session.openDocument({ fileName: "program_two.cs" }); @@ -53,7 +53,7 @@ describe("A user", () => { it("creates a empty document when using region and the project does not have a matching file", async () => { let session = await createReadySession(configuration, editorIFrame, dom.window); - let project = await createProject("console", [{ name: "program.cs", content: "file content" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "file content" }] }); await session.openProject(project); let document = await session.openDocument({ fileName: "program_two.cs", region: "controller" }); @@ -64,7 +64,7 @@ describe("A user", () => { it("can open a document with region as identifier", async () => { let session = await createReadySession(configuration, editorIFrame, dom.window); - let project = await createProject("console", [{ name: "program.cs", content: "//pre\n#region controller\n//content\n e#endregion\n//post/n" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "//pre\n#region controller\n//content\n e#endregion\n//post/n" }] }); await session.openProject(project); registerForRequestIdGeneration(configuration, editorIFrame, dom.window, (_rid) => "TestRun"); @@ -83,7 +83,7 @@ describe("A user", () => { let editorState = { content: "", documentId: "" }; let session = await createReadySession(configuration, editorIFrame, dom.window); let defaultEditor = session.getTextEditor(); - let project = await createProject("console", [{ name: "program.cs", content: "file content" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "file content" }] }); registerForRequestIdGeneration(configuration, editorIFrame, dom.window, _r => "TestRun0"); await session.openProject(project); registerForEditorMessages(configuration, editorIFrame, dom.window, editorState); @@ -99,7 +99,7 @@ describe("A user", () => { let editorState = { content: "", documentId: "" }; let session = await createReadySession(configuration, editorIFrame, dom.window); let defaultEditor = session.getTextEditor(); - let project = await createProject("console", [{ name: "program.cs", content: "file content" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "file content" }] }); await session.openProject(project); registerForRequestIdGeneration(configuration, editorIFrame, dom.window, _r => "TestRun1"); @@ -116,7 +116,7 @@ describe("A user", () => { let editorState = { content: "", documentId: "" }; let session = await createReadySession(configuration, editorIFrame, dom.window); let defaultEditor = session.getTextEditor(); - let project = await createProject("console", [{ name: "program.cs", content: "file content" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "file content" }] }); await session.openProject(project); registerForRequestIdGeneration(configuration, editorIFrame, dom.window, _r => "TestRun2"); registerForEditorMessages(configuration, editorIFrame, dom.window, editorState); @@ -135,11 +135,12 @@ describe("A user", () => { let session = await createReadySessionWithMultipleEditors(configuration, editorIFrames, dom.window); let project = await createProject( - "console", - [ - { name: "program.cs", content: "the program" }, - { name: "otherFile.cs", content: "other file content" } - ]); + { + packageName: "console", files: [ + { name: "program.cs", content: "the program" }, + { name: "otherFile.cs", content: "other file content" } + ] + }); await session.openProject(project); @@ -171,11 +172,12 @@ describe("A user", () => { let session = await createReadySessionWithMultipleEditors(configuration, editorIFrames, dom.window); let project = await createProject( - "console", - [ - { name: "program.cs", content: "the program" }, - { name: "otherFile.cs", content: "other file content" } - ]); + { + packageName: "console", files: [ + { name: "program.cs", content: "the program" }, + { name: "otherFile.cs", content: "other file content" } + ] + }); await session.openProject(project); diff --git a/Microsoft.DotNet.Try.js/test/session.intellisense.specs.ts b/Microsoft.DotNet.Try.js/test/session.intellisense.specs.ts index d6026071e..304951ab7 100644 --- a/Microsoft.DotNet.Try.js/test/session.intellisense.specs.ts +++ b/Microsoft.DotNet.Try.js/test/session.intellisense.specs.ts @@ -22,7 +22,7 @@ describe.skip("a user", () => { describe("with a trydotnet session", () => { it("can request completion list", async () => { let session = await createReadySession(configuration, editorIFrame, dom.window); - let project = await createProject("console", [{ name: "program.cs", content: "Console.W" }]) + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "Console.W" }] }) session.openProject(project); let completionListResult = await session.getCompletionList("program.cs", 9); completionListResult.should.not.be.null; @@ -30,7 +30,7 @@ describe.skip("a user", () => { it("can request completion list with a scope", async () => { let session = await createReadySession(configuration, editorIFrame, dom.window); - let project = await createProject("console", [{ name: "program.cs", content: "var a = 10; #region controller Console.W #endregion" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "var a = 10; #region controller Console.W #endregion" }] }); session.openProject(project); let completionListResult = await session.getCompletionList("program.cs", 9, "controller"); completionListResult.should.not.be.null; @@ -38,7 +38,7 @@ describe.skip("a user", () => { it("can request signature help", async () => { let session = await createReadySession(configuration, editorIFrame, dom.window); - let project = await createProject("console", [{ name: "program.cs", content: "Console.Write()" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "Console.Write()" }] }); session.openProject(project); let singatureHelpResult = await session.getSignatureHelp("program.cs", 14); @@ -47,7 +47,7 @@ describe.skip("a user", () => { it("can request signature help with a scope", async () => { let session = await createReadySession(configuration, editorIFrame, dom.window); - let project = await createProject("console", [{ name: "program.cs", content: "var a = 10; #region controller Console.Write() #endregion" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "var a = 10; #region controller Console.Write() #endregion" }] }); session.openProject(project); let singatureHelpResult = await session.getSignatureHelp("program.cs", 14, "controller"); diff --git a/Microsoft.DotNet.Try.js/test/session.projectapi.specs.ts b/Microsoft.DotNet.Try.js/test/session.projectapi.specs.ts index 81e647e56..8886acc0d 100644 --- a/Microsoft.DotNet.Try.js/test/session.projectapi.specs.ts +++ b/Microsoft.DotNet.Try.js/test/session.projectapi.specs.ts @@ -22,7 +22,7 @@ describe("a user", () => { describe("with a trydotnet session", () => { it("can open a project", async () => { let session = await createReadySession(configuration, editorIFrame, dom.window); - let project = await createProject("console", [{ name: "program.cs", content: "" }]); + let project = await createProject({ packageName: "console", files: [{ name: "program.cs", content: "" }] }); await session.openProject(project); }); }); From 1feea2352bb16cfc9d730c248510e46052df600b Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 4 Jun 2019 22:57:01 +0100 Subject: [PATCH 6/7] PR feedback --- .../CommandLine/VerifyCommandTests.cs | 2 +- MLS.Agent/CommandLine/VerifyCommand.cs | 6 +- .../Markdown/AnnotatedCodeBlockExtensions.cs | 2 +- .../LocalCodeFenceAnnotationsParser.cs | 84 +++++++++------- Microsoft.DotNet.Try.Client/src/IState.ts | 2 +- .../AnnotatedCodeBlock.cs | 2 +- .../CodeBlockAnnotations.cs | 21 +++- .../CodeFenceAnnotationsParser.cs | 97 ++++++++----------- .../src/internals/workspace.ts | 4 +- .../test/internals/workspace.specs.ts | 4 +- 10 files changed, 120 insertions(+), 104 deletions(-) diff --git a/MLS.Agent.Tests/CommandLine/VerifyCommandTests.cs b/MLS.Agent.Tests/CommandLine/VerifyCommandTests.cs index deda10de2..b41e5860e 100644 --- a/MLS.Agent.Tests/CommandLine/VerifyCommandTests.cs +++ b/MLS.Agent.Tests/CommandLine/VerifyCommandTests.cs @@ -146,7 +146,7 @@ public async Task Fails_if_language_is_not_compatible_with_backing_project() ("Program.cs", CompilingProgramCs), ("support.fs", "let a = 0"), ("doc.md", @" -```fs --source-file support.fs +```fs --source-file support.fs --project some.csproj ``` ") }.CreateFiles(); diff --git a/MLS.Agent/CommandLine/VerifyCommand.cs b/MLS.Agent/CommandLine/VerifyCommand.cs index ecfacba79..dd01e505c 100644 --- a/MLS.Agent/CommandLine/VerifyCommand.cs +++ b/MLS.Agent/CommandLine/VerifyCommand.cs @@ -274,11 +274,11 @@ void ReportCodeLinkageResults( private static bool ProjectIsCompatibleWithLanguage(string projectOrPackageName, string language) { - var extenstion = Path.GetExtension(projectOrPackageName)?.ToLowerInvariant(); + var extension = Path.GetExtension(projectOrPackageName)?.ToLowerInvariant(); var supported = true; - if (!string.IsNullOrWhiteSpace(extenstion)) + if (!string.IsNullOrWhiteSpace(extension)) { - switch (extenstion) + switch (extension) { case ".csproj": supported = StringComparer.OrdinalIgnoreCase.Compare(language, "csharp") == 0; diff --git a/MLS.Agent/Markdown/AnnotatedCodeBlockExtensions.cs b/MLS.Agent/Markdown/AnnotatedCodeBlockExtensions.cs index 02b257746..46b47f40c 100644 --- a/MLS.Agent/Markdown/AnnotatedCodeBlockExtensions.cs +++ b/MLS.Agent/Markdown/AnnotatedCodeBlockExtensions.cs @@ -33,7 +33,7 @@ public static string ProjectOrPackageName(this AnnotatedCodeBlock block) public static string Language(this AnnotatedCodeBlock block) { return - (block.Annotations as LocalCodeBlockAnnotations)?.NormalizedLanguage; + block.Annotations?.NormalizedLanguage; } } } \ No newline at end of file diff --git a/MLS.Agent/Markdown/LocalCodeFenceAnnotationsParser.cs b/MLS.Agent/Markdown/LocalCodeFenceAnnotationsParser.cs index 16c5f074b..f9a92de56 100644 --- a/MLS.Agent/Markdown/LocalCodeFenceAnnotationsParser.cs +++ b/MLS.Agent/Markdown/LocalCodeFenceAnnotationsParser.cs @@ -20,18 +20,24 @@ public class LocalCodeFenceAnnotationsParser : CodeFenceAnnotationsParser public LocalCodeFenceAnnotationsParser( IDirectoryAccessor directoryAccessor, PackageRegistry packageRegistry, - IDefaultCodeBlockAnnotations defaultAnnotations = null) : base(defaultAnnotations, csharp => - { - AddProjectOption(csharp, directoryAccessor); - AddSourceFileOption(csharp); - }) + IDefaultCodeBlockAnnotations defaultAnnotations = null) : base(defaultAnnotations, + csharp => + { + AddCsharpProjectOption(csharp, directoryAccessor); + AddSourceFileOption(csharp); + }, + fsharp => + { + AddFsharpProjectOption(fsharp, directoryAccessor); + AddSourceFileOption(fsharp); + }) { _directoryAccessor = directoryAccessor; _packageRegistry = packageRegistry ?? throw new ArgumentNullException(nameof(packageRegistry)); } public override CodeFenceOptionsParseResult TryParseCodeFenceOptions( - string line, + string line, MarkdownParserContext context = null) { var result = base.TryParseCodeFenceOptions(line, context); @@ -57,7 +63,7 @@ protected override ModelBinder CreateModelBinder() return new ModelBinder(typeof(LocalCodeBlockAnnotations)); } - private static void AddSourceFileOption(Command csharp) + private static void AddSourceFileOption(Command command) { var sourceFileArg = new Argument( result => @@ -76,46 +82,58 @@ private static void AddSourceFileOption(Command csharp) return ArgumentResult.Failure($"Error parsing the filename: {filename}"); }) - { - Name = "SourceFile", - Arity = ArgumentArity.ZeroOrOne - }; + { + Name = "SourceFile", + Arity = ArgumentArity.ZeroOrOne + }; var sourceFileOption = new Option("--source-file", argument: sourceFileArg); - csharp.AddOption(sourceFileOption); + command.AddOption(sourceFileOption); } - private static void AddProjectOption( - Command csharp, + private static void AddCsharpProjectOption( + Command command, IDirectoryAccessor directoryAccessor) + { + AddProjectOption(command, directoryAccessor, ".csproj"); + } + + private static void AddFsharpProjectOption( + Command command, + IDirectoryAccessor directoryAccessor) + { + AddProjectOption(command,directoryAccessor, ".fsproj"); + } + + private static void AddProjectOption( + Command command, + IDirectoryAccessor directoryAccessor, + string projectFileExtension) { var projectOptionArgument = new Argument(result => - { - var projectPath = new RelativeFilePath(result.Tokens.Select(t => t.Value).Single()); + { + var projectPath = new RelativeFilePath(result.Tokens.Select(t => t.Value).Single()); - if (directoryAccessor.FileExists(projectPath)) - { - return ArgumentResult.Success(directoryAccessor.GetFullyQualifiedPath(projectPath)); - } + if (directoryAccessor.FileExists(projectPath)) + { + return ArgumentResult.Success(directoryAccessor.GetFullyQualifiedPath(projectPath)); + } - return ArgumentResult.Failure($"Project not found: {projectPath.Value}"); - }) - { - Name = "project", - Arity = ArgumentArity.ExactlyOne - }; + return ArgumentResult.Failure($"Project not found: {projectPath.Value}"); + }) + { + Name = "project", + Arity = ArgumentArity.ExactlyOne + }; projectOptionArgument.SetDefaultValue(() => { var rootDirectory = directoryAccessor.GetFullyQualifiedPath(new RelativeDirectoryPath(".")); var projectFiles = directoryAccessor.GetAllFilesRecursively() - .Where(file => - { - return directoryAccessor.GetFullyQualifiedPath(file.Directory).FullName == rootDirectory.FullName && file.Extension == ".csproj"; - }) - .ToArray(); + .Where(file => directoryAccessor.GetFullyQualifiedPath(file.Directory).FullName == rootDirectory.FullName && file.Extension == projectFileExtension) + .ToArray(); if (projectFiles.Length == 1) { @@ -126,9 +144,9 @@ private static void AddProjectOption( }); var projectOption = new Option("--project", - argument: projectOptionArgument); + argument: projectOptionArgument); - csharp.Add(projectOption); + command.Add(projectOption); } } } \ No newline at end of file diff --git a/Microsoft.DotNet.Try.Client/src/IState.ts b/Microsoft.DotNet.Try.Client/src/IState.ts index 99befadf7..9c9423e8f 100644 --- a/Microsoft.DotNet.Try.Client/src/IState.ts +++ b/Microsoft.DotNet.Try.Client/src/IState.ts @@ -140,7 +140,7 @@ export interface IWorkspaceState export interface IWorkspace { workspaceType: string; - langauge?: string; + language?: string; files?: IWorkspaceFile[]; buffers: IWorkspaceBuffer[]; usings?: string[]; diff --git a/Microsoft.DotNet.Try.Markdown/AnnotatedCodeBlock.cs b/Microsoft.DotNet.Try.Markdown/AnnotatedCodeBlock.cs index 488704eef..233d8e975 100644 --- a/Microsoft.DotNet.Try.Markdown/AnnotatedCodeBlock.cs +++ b/Microsoft.DotNet.Try.Markdown/AnnotatedCodeBlock.cs @@ -99,7 +99,7 @@ protected virtual async Task AddAttributes(CodeBlockAnnotations annotations) AddAttributeIfNotNull("data-trydotnet-region", annotations.Region); AddAttributeIfNotNull("data-trydotnet-session-id", annotations.Session); - AddAttribute("class", $"language-{annotations.Language}"); + AddAttribute("class", $"language-{annotations.NormalizedLanguage}"); } public void RenderTo( diff --git a/Microsoft.DotNet.Try.Markdown/CodeBlockAnnotations.cs b/Microsoft.DotNet.Try.Markdown/CodeBlockAnnotations.cs index 03966bbef..8163fd4c4 100644 --- a/Microsoft.DotNet.Try.Markdown/CodeBlockAnnotations.cs +++ b/Microsoft.DotNet.Try.Markdown/CodeBlockAnnotations.cs @@ -4,6 +4,7 @@ using System; using System.CommandLine; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml; @@ -38,19 +39,23 @@ public CodeBlockAnnotations( { Session = $"Run{++_sessionIndex}"; } + + NormalizedLanguage = parseResult?.CommandResult.Name; + Language = parseResult?.Tokens.First().Value; + RunArgs = runArgs ?? Untokenize(parseResult); } public virtual string Package { get; } public RelativeFilePath DestinationFile { get; } public string Region { get; } - public string RunArgs { get; set; } + public string RunArgs { get; } public ParseResult ParseResult { get; } public string PackageVersion { get; } public string Session { get; } public bool Editable { get; } public bool Hidden { get; } - public string Language { get; set; } - public string NormalizedLanguage { get; set; } + public string Language { get; } + public string NormalizedLanguage { get; } public virtual Task TryGetExternalContent() => Task.FromResult(CodeBlockContentFetchResult.None); @@ -74,5 +79,15 @@ public virtual Task AddAttributes(AnnotatedCodeBlock block) return Task.CompletedTask; } + + private static string Untokenize(ParseResult result) => + result == null + ? null + : string.Join(" ", result.Tokens + .Select(t => t.Value) + .Skip(1) + .Select(t => Regex.IsMatch(t, @".*\s.*") + ? $"\"{t}\"" + : t)); } } \ No newline at end of file diff --git a/Microsoft.DotNet.Try.Markdown/CodeFenceAnnotationsParser.cs b/Microsoft.DotNet.Try.Markdown/CodeFenceAnnotationsParser.cs index bc66c6b77..81dd7cd8a 100644 --- a/Microsoft.DotNet.Try.Markdown/CodeFenceAnnotationsParser.cs +++ b/Microsoft.DotNet.Try.Markdown/CodeFenceAnnotationsParser.cs @@ -22,10 +22,11 @@ public class CodeFenceAnnotationsParser public CodeFenceAnnotationsParser( IDefaultCodeBlockAnnotations defaultAnnotations = null, - Action configureCsharpCommand = null) + Action configureCsharpCommand = null, + Action configureFsharpCommand = null) { _defaultAnnotations = defaultAnnotations; - _parser = CreateOptionsParser(configureCsharpCommand); + _parser = CreateOptionsParser(configureCsharpCommand, configureFsharpCommand); _modelBinder = new Lazy(CreateModelBinder); } @@ -52,7 +53,7 @@ public virtual CodeFenceOptionsParseResult TryParseCodeFenceOptions( var result = _parser.Parse(line); - if (!_supportedLanguages.Contains( result.CommandResult.Name) || + if (!_supportedLanguages.Contains(result.CommandResult.Name) || result.Tokens.Count == 1) { return CodeFenceOptionsParseResult.None; @@ -65,25 +66,27 @@ public virtual CodeFenceOptionsParseResult TryParseCodeFenceOptions( var annotations = (CodeBlockAnnotations)_modelBinder.Value.CreateInstance(new BindingContext(result)); - annotations.Language = result.Tokens.First().Value; - annotations.NormalizedLanguage = result.CommandResult.Name; - annotations.RunArgs = Untokenize(result); - return CodeFenceOptionsParseResult.Succeeded(annotations); } - private static string Untokenize(ParseResult result) => - string.Join(" ", result.Tokens - .Select(t => t.Value) - .Skip(1) - .Select(t => Regex.IsMatch(t, @".*\s.*") - ? $"\"{t}\"" - : t)); + private Parser CreateOptionsParser( + Action configureCsharpCommand = null, + Action configureFsharpCommand = null) + { - private Parser CreateOptionsParser(Action configureCsharpCommand = null) + var languageCommands = new[] + { + CreateCsharpCommand(configureCsharpCommand), + CreateFsharpCommand(configureFsharpCommand) + }; + _supportedLanguages = new HashSet(languageCommands.Select(c => c.Name)); + return new Parser(new RootCommand(symbols: languageCommands)); + } + + private IEnumerable