Skip to content

Commit

Permalink
Initial prototype of code fixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
ashmind committed Oct 1, 2016
1 parent 146381d commit 2e886da
Show file tree
Hide file tree
Showing 23 changed files with 360 additions and 67 deletions.
1 change: 1 addition & 0 deletions MirrorSharp.Common/Advanced/MiddlewareBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ protected MiddlewareBase(MirrorSharpOptions options) {
private ImmutableArray<ICommandHandler> CreateCommands() {
var commands = new ICommandHandler[26];
foreach (var command in new ICommandHandler[] {
new ApplyDiagnosticActionHandler(),
new CommitCompletionHandler(),
new MoveCursorHandler(),
new ReplaceTextHandler(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace MirrorSharp.Internal.Commands {
public class ApplyDiagnosticActionHandler : ICommandHandler {
public IImmutableList<char> CommandIds { get; } = ImmutableList.Create('F');

public async Task ExecuteAsync(ArraySegment<byte> data, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
var actionId = FastConvert.Utf8ByteArrayToInt32(data);
var action = session.CurrentCodeActions[actionId];
var operations = await action.GetOperationsAsync(cancellationToken).ConfigureAwait(false);
foreach (var operation in operations) {
operation.Apply(session.Workspace, cancellationToken);
}
var changes = await session.UpdateFromWorkspaceAsync().ConfigureAwait(false);

var writer = sender.StartJsonMessage("changes");
writer.WriteProperty("echo", false);
writer.WritePropertyStartArray("changes");
foreach (var change in changes) {
writer.WriteChange(change);
}
writer.WriteEndArray();
await sender.SendJsonMessageAsync(cancellationToken).ConfigureAwait(false);
}

public bool CanChangeSession => false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@ public async Task ExecuteAsync(ArraySegment<byte> data, WorkSession session, ICo
var change = await session.CompletionService.GetChangeAsync(session.Document, item, cancellationToken: cancellationToken).ConfigureAwait(false);

var writer = sender.StartJsonMessage("changes");
writer.WriteProperty("echo", true);
writer.WritePropertyStartArray("changes");
foreach (var textChange in change.TextChanges) {
writer.WriteStartObject();
writer.WriteProperty("start", textChange.Span.Start);
writer.WriteProperty("length", textChange.Span.Length);
writer.WriteProperty("text", textChange.NewText);
writer.WriteEndObject();
writer.WriteChange(textChange);
}
writer.WriteEndArray();
await sender.SendJsonMessageAsync(cancellationToken).ConfigureAwait(false);
Expand Down
44 changes: 41 additions & 3 deletions MirrorSharp.Common/Internal/Commands/SlowUpdateHandler.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;

namespace MirrorSharp.Internal.Commands {
public class SlowUpdateHandler : ICommandHandler {
private static readonly IReadOnlyCollection<CodeAction> NoCodeActions = new CodeAction[0];
public IImmutableList<char> CommandIds { get; } = ImmutableList.Create('U');

public async Task ExecuteAsync(ArraySegment<byte> data, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
var compilation = await session.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var diagnostics = await compilation.WithAnalyzers(session.Analyzers).GetAllDiagnosticsAsync(cancellationToken).ConfigureAwait(false);

await SendSlowUpdateAsync(diagnostics, sender, cancellationToken).ConfigureAwait(false);
await SendSlowUpdateAsync(diagnostics, session, sender, cancellationToken).ConfigureAwait(false);
}

private Task SendSlowUpdateAsync(ImmutableArray<Diagnostic> diagnostics, ICommandResultSender sender, CancellationToken cancellationToken) {
private async Task SendSlowUpdateAsync(ImmutableArray<Diagnostic> diagnostics, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
session.CurrentCodeActions.Clear();
var writer = sender.StartJsonMessage("slowUpdate");
writer.WritePropertyStartArray("diagnostics");
foreach (var diagnostic in diagnostics) {
Expand All @@ -32,10 +37,43 @@ private Task SendSlowUpdateAsync(ImmutableArray<Diagnostic> diagnostics, IComman
writer.WriteEndArray();
writer.WritePropertyName("span");
writer.WriteSpan(diagnostic.Location.SourceSpan);
var actions = await GetCodeActionsAsync(diagnostic, session, cancellationToken).ConfigureAwait(false);
if (actions.Count > 0) {
writer.WritePropertyStartArray("actions");
foreach (var action in actions) {
if (!RoslynInternals.GetIsInvokable(action)) // TODO: support subactions
continue;
var id = session.CurrentCodeActions.Count;
session.CurrentCodeActions.Add(action);
writer.WriteStartObject();
writer.WriteProperty("id", id);
writer.WriteProperty("title", action.Title);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
writer.WriteEndObject();
}
writer.WriteEndArray();
return sender.SendJsonMessageAsync(cancellationToken);
await sender.SendJsonMessageAsync(cancellationToken).ConfigureAwait(false);
}

private async Task<IReadOnlyCollection<CodeAction>> GetCodeActionsAsync(Diagnostic diagnostic, WorkSession session, CancellationToken cancellationToken) {
List<CodeAction> actions = null;
Action<CodeAction, ImmutableArray<Diagnostic>> registerCodeFix = (action, _) => {
if (actions == null)
actions = new List<CodeAction>();
actions.Add(action);
};
var fixContext = new CodeFixContext(session.Document, diagnostic, registerCodeFix, cancellationToken);
var providers = ImmutableDictionary.GetValueOrDefault(session.CodeFixProviders, diagnostic.Id);
if (providers == null)
return NoCodeActions;

foreach (var provider in providers) {
await provider.RegisterCodeFixesAsync(fixContext).ConfigureAwait(false);
}
return actions ?? NoCodeActions;
}

public bool CanChangeSession => false;
Expand Down
13 changes: 13 additions & 0 deletions MirrorSharp.Common/Internal/JsonWriterExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public static void WriteProperty(this JsonWriter writer, string name, int? value
writer.WriteValue(value);
}

public static void WriteProperty(this JsonWriter writer, string name, bool value) {
writer.WritePropertyName(name);
writer.WriteValue(value);
}

public static void WriteProperty(this JsonWriter writer, string name, string value) {
writer.WritePropertyName(name);
writer.WriteValue(value);
Expand All @@ -34,5 +39,13 @@ public static void WriteSpan(this JsonWriter writer, TextSpan span) {
writer.WriteProperty("length", span.Length);
writer.WriteEndObject();
}

public static void WriteChange(this JsonWriter writer, TextChange change) {
writer.WriteStartObject();
writer.WriteProperty("start", change.Span.Start);
writer.WriteProperty("length", change.Span.Length);
writer.WriteProperty("text", change.NewText);
writer.WriteEndObject();
}
}
}
18 changes: 18 additions & 0 deletions MirrorSharp.Common/Internal/RoslynInternals.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using AshMind.Extensions;

namespace MirrorSharp.Internal {
public static class RoslynInternals {
private static readonly Func<CodeAction, bool> _getIsInvokable =
typeof(CodeAction).GetTypeInfo()
.GetProperty("IsInvokable", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
.GetMethod.CreateDelegate<Func<CodeAction, bool>>();

public static bool GetIsInvokable(CodeAction action) => _getIsInvokable(action);
}
}
86 changes: 78 additions & 8 deletions MirrorSharp.Common/Internal/WorkSession.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
Expand All @@ -12,6 +16,7 @@

namespace MirrorSharp.Internal {
public class WorkSession {
private static readonly TextChange[] NoTextChanges = new TextChange[0];
private static readonly MefHostServices HostServices = MefHostServices.Create(MefHostServices.DefaultAssemblies.AddRange(new[] {
Assembly.Load(new AssemblyName("Microsoft.CodeAnalysis.Features")),
Assembly.Load(new AssemblyName("Microsoft.CodeAnalysis.CSharp.Features"))
Expand All @@ -31,23 +36,28 @@ public class WorkSession {
CreateAnalyzerReference("Microsoft.CodeAnalysis.CSharp.Features")
);

private static readonly ImmutableArray<DiagnosticAnalyzer> DefaultAnalyzers = CreateDefaultAnalyzers();
private static readonly ImmutableDictionary<string, ImmutableArray<CodeFixProvider>> DefaultCodeFixProviders = CreateDefaultCodeFixProviders();

public WorkSession() {
_workspace = new AdhocWorkspace(HostServices);
var projectId = ProjectId.CreateNewId();
var project = _workspace.AddProject(ProjectInfo.Create(
_workspace.AddProject(ProjectInfo.Create(
projectId, VersionStamp.Create(), "_", "_", "C#",
compilationOptions: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary),
metadataReferences: DefaultAssemblyReferences,
analyzerReferences: DefaultAnalyzerReferences
));
_sourceText = SourceText.From("");
_document = _workspace.AddDocument(projectId, "_", _sourceText);
_workspace.OpenDocument(_document.Id);
var closedDocument = _workspace.AddDocument(projectId, "_", _sourceText);
_workspace.OpenDocument(closedDocument.Id);
_document = _workspace.CurrentSolution.GetDocument(closedDocument.Id);
CompletionService = CompletionService.GetService(_document);
if (CompletionService == null)
throw new Exception("Failed to retrieve the completion service.");

Analyzers = ImmutableArray.CreateRange(project.AnalyzerReferences.SelectMany(r => r.GetAnalyzers("C#")));
Analyzers = DefaultAnalyzers;
CodeFixProviders = DefaultCodeFixProviders;
Buffers = new Buffers();
}

Expand All @@ -56,36 +66,96 @@ private static AnalyzerFileReference CreateAnalyzerReference(string assemblyName
return new AnalyzerFileReference(assembly.Location, new PreloadedAnalyzerAssemblyLoader(assembly));
}

private static ImmutableArray<DiagnosticAnalyzer> CreateDefaultAnalyzers() {
return ImmutableArray.CreateRange(DefaultAnalyzerReferences.SelectMany(r => r.GetAnalyzers("C#")));
}

private static ImmutableDictionary<string, ImmutableArray<CodeFixProvider>> CreateDefaultCodeFixProviders() {
var codeFixProviderTypes = DefaultAnalyzerReferences
.OfType<AnalyzerFileReference>()
.Select(a => a.GetAssembly())
.SelectMany(a => a.DefinedTypes)
.Where(t => t.IsDefined(typeof(ExportCodeFixProviderAttribute)));

var providersByDiagnosticIds = new Dictionary<string, IList<CodeFixProvider>>();
foreach (var type in codeFixProviderTypes) {
var provider = (CodeFixProvider)Activator.CreateInstance(type.AsType());

foreach (var id in provider.FixableDiagnosticIds) {
IList<CodeFixProvider> list;
if (!providersByDiagnosticIds.TryGetValue(id, out list)) {
list = new List<CodeFixProvider>();
providersByDiagnosticIds.Add(id, list);
}
list.Add(provider);
}
}
return ImmutableDictionary.CreateRange(
providersByDiagnosticIds.Select(p => new KeyValuePair<string, ImmutableArray<CodeFixProvider>>(p.Key, ImmutableArray.CreateRange(p.Value)))
);
}

public int CursorPosition { get; set; }

public SourceText SourceText {
get { return _sourceText; }
set {
if (value == _sourceText)
return;
_sourceText = value;
_documentOutOfDate = true;
}
}

public Document Document {
get {
if (_documentOutOfDate) {
_document = _document.WithText(_sourceText);
_documentOutOfDate = false;
}
EnsureDocumentUpToDate();
return _document;
}
}

[NotNull] public CompletionService CompletionService { get; }
[CanBeNull] public CompletionList CurrentCompletionList { get; set; }
[NotNull] public IList<CodeAction> CurrentCodeActions { get; } = new List<CodeAction>();

public Workspace Workspace {
get {
EnsureDocumentUpToDate();
return _workspace;
}
}
public Project Project => Document.Project;
public ImmutableArray<DiagnosticAnalyzer> Analyzers { get; }
public ImmutableDictionary<string, ImmutableArray<CodeFixProvider>> CodeFixProviders { get; }

public Buffers Buffers { get; }

private void EnsureDocumentUpToDate() {
if (!_documentOutOfDate)
return;

var document = _document.WithText(_sourceText);
if (!_workspace.TryApplyChanges(document.Project.Solution))
throw new Exception("Failed to apply changes to workspace.");
_document = _workspace.CurrentSolution.GetDocument(document.Id);
_documentOutOfDate = false;
}

public async Task<IReadOnlyList<TextChange>> UpdateFromWorkspaceAsync() {
EnsureDocumentUpToDate();
var project = _workspace.CurrentSolution.GetProject(Project.Id);
if (project == Project)
return NoTextChanges;

var oldText = _sourceText;
_document = project.GetDocument(_document.Id);
_sourceText = await _document.GetTextAsync();
return _sourceText.GetTextChanges(oldText);
}

public void Dispose() {
_workspace.Dispose();
}
}
}

3 changes: 2 additions & 1 deletion MirrorSharp.Common/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"Microsoft.CodeAnalysis.CSharp": "1.3.2",
"Microsoft.CodeAnalysis.Workspaces.Common": "1.3.2",
"Microsoft.CodeAnalysis.CSharp.Features": "1.3.2",
"Newtonsoft.Json": "9.0.1"
"Newtonsoft.Json": "9.0.1",
"AshMind.Extensions": "2.0.0-pre-20160719"
},

"frameworks": {
Expand Down
4 changes: 3 additions & 1 deletion MirrorSharp.Owin.Demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<link rel="stylesheet" href="bower_components/codemirror/lib/codemirror.css" />
<link rel="stylesheet" href="bower_components/codemirror/addon/lint/lint.css" />
<link rel="stylesheet" href="bower_components/codemirror/addon/hint/show-hint.css" />
<link rel="stylesheet" href="bower_components/codemirror-addon-lint-fix/addon/lint-fix/lint-fix.css" />
<link rel="stylesheet" href="bower_components/mirrorsharp/css/mirrorsharp.css" />
<style>
html, body { width: 100%; height: 100%; }
Expand All @@ -19,8 +20,9 @@

<script src="bower_components/codemirror/lib/codemirror.js"></script>
<script src="bower_components/codemirror/mode/clike/clike.js"></script>
<script src="bower_components/codemirror/addon/lint/lint.js"></script>
<script src="bower_components/codemirror-addon-lint-fix/addon/lint/lint.js"></script>
<script src="bower_components/codemirror/addon/hint/show-hint.js"></script>
<script src="bower_components/codemirror-addon-lint-fix/addon/lint-fix/lint-fix.js"></script>
<script src="bower_components/mirrorsharp/js/mirrorsharp.js"></script>
<script>
mirrorsharp(document.getElementsByTagName('textarea')[0], {
Expand Down
10 changes: 0 additions & 10 deletions MirrorSharp.Owin/Internal/DictionaryExtensions.cs

This file was deleted.

1 change: 1 addition & 0 deletions MirrorSharp.Owin/Internal/Middleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using MirrorSharp.Advanced;

namespace MirrorSharp.Owin.Internal {
using AshMind.Extensions;
using AppFunc = Func<IDictionary<string, object>, Task>;
using WebSocketAccept = Action<IDictionary<string, object>, Func<IDictionary<string, object>, Task>>;

Expand Down
3 changes: 2 additions & 1 deletion MirrorSharp.Owin/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"dependencies": {
"JetBrains.Annotations": "10.1.5",
"MirrorSharp.Common": "0.9.0-*",
"Owin": "1.0.0"
"Owin": "1.0.0",
"AshMind.Extensions": "2.0.0-pre-20160719"
},

"frameworks": {
Expand Down
Loading

0 comments on commit 2e886da

Please sign in to comment.