Skip to content
This repository was archived by the owner on Jun 30, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
973fd6d
Initial support for a legacy API shim
kzu Jul 23, 2019
b83445a
Add codegen support for new Mock<T>
kzu Jul 23, 2019
a58ad2d
Setup should happen as soon as possible
kzu Jul 23, 2019
72a1a28
Make sure we use the test calling assembly
adalon Jul 24, 2019
df46b70
Added SkipBehavior support for behavior pipeline
adalon Jul 24, 2019
5aaca90
Add new behavior for setup scope runs
kzu Jul 24, 2019
0c2ff79
Add setup overloads to avoid the mock T argument
kzu Jul 24, 2019
60237fe
Added AsMoq extension method
adalon Jul 24, 2019
eda348b
Simplify Behavior implementation by using SkipBehaviors
kzu Jul 24, 2019
edc7b68
Revert "Add setup overloads to avoid the mock T argument"
kzu Jul 24, 2019
efeefc3
Fix failing tests
kzu Jul 24, 2019
4e9d640
Move setup scope to Moq
kzu Jul 24, 2019
004e96e
Fix visiblity of various Sdk-like classes in the Moq main assembly
kzu Jul 24, 2019
91b4853
Added CallBase support
adalon Jul 24, 2019
58a67fe
Don't run FixupImports twice
kzu Jul 25, 2019
3f191e7
Ensure both analyzers and codefixers have NuGetPackageId metadata
kzu Jul 25, 2019
4c5db10
Optimize codegen performance for real world solutions
kzu Jul 25, 2019
e159b48
Do not clean unused namespaces, since it is costly for little benefit
kzu Jul 25, 2019
814afdc
Bump to latest Roslyn for VS2017 and updated supported code fix names
kzu Jul 25, 2019
708db5b
Ensure a clean restore is performed always, add CI feed
kzu Jul 25, 2019
20b9fd2
Set proper names for CallBase tests
adalon Jul 25, 2019
6143a8a
Bump TFV to the 16.0+ official one supporting NS2
kzu Jul 29, 2019
e5b1c4e
Properly generate code for generic mocks
kzu Aug 3, 2019
d9bbc2e
Add support for mocking generic types
kzu Aug 6, 2019
34a6c49
Move OverrideAllMembersCodeFix to CodeFix assembly to avoid csc error
kzu Aug 6, 2019
1e97239
Don't assume mocked types will be public
kzu Aug 6, 2019
e30d526
Cleanup and encapsulate the batch code fixer and avoid state capturing
kzu Aug 6, 2019
f4b4be4
Delete OverrideAllMembersCodeFix class that moved to CodeFix
kzu Aug 6, 2019
d5e5fd7
Re-enable end to end tests for VB since they work now
kzu Aug 6, 2019
cfddaf2
Fix roslyn internals tests from moved RoslynInternals.cs file
kzu Aug 8, 2019
2e4f6e8
Fix minor style issues flagged by codefactor.io
kzu Aug 8, 2019
462cf05
Unify naming conventions for runtime lookup
kzu Aug 8, 2019
83a22b0
Minor docs tweaks to CallBase
kzu Aug 8, 2019
db87731
Drastically simplify As<T> support by adding new Mock<T...Tn>
kzu Aug 8, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Optimize codegen performance for real world solutions
Based on the ASP.NET Core solution, we added the following optimizations:

* Add a FixAll provider to support batch code fixing of all mocks/stunts that need codegen
* Do not get *all* diagnostics from *all* analyzers when getting code fixes, but only those from analyzers that produce diagnostics the desired code fix provider can actually fix (otherwise, we were also getting a ton of diagnostics that might have nothing to do with the code fix provider and intended code fix name)

This *significantly* improves the performance of the code generation with real world solutions.
  • Loading branch information
kzu committed Jul 25, 2019
commit 4c5db10a1bd66466a2db84ddbb7fe35c20fe109a
38 changes: 19 additions & 19 deletions src/Stunts/Stunts.CodeFix/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions src/Stunts/Stunts.CodeFix/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,6 @@
<data name="DuplicateBaseType_Title" xml:space="preserve">
<value>Multiple base types specified</value>
</data>
<data name="GenerateStuntCodeFix_FixAllTitle" xml:space="preserve">
<value>Generate all stunts in {0} {1}</value>
</data>
<data name="GenerateStuntCodeFix_Title" xml:space="preserve">
<value>Generate Stunt</value>
</data>
Expand Down Expand Up @@ -183,6 +180,12 @@
<data name="SealedBaseType_Title" xml:space="preserve">
<value>Invalid sealed base type</value>
</data>
<data name="StuntCodeAction_Title" xml:space="preserve">
<value>Fix {0}</value>
</data>
<data name="StuntFixAllProvider_Title" xml:space="preserve">
<value>Fix all {0} in {1}</value>
</data>
<data name="UnsupportedNestedTypeAnalyzer_Description" xml:space="preserve">
<value>Generating code for nested types is not supported yet.</value>
</data>
Expand All @@ -192,9 +195,6 @@
<data name="UnsupportedNestedTypeAnalyzer_Title" xml:space="preserve">
<value>Unsupported nested type</value>
</data>
<data name="UpdateStuntCodeFix_FixAllTitle" xml:space="preserve">
<value>Update all stunts in {0} {1}</value>
</data>
<data name="UpdateStuntCodeFix_Title" xml:space="preserve">
<value>Update Stunt</value>
</data>
Expand Down
19 changes: 18 additions & 1 deletion src/Stunts/Stunts.CodeFix/StuntCodeAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Simplification;
using Stunts.Properties;

namespace Stunts
{
Expand All @@ -22,6 +23,14 @@ public class StuntCodeAction : CodeAction
readonly Diagnostic diagnostic;
readonly NamingConvention naming;

/// <summary>
/// Initializes the action.
/// </summary>
public StuntCodeAction(Document document, Diagnostic diagnostic, NamingConvention naming)
: this(Resources.StuntCodeAction_Title, document, diagnostic, naming)
{
}

/// <summary>
/// Initializes the action.
/// </summary>
Expand All @@ -34,7 +43,9 @@ public StuntCodeAction(string title, Document document, Diagnostic diagnostic, N
}

/// <inheritdoc />
public override string EquivalenceKey => diagnostic.Id + ":" + diagnostic.Properties["TargetFullName"];
//public override string EquivalenceKey => diagnostic.Id + ":" + diagnostic.Properties["TargetFullName"];

public override string EquivalenceKey => diagnostic.Id;

/// <inheritdoc />
public override string Title => title;
Expand Down Expand Up @@ -62,6 +73,12 @@ protected override async Task<Document> GetChangedDocumentAsync(CancellationToke
.Where(t => t != null)
.ToArray();

var name = naming.GetFullName(symbols);
// Check if the compilation already contains the type, which will be
// the case in batch fixing
if (compilation.GetTypeByMetadataName(name) != null)
return document;

var generator = SyntaxGenerator.GetGenerator(document.Project);
var stunts = CreateGenerator(naming);

Expand Down
3 changes: 2 additions & 1 deletion src/Stunts/Stunts.CodeFix/StuntCodeFixProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
/// <returns></returns>
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
//public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
public override FixAllProvider GetFixAllProvider() => null;
public override FixAllProvider GetFixAllProvider()
=> new StuntFixAllProvider(FixableDiagnosticIds.First(), CreateCodeAction);
}
}
112 changes: 87 additions & 25 deletions src/Stunts/Stunts.CodeFix/StuntFixAllProvider.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
using System.Collections.Generic;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Text;
using Stunts.Properties;

namespace Stunts
{
class StuntFixAllProvider : FixAllProvider
{
string titleFormat;
string diagnosticId;
readonly string diagnosticId;
readonly Func<Document, Diagnostic, CodeAction> codeActionFactory;

public StuntFixAllProvider(string titleFormat, string diagnosticId)
public StuntFixAllProvider(string diagnosticId, Func<Document, Diagnostic, CodeAction> codeActionFactory)
{
this.titleFormat = titleFormat;
this.diagnosticId = diagnosticId;
this.codeActionFactory = codeActionFactory;
}

public override async Task<CodeAction> GetFixAsync(FixAllContext fixAllContext)
Expand All @@ -34,7 +40,7 @@ public override async Task<CodeAction> GetFixAsync(FixAllContext fixAllContext)
if (diagnostics.Length > 0)
diagnosticsToFix.Add(new KeyValuePair<Project, ImmutableArray<Diagnostic>>(fixAllContext.Project, diagnostics));

title = string.Format(titleFormat, "document", fixAllContext.Document.Name);
title = string.Format(Resources.StuntFixAllProvider_Title, diagnosticId, fixAllContext.Document.Name);
break;
}

Expand All @@ -45,7 +51,7 @@ public override async Task<CodeAction> GetFixAsync(FixAllContext fixAllContext)
if (diagnostics.Length > 0)
diagnosticsToFix.Add(new KeyValuePair<Project, ImmutableArray<Diagnostic>>(project, diagnostics));

title = string.Format(titleFormat, "project", fixAllContext.Project.Name);
title = string.Format(Resources.StuntFixAllProvider_Title, diagnosticId, fixAllContext.Project.Name);
break;
}

Expand All @@ -58,7 +64,10 @@ public override async Task<CodeAction> GetFixAsync(FixAllContext fixAllContext)
diagnosticsToFix.Add(new KeyValuePair<Project, ImmutableArray<Diagnostic>>(project, diagnostics));
}

title = string.Format(titleFormat, "solution", "");
title = string.Format(Resources.StuntFixAllProvider_Title, diagnosticId,
fixAllContext.Solution.FilePath == null ?
nameof(FixAllScope.Solution) :
Path.GetFileName(fixAllContext.Solution.FilePath));
break;
}

Expand All @@ -68,48 +77,101 @@ public override async Task<CodeAction> GetFixAsync(FixAllContext fixAllContext)
break;
}

return new FixAllProxiesCodeAction(title, fixAllContext.Solution, diagnosticsToFix);
return new FixAllProxiesCodeAction(title, fixAllContext.Solution, diagnosticsToFix, codeActionFactory);
}

class FixAllProxiesCodeAction : CodeAction
{
string title;
Solution solution;
List<KeyValuePair<Project, ImmutableArray<Diagnostic>>> diagnosticsToFix;

public FixAllProxiesCodeAction(string title, Solution solution, List<KeyValuePair<Project, ImmutableArray<Diagnostic>>> diagnosticsToFix)
readonly string title;
readonly Solution solution;
readonly List<KeyValuePair<Project, ImmutableArray<Diagnostic>>> diagnosticsToFix;
readonly Func<Document, Diagnostic, CodeAction> codeActionFactory;

public FixAllProxiesCodeAction(string title,
Solution solution,
List<KeyValuePair<Project, ImmutableArray<Diagnostic>>> diagnosticsToFix,
Func<Document, Diagnostic, CodeAction> codeActionFactory)
{
this.title = title;
this.solution = solution;
this.diagnosticsToFix = diagnosticsToFix;
this.codeActionFactory = codeActionFactory;
}

public override string Title => title;

protected override async Task<Solution> GetChangedSolutionAsync(CancellationToken cancellationToken)
{
var changedSolution = solution;
var currentSolution = solution;
var fixerTasks = new List<Task>();
var addedDocs = new ConcurrentBag<Document>();
var updatedDocs = new ConcurrentBag<Document>();

foreach (var pair in diagnosticsToFix)
{
var project = changedSolution.GetProject(pair.Key.Id);
var project = solution.GetProject(pair.Key.Id);
Debug.Assert(project != null, "Failed to get project from solution.");
var diagnostics = pair.Value;

var group = diagnostics.GroupBy(d => d.Properties["Name"]);
var group = diagnostics.GroupBy(d => d.Properties["TargetFullName"]);
foreach (var diag in group)
{
var diagnostic = diag.First();
var document = project.GetDocument(diagnostic.Location.SourceTree);
var codeAction = new StuntCodeAction(Title, document, diagnostic, new NamingConvention());
var document = diagnostic.Location.IsInSource ?
project.Documents.FirstOrDefault(doc => doc.FilePath == diagnostic.Location.SourceTree.FilePath) :
project.GetDocument(diagnostic.Location.SourceTree);

Debug.Assert(document != null, "Failed to locate document from diagnostic.");

var operations = await codeAction.GetOperationsAsync(cancellationToken);
ApplyChangesOperation operation;
if ((operation = operations.OfType<ApplyChangesOperation>().FirstOrDefault()) != null)
changedSolution = operation.ChangedSolution;
fixerTasks.Add(Task.Run(async () =>
{
// NOTE: stunts don't need to update the source document where the diagnostic
// was reported, so we don't need any of the document updating stuff that
// we need when applying code fixers in the moq/stunt codegen itself on its own
// document. So we just apply the workspace changes and that's it.
var codeAction = codeActionFactory(document, diagnostic);
var operations = await codeAction.GetOperationsAsync(cancellationToken);
var applyChanges = operations.OfType<ApplyChangesOperation>().FirstOrDefault();
if (applyChanges != null)
{
var changes = applyChanges.ChangedSolution.GetChanges(currentSolution);
foreach (var change in changes.GetProjectChanges())
{
foreach (var addedId in change.GetAddedDocuments())
{
addedDocs.Add(applyChanges.ChangedSolution.GetDocument(addedId));
}
foreach (var changedId in change.GetChangedDocuments(true))
{
updatedDocs.Add(applyChanges.ChangedSolution.GetDocument(changedId));
}
}
}
}));
}
}

return changedSolution;
await Task.WhenAll(fixerTasks).ConfigureAwait(false);

foreach (var addedDoc in addedDocs)
{
var addedText = await addedDoc.GetTextAsync(cancellationToken).ConfigureAwait(false);
var addedVersion = await addedDoc.GetTextVersionAsync(cancellationToken).ConfigureAwait(false);
currentSolution = currentSolution.AddDocument(DocumentInfo.Create(
addedDoc.Id, addedDoc.Name, addedDoc.Folders,
addedDoc.SourceCodeKind,
TextLoader.From(TextAndVersion.Create(addedText, addedVersion, addedDoc.FilePath)),
addedDoc.FilePath));
}

foreach (var updatedDoc in updatedDocs)
{
var updatedText = await updatedDoc.GetTextAsync(cancellationToken).ConfigureAwait(false);
currentSolution = currentSolution.WithDocumentText(updatedDoc.Id, updatedText);
}

return currentSolution;
}
}
}
}
}
34 changes: 31 additions & 3 deletions src/Stunts/Stunts.Sdk/DocumentExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,24 @@ static async Task<ImmutableArray<ICodeFix>> GetCodeFixes(
if (analyzers.IsDefaultOrEmpty)
analyzers = builtInAnalyzers.Value;

var analyerCompilation = compilation.WithAnalyzers(analyzers, cancellationToken: cancellationToken);
var allDiagnostics = await analyerCompilation.GetAllDiagnosticsAsync(cancellationToken);
var supportedAnalyers = analyzers
.Where(a => a.SupportedDiagnostics.Any(d => provider.FixableDiagnosticIds.Contains(d.Id)))
.ToImmutableArray();

var allDiagnostics = default(ImmutableArray<Diagnostic>);

// This may be a compiler warning/error, not an analyzer-produced one, such as
// the missing abstract method implementations.
if (supportedAnalyers.IsEmpty)
{
allDiagnostics = compilation.GetDiagnostics(cancellationToken);
}
else
{
var analyerCompilation = compilation.WithAnalyzers(supportedAnalyers, cancellationToken: cancellationToken);
allDiagnostics = await analyerCompilation.GetAllDiagnosticsAsync(cancellationToken);
}

var diagnostics = allDiagnostics
.Where(x => provider.FixableDiagnosticIds.Contains(x.Id))
// Only consider the diagnostics raised by the target document.
Expand All @@ -111,9 +127,21 @@ await provider.RegisterCodeFixesAsync(
cancellationToken));
}

return codeFixes.ToImmutableArray();
var finalFixes = new List<ICodeFix>();

// All code actions without equivalence keys must be applied individually.
finalFixes.AddRange(codeFixes.Where(x => x.Action.EquivalenceKey == null));
// All code actions with the same equivalence key should be applied only once.
finalFixes.AddRange(codeFixes
.Where(x => x.Action.EquivalenceKey != null)
.GroupBy(x => x.Action.EquivalenceKey)
.Select(x => x.First()));

return finalFixes.ToImmutableArray();
}

// Debug view of all available providers and their metadata
// document.Project.Solution.Workspace.Services.HostServices.GetExports<CodeFixProvider, IDictionary<string, object>>().OrderBy(x => x.Metadata["Name"]?.ToString()).Select(x => $"{x.Metadata["Name"]}: {string.Join(", ", (string[])x.Metadata["Languages"])}" ).ToList()
static CodeFixProvider GetCodeFixProvider(Document document, string codeFixName)
=> codeFixName == nameof(OverrideAllMembersCodeFix) ? new OverrideAllMembersCodeFix() :
document.Project.Solution.Workspace.Services.HostServices
Expand Down
Loading