Skip to content

Commit 6c829af

Browse files
authored
Adds support for pre-processor like directives (#42)
* Adds support for pre-processor like directives Adds a nuget pre-processor like directive
1 parent 3be5608 commit 6c829af

15 files changed

+551
-84
lines changed

.editorconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[*.cs]
2+
3+
# CA1822: Mark members as static
4+
dotnet_diagnostic.CA1822.severity = silent

CSDiscord.sln

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26730.3
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.30404.54
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{443496A8-A004-4C46-8C8F-66FD55AB6402}"
77
ProjectSection(SolutionItems) = preProject
@@ -20,6 +20,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docker", "Docker", "{05C552
2020
Dockerfile = Dockerfile
2121
EndProjectSection
2222
EndProject
23+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C99A7F61-E624-47AC-9782-1B864E2DBB7A}"
24+
ProjectSection(SolutionItems) = preProject
25+
.editorconfig = .editorconfig
26+
EndProjectSection
27+
EndProject
2328
Global
2429
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2530
Debug|Any CPU = Debug|Any CPU

CSDiscordService.Tests/CSDiscordService.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<FrameworkReference Include="Microsoft.AspNetCore.App" />
1313
</ItemGroup>
1414
<ItemGroup>
15-
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="5.0.0-preview.7.20365.19" />
15+
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="5.0.0-preview.8.20414.8" />
1616
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0-preview-20200812-03" />
1717
<PackageReference Include="xunit" Version="2.4.1" />
1818
<PackageReference Include="xunit.runner.reporters" Version="2.4.1" />

CSDiscordService.Tests/EvalTests.cs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
1414
using CSDiscordService.Infrastructure.JsonFormatters;
1515
using System.Collections.Generic;
1616
using System.IO;
17+
using CSDiscordService.Eval;
1718

1819
namespace CSDiscordService.Tests
1920
{
2021
public class EvalTests : IDisposable
2122
{
23+
private bool disposedValue;
24+
2225
public EvalTests(ITestOutputHelper outputHelper)
2326
{
2427
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", Environments.Development);
@@ -176,6 +179,52 @@ public async Task Eval_ConsoleOutputIsCaptured(string expr, string consoleOut, o
176179
Assert.Equal(returnValue, result.ReturnValue);
177180
}
178181

182+
[Fact]
183+
public async Task Eval_SupportsNugetDirective()
184+
{
185+
var expr = "#nuget Newtonsoft.Json\nConsole.WriteLine(\"foo\");";
186+
var (result, statusCode) = await Execute(expr);
187+
188+
Assert.Equal(HttpStatusCode.OK, statusCode);
189+
Assert.StartsWith("Console", result.Code);
190+
}
191+
192+
[Fact]
193+
public async Task Eval_FaultyDirectiveFailsGracefully()
194+
{
195+
var expr = "#nuget asdasd asdasd asda\nConsole.WriteLine(\"foo\");";
196+
var (result, statusCode) = await Execute(expr);
197+
198+
Assert.Equal(HttpStatusCode.BadRequest, statusCode);
199+
Assert.StartsWith("#nuget", result.Code);
200+
Assert.Equal("Unable to resolve nuget package with id asdasd", result.Exception);
201+
}
202+
203+
[Fact]
204+
public void Eval_MissingArgumentsThrowsOnParse()
205+
{
206+
var expr = "#nuget";
207+
208+
Assert.Throws<ArgumentException>(() => NugetPreProcessorDirective.Parse(expr));
209+
}
210+
211+
[Fact]
212+
public async Task Eval_SupportsNugetDirectiveWithActualUsage()
213+
{
214+
var expr = @"#nuget ByteSize
215+
var input = ""80527998976 B"";
216+
if (ByteSize.TryParse(input, out var output))
217+
{
218+
Console.WriteLine(output);
219+
}";
220+
221+
var (result, statusCode) = await Execute(expr);
222+
223+
Assert.Equal(HttpStatusCode.OK, statusCode);
224+
Assert.StartsWith(" var input", result.Code);
225+
Assert.StartsWith("80.53 GB", result.ConsoleOut);
226+
}
227+
179228
[Fact]
180229
public async Task Eval_ConsoleOutputIsCapturedAndValueReturned()
181230
{
@@ -344,10 +393,24 @@ public async Task Eval_CanUseSystemDrawing()
344393
return (result, response.StatusCode);
345394
}
346395

396+
protected virtual void Dispose(bool disposing)
397+
{
398+
if (!disposedValue)
399+
{
400+
if (disposing)
401+
{
402+
Client.Dispose();
403+
Server.Dispose();
404+
}
405+
disposedValue = true;
406+
}
407+
}
408+
409+
347410
public void Dispose()
348411
{
349-
Client.Dispose();
350-
Server.Dispose();
412+
Dispose(disposing: true);
413+
GC.SuppressFinalize(this);
351414
}
352415
}
353416
}

CSDiscordService.Tests/ILTests.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
namespace CSDiscordService.Tests
1313
{
1414
public class ILTests : IDisposable
15-
{
15+
{
16+
private bool disposedValue;
17+
1618
public ILTests(ITestOutputHelper outputHelper)
1719
{
1820
var host = WebHost.CreateDefaultBuilder()
@@ -60,11 +62,24 @@ public async Task TestIfWorks(string script)
6062
return (result, response.StatusCode);
6163
}
6264

65+
protected virtual void Dispose(bool disposing)
66+
{
67+
if (!disposedValue)
68+
{
69+
if (disposing)
70+
{
71+
Client.Dispose();
72+
Server.Dispose();
73+
}
74+
disposedValue = true;
75+
}
76+
}
77+
6378

6479
public void Dispose()
6580
{
66-
Client.Dispose();
67-
Server.Dispose();
81+
Dispose(disposing: true);
82+
GC.SuppressFinalize(this);
6883
}
6984
}
7085
}
Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,11 @@
11
{
2-
"iisSettings": {
3-
"windowsAuthentication": false,
4-
"anonymousAuthentication": true,
5-
"iisExpress": {
6-
"applicationUrl": "http://localhost:55255/",
7-
"sslPort": 0
8-
}
9-
},
102
"profiles": {
11-
"IIS Express": {
12-
"commandName": "IISExpress",
13-
"launchBrowser": true,
14-
"environmentVariables": {
15-
"ASPNETCORE_ENVIRONMENT": "Development"
16-
}
17-
},
183
"CSDiscordService.Tests": {
194
"commandName": "Project",
205
"launchBrowser": true,
216
"environmentVariables": {
227
"ASPNETCORE_ENVIRONMENT": "Development"
238
},
24-
"applicationUrl": "http://localhost:55256/"
259
}
2610
}
2711
}

CSDiscordService/CSDiscordService.csproj

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,22 @@
88
<ApplicationInsightsAnnotationResourceId>/subscriptions/a46288ed-15a1-40ee-8fb2-d8867d876013/resourcegroups/CSharpDiscord/providers/microsoft.insights/components/CSDiscordService</ApplicationInsightsAnnotationResourceId>
99
</PropertyGroup>
1010

11+
<ItemGroup>
12+
<None Include="..\.editorconfig" Link=".editorconfig" />
13+
</ItemGroup>
14+
1115
<ItemGroup>
1216
<PackageReference Include="ICSharpCode.Decompiler" Version="3.2.0.3856" />
1317

1418
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
1519

16-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.7.0-6.20418.4" />
17-
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="5.0.0-preview.7.20364.11" />
20+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.7.0-6.20459.4" />
21+
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="5.0.0-preview.8.20407.11" />
1822
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
19-
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0-preview.7.20364.11" />
23+
<PackageReference Include="NuGet.Packaging" Version="5.8.0-preview.2.6776" />
24+
<PackageReference Include="NuGet.Protocol" Version="5.8.0-preview.2.6776" />
25+
<PackageReference Include="NuGet.Resolver" Version="5.7.0" />
26+
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0-preview.8.20407.11" />
2027

2128
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
2229
<PackageReference Include="System.Memory" Version="4.5.4" />

CSDiscordService/Eval/CSharpEval.cs

Lines changed: 18 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,80 +5,29 @@
55
using Microsoft.CodeAnalysis.Diagnostics;
66
using Microsoft.CodeAnalysis.Scripting;
77
using System;
8-
using System.Collections.Generic;
98
using System.Collections.Immutable;
109
using System.Diagnostics;
1110
using System.Linq;
12-
using System.Net.Http;
13-
using System.Reflection;
14-
using System.Runtime.CompilerServices;
1511
using System.Text;
1612
using System.Threading.Tasks;
1713
using System.Text.Json;
14+
using Microsoft.CodeAnalysis.Text;
1815

1916
namespace CSDiscordService.Eval
2017
{
2118
public class CSharpEval
2219
{
23-
private static readonly ImmutableArray<string> DefaultImports =
24-
ImmutableArray.Create(
25-
"Newtonsoft.Json",
26-
"Newtonsoft.Json.Linq",
27-
"System",
28-
"System.Collections",
29-
"System.Collections.Concurrent",
30-
"System.Collections.Immutable",
31-
"System.Collections.Generic",
32-
"System.Dynamic",
33-
"System.Security.Cryptography",
34-
"System.Globalization",
35-
"System.IO",
36-
"System.Linq",
37-
"System.Linq.Expressions",
38-
"System.Net",
39-
"System.Net.Http",
40-
"System.Numerics",
41-
"System.Reflection",
42-
"System.Reflection.Emit",
43-
"System.Runtime.CompilerServices",
44-
"System.Runtime.InteropServices",
45-
"System.Runtime.Intrinsics",
46-
"System.Runtime.Intrinsics.X86",
47-
"System.Text",
48-
"System.Text.RegularExpressions",
49-
"System.Threading",
50-
"System.Threading.Tasks",
51-
"System.Text.Json",
52-
"CSDiscordService.Eval"
53-
);
54-
55-
private static readonly ImmutableArray<Assembly> DefaultReferences =
56-
ImmutableArray.Create(
57-
typeof(Enumerable).GetTypeInfo().Assembly,
58-
typeof(HttpClient).GetTypeInfo().Assembly,
59-
typeof(List<>).GetTypeInfo().Assembly,
60-
typeof(string).GetTypeInfo().Assembly,
61-
typeof(Unsafe).GetTypeInfo().Assembly,
62-
typeof(ValueTuple).GetTypeInfo().Assembly,
63-
typeof(Globals).GetTypeInfo().Assembly,
64-
typeof(Memory<>).GetTypeInfo().Assembly
65-
);
66-
67-
private static readonly ScriptOptions Options =
68-
ScriptOptions.Default
69-
.WithLanguageVersion(LanguageVersion.Preview)
70-
.WithImports(DefaultImports)
71-
.WithReferences(DefaultReferences);
72-
7320
private static readonly ImmutableArray<DiagnosticAnalyzer> Analyzers =
7421
ImmutableArray.Create<DiagnosticAnalyzer>(new BlacklistedTypesAnalyzer());
7522

7623
private static readonly Random random = new Random();
7724
private readonly JsonSerializerOptions _serializerOptions;
25+
private readonly IPreProcessorService _preProcessor;
7826

79-
public CSharpEval(JsonSerializerOptions serializerOptons)
27+
public CSharpEval(JsonSerializerOptions serializerOptons, IPreProcessorService preProcessor)
8028
{
8129
_serializerOptions = serializerOptons;
30+
_preProcessor = preProcessor;
8231
}
8332

8433
public async Task<EvalResult> RunEvalAsync(string code)
@@ -88,7 +37,19 @@ public async Task<EvalResult> RunEvalAsync(string code)
8837
var env = new BasicEnvironment();
8938

9039
var sw = Stopwatch.StartNew();
91-
var eval = CSharpScript.Create(code, Options, typeof(Globals));
40+
41+
var context = new ScriptExecutionContext(code);
42+
try
43+
{
44+
await _preProcessor.PreProcess(context);
45+
}
46+
catch(Exception ex)
47+
{
48+
var diagnostics = Diagnostic.Create(new DiagnosticDescriptor("REPL01", ex.Message, ex.Message, "Code", DiagnosticSeverity.Error, true),
49+
Location.Create("", TextSpan.FromBounds(0,0), new LinePositionSpan(LinePosition.Zero, LinePosition.Zero)));
50+
return EvalResult.CreateErrorResult(code, string.Empty, TimeSpan.Zero, new[] { diagnostics }.ToImmutableArray());
51+
}
52+
var eval = CSharpScript.Create(context.Code, context.Options, typeof(Globals));
9253

9354
var compilation = eval.GetCompilation().WithAnalyzers(Analyzers);
9455

@@ -97,7 +58,7 @@ public async Task<EvalResult> RunEvalAsync(string code)
9758
sw.Stop();
9859

9960
var compileTime = sw.Elapsed;
100-
if (compileErrors.Length > 0)
61+
if (!compileErrors.IsEmpty)
10162
{
10263
return EvalResult.CreateErrorResult(code, sb.ToString(), sw.Elapsed, compileErrors);
10364
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
5+
namespace CSDiscordService.Eval
6+
{
7+
public class DefaultPreProcessorService : IPreProcessorService
8+
{
9+
private readonly IEnumerable<IDirectiveProcessor> _directives;
10+
11+
public DefaultPreProcessorService(IEnumerable<IDirectiveProcessor> directives)
12+
{
13+
_directives = directives;
14+
}
15+
public async Task PreProcess(ScriptExecutionContext context)
16+
{
17+
var codeLines = context.Code.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None);
18+
var remainingLines = new List<string>();
19+
20+
foreach(var line in codeLines)
21+
{
22+
var lineHandled = false;
23+
foreach(var directive in _directives)
24+
{
25+
if (lineHandled)
26+
{
27+
continue;
28+
}
29+
30+
var canProcess = directive.CanProcessDirective(line);
31+
if(!canProcess)
32+
{
33+
continue;
34+
}
35+
36+
lineHandled = true;
37+
await directive.PreProcess(line, context);
38+
}
39+
40+
if(!lineHandled)
41+
{
42+
remainingLines.Add(line);
43+
}
44+
}
45+
46+
context.Code = string.Join(Environment.NewLine, remainingLines);
47+
}
48+
}
49+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Threading.Tasks;
2+
3+
namespace CSDiscordService.Eval
4+
{
5+
public interface IDirectiveProcessor
6+
{
7+
bool CanProcessDirective(string directive);
8+
Task PreProcess(string directive, ScriptExecutionContext context);
9+
}
10+
}

0 commit comments

Comments
 (0)