diff --git a/appveyor.yml b/appveyor.yml
index 7e1f1874..b777f4ed 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-os: Visual Studio 2017
+image: Visual Studio 2017 Preview
init:
- git config --global core.autocrlf input
diff --git a/build.proj b/build.proj
index 1d3863c9..77e0f891 100644
--- a/build.proj
+++ b/build.proj
@@ -4,13 +4,13 @@
Debug
$(RestoreOutputPath)\
- Configuration=$(Configuration)
+ Configuration=$(Configuration);Dev=15.0
out
-
+
diff --git a/src/Analyzer.Vsix/BindingRedirects.targets b/src/Analyzer.Vsix/BindingRedirects.targets
new file mode 100644
index 00000000..392a54c7
--- /dev/null
+++ b/src/Analyzer.Vsix/BindingRedirects.targets
@@ -0,0 +1,120 @@
+
+
+
+
+ BindingRedirects.pkgdef
+
+ ^Microsoft.CodeAnalysis|^System.Composition
+
+ true
+
+
+
+
+
+ CollectBindingRedirected;
+ CleanBindingRedirectsPackage;
+ GenerateBindingRedirectsPackage
+
+
+ BindingRedirects;
+ $(GetCopyToOutputDirectoryItemsDependsOn)
+
+
+ $(ResolveReferencesDependsOn);
+ BindingRedirects
+
+
+ $(BuildDependsOn);
+ ReportBindingRedirects
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2.9.9.9999
+
+
+
+
+
+
+
+
+
+
+
+ <_FusionName>%(BindingRedirected.FusionName)
+ <_IsFullName Condition=" $(_FusionName.IndexOf(',')) != '-1' ">true
+
+
+ <_Name>$(_FusionName.Substring(0, $(_FusionName.IndexOf(','))))
+ <_IndexOfToken>$(_FusionName.IndexOf('PublicKeyToken='))
+ <_IndexOfToken>$([MSBuild]::Add($(_IndexOfToken), 15))
+ <_Token>$(_FusionName.Substring($(_IndexOfToken)))
+
+
+
+
+ $([System.Guid]::NewGuid().ToString().ToUpper())
+ $(_Name)
+ $(_Token)
+ 0.0.0.0
+ 99.9.9.9
+ %(BindingRedirected.Version)
+
+
+
+
+
+
+
+
+ true
+ PreserveNewest
+ $(BindingRedirects)
+
+
+
+
+
diff --git a/src/Analyzer.Vsix/Moq.Analyzer.Vsix.csproj b/src/Analyzer.Vsix/Moq.Analyzer.Vsix.csproj
new file mode 100644
index 00000000..5c8d9218
--- /dev/null
+++ b/src/Analyzer.Vsix/Moq.Analyzer.Vsix.csproj
@@ -0,0 +1,43 @@
+
+
+
+ net461;net462
+ net461
+
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ Moq
+
+ Moq.vsix
+ None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Analyzer.Vsix/Moq.Analyzer.Vsix.targets b/src/Analyzer.Vsix/Moq.Analyzer.Vsix.targets
new file mode 100644
index 00000000..d97127c1
--- /dev/null
+++ b/src/Analyzer.Vsix/Moq.Analyzer.Vsix.targets
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Analyzer.Vsix/Properties/launchSettings.json b/src/Analyzer.Vsix/Properties/launchSettings.json
new file mode 100644
index 00000000..f9ac517d
--- /dev/null
+++ b/src/Analyzer.Vsix/Properties/launchSettings.json
@@ -0,0 +1,9 @@
+{
+ "profiles": {
+ "Moq.Analyzer.Vsix": {
+ "commandName": "Executable",
+ "executablePath": "$(VsInstallRoot)\\Common7\\IDE\\devenv.exe",
+ "commandLineArgs": "/rootSuffix $(VSSDKTargetPlatformRegRootSuffix)"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Analyzer.Vsix/source.extension.vsixmanifest b/src/Analyzer.Vsix/source.extension.vsixmanifest
new file mode 100644
index 00000000..a70bd64c
--- /dev/null
+++ b/src/Analyzer.Vsix/source.extension.vsixmanifest
@@ -0,0 +1,23 @@
+
+
+
+
+ Moq
+ Provides analyzers and code fixes for Moq.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Analyzer/GenerateProxyCodeFix.cs b/src/Analyzer/GenerateProxyCodeFix.cs
new file mode 100644
index 00000000..306d90e3
--- /dev/null
+++ b/src/Analyzer/GenerateProxyCodeFix.cs
@@ -0,0 +1,103 @@
+using System.Collections.Immutable;
+using System.Composition;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.Host;
+using System.IO;
+using Microsoft.CodeAnalysis.Text;
+using Moq.Analyzer.Properties;
+using Moq.Proxy;
+using Microsoft.CodeAnalysis.Editing;
+using Microsoft.CodeAnalysis.Simplification;
+using Microsoft.CodeAnalysis.Formatting;
+
+namespace Moq.Analyzer
+{
+ [ExportCodeFixProvider(LanguageNames.CSharp, new [] { LanguageNames.VisualBasic }, Name = nameof(GenerateProxyCodeFix)), Shared]
+ public class GenerateProxyCodeFix : CodeFixProvider
+ {
+ ICodeAnalysisServices analysisServices;
+
+ [ImportingConstructor]
+ public GenerateProxyCodeFix([Import(AllowDefault = true)] ICodeAnalysisServices analysisServices) => this.analysisServices = analysisServices;
+
+ public sealed override ImmutableArray FixableDiagnosticIds
+ {
+ get => ImmutableArray.Create(MissingProxyAnalyzer.DiagnosticId, OutdatedProxyAnalyzer.DiagnosticId);
+ }
+
+ public sealed override FixAllProvider GetFixAllProvider()
+ {
+ // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
+ // TODO: implement
+ return WellKnownFixAllProviders.BatchFixer;
+ }
+
+ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
+
+ var diagnostic = context.Diagnostics.First();
+ var sourceToken = root.FindToken(diagnostic.Location.SourceSpan.Start);
+
+ // Find the invocation identified by the diagnostic.
+ var invocation =
+ (SyntaxNode)sourceToken.Parent.AncestorsAndSelf().OfType().FirstOrDefault() ??
+ (SyntaxNode)sourceToken.Parent.AncestorsAndSelf().OfType().FirstOrDefault();
+
+ // Register a code action that will invoke the fix.
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ title: Strings.MissingProxyCodeFix.Title,
+ createChangedSolution: c => GenerateProxyAsync(context.Document, invocation, c),
+ equivalenceKey: nameof(GenerateProxyCodeFix)),
+ diagnostic);
+ }
+
+ async Task GenerateProxyAsync(Document document, SyntaxNode invocation, CancellationToken cancellationToken)
+ {
+ var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
+ var symbol = semanticModel.GetSymbolInfo(invocation);
+ if (symbol.Symbol?.Kind == SymbolKind.Method)
+ {
+ var method = (IMethodSymbol)symbol.Symbol;
+ var generator = SyntaxGenerator.GetGenerator(document.Project);
+ var (name, syntax) = ProxyGenerator.CreateProxy(method.TypeArguments, generator);
+
+ var extension = document.Project.Language == LanguageNames.CSharp ? ".cs" : ".vb";
+ var file = Path.Combine(Path.GetDirectoryName(document.Project.FilePath), ProxyFactory.ProxyNamespace, name + extension);
+
+ var proxyDoc = document.Project.Documents.FirstOrDefault(d => d.Name == Path.GetFileName(file) && d.Folders.SequenceEqual(new[] { "Proxies" }));
+ if (proxyDoc == null)
+ {
+ proxyDoc = document.Project.AddDocument(Path.GetFileName(file),
+ syntax,
+ new[] { "Proxies" },
+ file);
+ }
+ else
+ {
+ proxyDoc = proxyDoc.WithSyntaxRoot(syntax);
+ }
+
+ proxyDoc = await ProxyGenerator.ApplyVisitors(proxyDoc, analysisServices, cancellationToken);
+ // This is somewhat expensive, but since we're adding it to the user' solution, we might
+ // as well make it look great ;)
+ proxyDoc = await Simplifier.ReduceAsync(proxyDoc);
+ proxyDoc = await Formatter.FormatAsync(proxyDoc);
+ syntax = await proxyDoc.GetSyntaxRootAsync();
+ //syntax = syntax.NormalizeWhitespace();
+
+ return proxyDoc.WithSyntaxRoot(syntax).Project.Solution;
+ }
+ else
+ {
+ return document.Project.Solution;
+ }
+ }
+ }
+}
diff --git a/src/Analyzer/MissingProxyAnalyzer.cs b/src/Analyzer/MissingProxyAnalyzer.cs
new file mode 100644
index 00000000..4fe1fa02
--- /dev/null
+++ b/src/Analyzer/MissingProxyAnalyzer.cs
@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Moq.Analyzer.Properties;
+using Moq.Proxy;
+
+namespace Moq.Analyzer
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
+ public class MissingProxyAnalyzer : DiagnosticAnalyzer
+ {
+ public const string DiagnosticId = "MOQ001";
+
+ static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.MissingProxyAnalyzer_Title), Resources.ResourceManager, typeof(Resources));
+ static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.MissingProxyAnalyzer_Description), Resources.ResourceManager, typeof(Resources));
+ static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.MissingProxyAnalyzer_Message), Resources.ResourceManager, typeof(Resources));
+
+ const string Category = "Build";
+
+ static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description);
+
+ public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, Microsoft.CodeAnalysis.CSharp.SyntaxKind.InvocationExpression);
+ context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.InvocationExpression);
+ }
+
+ static void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context)
+ {
+ var generator = context.Compilation.GetTypeByMetadataName(typeof(ProxyGeneratorAttribute).FullName);
+ if (generator == null)
+ return;
+
+ var symbol = context.Compilation.GetSemanticModel(context.Node.SyntaxTree).GetSymbolInfo(context.Node);
+ if (symbol.Symbol?.Kind == SymbolKind.Method)
+ {
+ var method = (IMethodSymbol)symbol.Symbol;
+ if (method.GetAttributes().Any(x => x.AttributeClass == generator) &&
+ // Skip generic method definitions since they are typically usability overloads
+ // like Mock.Of(...)
+ !method.TypeArguments.Any(x => x.Kind == SymbolKind.TypeParameter))
+ {
+ var name = ProxyGenerator.GetProxyFullName(method.TypeArguments);
+
+ // See if the proxy already exists
+ var proxy = context.Compilation.GetTypeByMetadataName(name);
+ if (proxy == null)
+ {
+ var diagnostic = Diagnostic.Create(Rule, context.Node.GetLocation(),
+ new [] { new KeyValuePair("Name", name) }.ToImmutableDictionary(),
+ name);
+
+ context.ReportDiagnostic(diagnostic);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Analyzer/Moq.Analyzer.csproj b/src/Analyzer/Moq.Analyzer.csproj
new file mode 100644
index 00000000..4670cf91
--- /dev/null
+++ b/src/Analyzer/Moq.Analyzer.csproj
@@ -0,0 +1,37 @@
+
+
+
+ net461
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
\ No newline at end of file
diff --git a/src/Analyzer/OutdatedProxyAnalyzer.cs b/src/Analyzer/OutdatedProxyAnalyzer.cs
new file mode 100644
index 00000000..c5c3ab5e
--- /dev/null
+++ b/src/Analyzer/OutdatedProxyAnalyzer.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Moq.Analyzer.Properties;
+using Moq.Proxy;
+
+namespace Moq.Analyzer
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
+ public class OutdatedProxyAnalyzer : DiagnosticAnalyzer
+ {
+ public const string DiagnosticId = "MOQ002";
+
+ static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.OutdatedProxyAnalyzer_Title), Resources.ResourceManager, typeof(Resources));
+ static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.OutdatedProxyAnalyzer_Description), Resources.ResourceManager, typeof(Resources));
+ static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.OutdatedProxyAnalyzer_Message), Resources.ResourceManager, typeof(Resources));
+
+ const string Category = "Build";
+
+ static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description);
+
+ public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, Microsoft.CodeAnalysis.CSharp.SyntaxKind.InvocationExpression);
+ context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.InvocationExpression);
+ }
+
+ private static void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context)
+ {
+ var symbol = context.Compilation.GetSemanticModel(context.Node.SyntaxTree).GetSymbolInfo(context.Node);
+ if (symbol.Symbol?.Kind == SymbolKind.Method)
+ {
+ var generator = context.Compilation.GetTypeByMetadataName(typeof(ProxyGeneratorAttribute).FullName);
+ var method = (IMethodSymbol)symbol.Symbol;
+ if (method.GetAttributes().Any(x => x.AttributeClass == generator) &&
+ // Skip generic method definitions since they are typically usability overloads
+ // like Mock.Of(...)
+ !method.TypeArguments.Any(x => x.Kind == SymbolKind.TypeParameter))
+ {
+ var name = ProxyGenerator.GetProxyFullName(method.TypeArguments);
+
+ // See if the proxy already exists
+ var proxy = context.Compilation.GetTypeByMetadataName(name);
+ if (proxy != null)
+ {
+ // See if the symbol has any diagnostics associated
+ var diag = context.Compilation.GetDiagnostics();
+ var proxyPath = proxy.Locations[0].GetLineSpan().Path;
+
+ Func isProxyLoc = (loc) => loc.IsInSource && loc.GetLineSpan().Path == proxyPath;
+
+ var proxyDiag = diag
+ .Where(d => isProxyLoc(d.Location) || d.AdditionalLocations.Any(a => isProxyLoc(a)))
+ .Where(d => d.Id == "CS0535");
+
+ if (proxyDiag.Any())
+ {
+ // If there are compilation errors, we should update the proxy.
+ var diagnostic = Diagnostic.Create(Rule, context.Node.GetLocation(), name);
+
+ context.ReportDiagnostic(diagnostic);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Analyzer/Properties/Resources.Designer.cs b/src/Analyzer/Properties/Resources.Designer.cs
new file mode 100644
index 00000000..8c0ffc58
--- /dev/null
+++ b/src/Analyzer/Properties/Resources.Designer.cs
@@ -0,0 +1,144 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Moq.Analyzer.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Moq.Analyzer.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Only classes and interfaces can be used in proxies. Invalid set of symbols: {0}..
+ ///
+ internal static string InvalidProxyTypes {
+ get {
+ return ResourceManager.GetString("InvalidProxyTypes", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Invoked method requires a proxy to be generated at design-time or compile-time..
+ ///
+ internal static string MissingProxyAnalyzer_Description {
+ get {
+ return ResourceManager.GetString("MissingProxyAnalyzer_Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A proxy named '{0}' was not found in the current compilation..
+ ///
+ internal static string MissingProxyAnalyzer_Message {
+ get {
+ return ResourceManager.GetString("MissingProxyAnalyzer_Message", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Proxy not found.
+ ///
+ internal static string MissingProxyAnalyzer_Title {
+ get {
+ return ResourceManager.GetString("MissingProxyAnalyzer_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Generate Proxy.
+ ///
+ internal static string MissingProxyCodeFix_Title {
+ get {
+ return ResourceManager.GetString("MissingProxyCodeFix_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Existing generated proxy is no longer up-to-date..
+ ///
+ internal static string OutdatedProxyAnalyzer_Description {
+ get {
+ return ResourceManager.GetString("OutdatedProxyAnalyzer_Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Proxy named '{0}' must be updated..
+ ///
+ internal static string OutdatedProxyAnalyzer_Message {
+ get {
+ return ResourceManager.GetString("OutdatedProxyAnalyzer_Message", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Proxy must be updated.
+ ///
+ internal static string OutdatedProxyAnalyzer_Title {
+ get {
+ return ResourceManager.GetString("OutdatedProxyAnalyzer_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Only one base type can be specified for a proxy to generate and it must be the first type in the list. Invalid set of symbols: {0}..
+ ///
+ internal static string WrongProxyBaseType {
+ get {
+ return ResourceManager.GetString("WrongProxyBaseType", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/Analyzer/Properties/Resources.resx b/src/Analyzer/Properties/Resources.resx
new file mode 100644
index 00000000..2c2f4382
--- /dev/null
+++ b/src/Analyzer/Properties/Resources.resx
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Only classes and interfaces can be used in proxies. Invalid set of symbols: {0}.
+
+
+ Invoked method requires a proxy to be generated at design-time or compile-time.
+
+
+ A proxy named '{0}' was not found in the current compilation.
+
+
+ Proxy not found
+
+
+ Generate Proxy
+
+
+ Existing generated proxy is no longer up-to-date.
+
+
+ Proxy named '{0}' must be updated.
+
+
+ Proxy must be updated
+
+
+ Only one base type can be specified for a proxy to generate and it must be the first type in the list. Invalid set of symbols: {0}.
+
+
\ No newline at end of file
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
index 39353f9a..d672aa30 100644
--- a/src/Directory.Build.targets
+++ b/src/Directory.Build.targets
@@ -1,7 +1,18 @@
-
+
$(MSBuildAllProjects);$(MSBuildThisFileFullPath)
-
+
+
+
+
+ $(ResolveAssemblyReferencesDependsOn);
+ ResolveProjectReferences
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Moq.Sdk.sln b/src/Moq.Sdk.sln
index c9b4f5aa..35222844 100644
--- a/src/Moq.Sdk.sln
+++ b/src/Moq.Sdk.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26430.6
+VisualStudioVersion = 15.0.26430.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Sdk", "Sdk\Moq.Sdk.csproj", "{234A39D6-6265-462D-A482-6A22BD351718}"
EndProject
@@ -13,10 +13,32 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Moq.Testing", "..\test\Moq.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Proxy.Generator", "Proxy\Proxy.Generator\Moq.Proxy.Generator.csproj", "{9115EAB8-8897-4454-8270-0156EBA6A5DF}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Sdk.Build", "Sdk.Build\Moq.Sdk.Build.csproj", "{CE7D2B30-CDFF-443E-9916-F6A072C823EB}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Proxy.Generator.Build", "Proxy\Proxy.Generator.Build\Moq.Proxy.Generator.Build.csproj", "{35ED3B8A-E10A-42A6-BD81-DCE46480A6F2}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManualProxies", "..\test\ManualProxies\ManualProxies.csproj", "{9952FC0D-48B0-4F60-81E3-C9ADE3BE6475}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Proxy.Tests", "..\test\Proxy.Tests\Moq.Proxy.Tests.csproj", "{377223FA-B07C-4E99-852A-0D4E5D0A4347}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Analyzer", "Analyzer\Moq.Analyzer.csproj", "{D6C737CA-5E8C-49F2-96DA-4062264D6866}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Analyzer.Vsix", "Analyzer.Vsix\Moq.Analyzer.Vsix.csproj", "{D54839E8-325E-4994-86BC-1079516CB245}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{072E955D-AE09-41B0-810C-7A005CF276CA}"
+ ProjectSection(SolutionItems) = preProject
+ ..\appveyor.yml = ..\appveyor.yml
+ ..\build.cmd = ..\build.cmd
+ ..\build.proj = ..\build.proj
+ Directory.Build.props = Directory.Build.props
+ Directory.Build.targets = Directory.Build.targets
+ Version.targets = Version.targets
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Proxy.Generator.Console", "Proxy\Proxy.Generator.Console\Moq.Proxy.Generator.Console.csproj", "{886905FA-99B0-48E8-BD3E-6C6ED96CE69F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Proxy.Sdk", "Proxy\Proxy.Sdk\Moq.Proxy.Sdk.csproj", "{8E379F7D-D919-4104-AB74-B5C071EBD0A4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Sdk.Generator", "Sdk.Generator\Moq.Sdk.Generator.csproj", "{00D2D86C-1B49-4E6A-A659-30DBC01F9761}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\test\Moq.Testing\Moq.Testing.projitems*{1bd7dfde-2fc8-4899-9903-6ca5724cc185}*SharedItemsImports = 13
@@ -42,14 +64,38 @@ Global
{9115EAB8-8897-4454-8270-0156EBA6A5DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9115EAB8-8897-4454-8270-0156EBA6A5DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9115EAB8-8897-4454-8270-0156EBA6A5DF}.Release|Any CPU.Build.0 = Release|Any CPU
- {CE7D2B30-CDFF-443E-9916-F6A072C823EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CE7D2B30-CDFF-443E-9916-F6A072C823EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CE7D2B30-CDFF-443E-9916-F6A072C823EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CE7D2B30-CDFF-443E-9916-F6A072C823EB}.Release|Any CPU.Build.0 = Release|Any CPU
{35ED3B8A-E10A-42A6-BD81-DCE46480A6F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{35ED3B8A-E10A-42A6-BD81-DCE46480A6F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{35ED3B8A-E10A-42A6-BD81-DCE46480A6F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{35ED3B8A-E10A-42A6-BD81-DCE46480A6F2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9952FC0D-48B0-4F60-81E3-C9ADE3BE6475}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9952FC0D-48B0-4F60-81E3-C9ADE3BE6475}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9952FC0D-48B0-4F60-81E3-C9ADE3BE6475}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9952FC0D-48B0-4F60-81E3-C9ADE3BE6475}.Release|Any CPU.Build.0 = Release|Any CPU
+ {377223FA-B07C-4E99-852A-0D4E5D0A4347}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {377223FA-B07C-4E99-852A-0D4E5D0A4347}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {377223FA-B07C-4E99-852A-0D4E5D0A4347}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {377223FA-B07C-4E99-852A-0D4E5D0A4347}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D6C737CA-5E8C-49F2-96DA-4062264D6866}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D6C737CA-5E8C-49F2-96DA-4062264D6866}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D6C737CA-5E8C-49F2-96DA-4062264D6866}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D6C737CA-5E8C-49F2-96DA-4062264D6866}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D54839E8-325E-4994-86BC-1079516CB245}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D54839E8-325E-4994-86BC-1079516CB245}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D54839E8-325E-4994-86BC-1079516CB245}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D54839E8-325E-4994-86BC-1079516CB245}.Release|Any CPU.Build.0 = Release|Any CPU
+ {886905FA-99B0-48E8-BD3E-6C6ED96CE69F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {886905FA-99B0-48E8-BD3E-6C6ED96CE69F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {886905FA-99B0-48E8-BD3E-6C6ED96CE69F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {886905FA-99B0-48E8-BD3E-6C6ED96CE69F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8E379F7D-D919-4104-AB74-B5C071EBD0A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8E379F7D-D919-4104-AB74-B5C071EBD0A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8E379F7D-D919-4104-AB74-B5C071EBD0A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8E379F7D-D919-4104-AB74-B5C071EBD0A4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {00D2D86C-1B49-4E6A-A659-30DBC01F9761}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {00D2D86C-1B49-4E6A-A659-30DBC01F9761}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {00D2D86C-1B49-4E6A-A659-30DBC01F9761}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {00D2D86C-1B49-4E6A-A659-30DBC01F9761}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Moq.sln b/src/Moq.sln
index d15ae0ce..0cb7c49a 100644
--- a/src/Moq.sln
+++ b/src/Moq.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26430.6
+VisualStudioVersion = 15.0.26706.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Proxy", "Proxy\Proxy\Moq.Proxy.csproj", "{C6932108-488E-4138-B411-6C1F63A0024D}"
EndProject
@@ -32,6 +32,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Proxy.Generator", "Prox
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Sdk.Tests", "..\test\Sdk.Tests\Moq.Sdk.Tests.csproj", "{EB66BC5E-4072-4F26-9772-979D8561883F}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Proxy.Sdk", "Proxy\Proxy.Sdk\Moq.Proxy.Sdk.csproj", "{3402E15C-DB88-4DFA-906E-8B529AFA3AC3}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\test\Moq.Testing\Moq.Testing.projitems*{1bd7dfde-2fc8-4899-9903-6ca5724cc185}*SharedItemsImports = 13
@@ -73,8 +75,15 @@ Global
{EB66BC5E-4072-4F26-9772-979D8561883F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB66BC5E-4072-4F26-9772-979D8561883F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB66BC5E-4072-4F26-9772-979D8561883F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3402E15C-DB88-4DFA-906E-8B529AFA3AC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3402E15C-DB88-4DFA-906E-8B529AFA3AC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3402E15C-DB88-4DFA-906E-8B529AFA3AC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3402E15C-DB88-4DFA-906E-8B529AFA3AC3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {DA4CFD03-827D-482B-9304-83456D2A8115}
+ EndGlobalSection
EndGlobal
diff --git a/src/Moq/MockExtensions.cs b/src/Moq/MockExtensions.cs
index 7b13a544..4a221d18 100644
--- a/src/Moq/MockExtensions.cs
+++ b/src/Moq/MockExtensions.cs
@@ -21,14 +21,52 @@ public static TResult Callback(this TResult target, Action callback)
mock.Invocations.Remove(setup.Invocation);
var behavior = mock.BehaviorFor(setup);
- behavior.Behaviors.Add(new InvocationBehavior(
- (mi, next) =>
- {
- callback();
- return next()(mi, next);
- },
- "Callback")
- );
+
+ // If there is already a behavior wrap it instead,
+ // so we can do a callback after even if it's a
+ // shortcircuiting one like Returns.
+ if (behavior.Behaviors.Count > 0)
+ {
+ var wrapped = behavior.Behaviors.Pop();
+ behavior.Behaviors.Add(new InvocationBehavior(
+ (mi, next) =>
+ {
+ // If the wrapped target does not invoke the next
+ // behavior (us), then we invoke the callback explicitly.
+ var called = false;
+
+ // Note we're tweaking the GetNextBehavior to always
+ // call us, before invoking the actual next behavior.
+ var result = wrapped.Invoke(mi, () => (_, __) =>
+ {
+ callback();
+ called = true;
+ return next()(mi, next);
+ });
+
+ // The Returns behavior does not invoke the GetNextBehavior,
+ // and therefore we won't have been called in that case,
+ // so call the callback before returning.
+ if (!called)
+ callback();
+
+ return result;
+ }
+ ,
+ "Callback")
+ );
+ }
+ else
+ {
+ behavior.Behaviors.Add(new InvocationBehavior(
+ (mi, next) =>
+ {
+ callback();
+ return next()(mi, next);
+ },
+ "Callback")
+ );
+ }
}
return target;
diff --git a/src/Proxy/Proxy.Generator.Build/Moq.Proxy.Generator.targets b/src/Proxy/Proxy.Generator.Build/Moq.Proxy.Generator.targets
index da986ca7..11c7eddc 100644
--- a/src/Proxy/Proxy.Generator.Build/Moq.Proxy.Generator.targets
+++ b/src/Proxy/Proxy.Generator.Build/Moq.Proxy.Generator.targets
@@ -48,7 +48,7 @@
AdditionalProxies="@(AdditionalProxyForType)"
AdditionalGenerators="@(AdditionalProxyGenerator)"
ToolPath="$(PGenPath)"
- Debug="$(DebugPGen)"/>
+ Debug="$(DebugPGen)"/>
diff --git a/src/Proxy/Proxy.Generator.Console/Moq.Proxy.Generator.Console.csproj b/src/Proxy/Proxy.Generator.Console/Moq.Proxy.Generator.Console.csproj
index edf2b779..37f0bd1e 100644
--- a/src/Proxy/Proxy.Generator.Console/Moq.Proxy.Generator.Console.csproj
+++ b/src/Proxy/Proxy.Generator.Console/Moq.Proxy.Generator.Console.csproj
@@ -17,6 +17,7 @@
true
+
true
diff --git a/src/Proxy/Proxy.Generator/DocumentVisitorLayer.cs b/src/Proxy/Proxy.Generator/DocumentVisitorLayer.cs
deleted file mode 100644
index 4b4dd191..00000000
--- a/src/Proxy/Proxy.Generator/DocumentVisitorLayer.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace Moq.Proxy
-{
- ///
- /// The layer at which an acts.
- ///
- public static class DocumentVisitorLayer
- {
- ///
- /// Initial proxy generation phase, where the members are laid out with
- /// default implementations that basically throw .
- ///
- public const string Scaffold = nameof(Scaffold);
-
- ///
- /// First phase after scaffold, which does the initial proxy implementation rewriting
- /// to replace the methods that throw generated
- /// during scaffold to invoke the instead.
- ///
- public const string Rewrite = nameof(Rewrite);
-
- ///
- /// Final phase that allows generators to perform additional generation beyond scaffold
- /// and inital proxy rewriting. Members generated in this phase are not rewritten at all
- /// to use the .
- ///
- public const string Fixup = nameof(Fixup);
- }
-}
diff --git a/src/Proxy/Proxy.Generator/Moq.Proxy.Generator.csproj b/src/Proxy/Proxy.Generator/Moq.Proxy.Generator.csproj
index b5f227a7..b21ebf3d 100644
--- a/src/Proxy/Proxy.Generator/Moq.Proxy.Generator.csproj
+++ b/src/Proxy/Proxy.Generator/Moq.Proxy.Generator.csproj
@@ -10,11 +10,12 @@
-
+
+
diff --git a/src/Proxy/Proxy.Generator/ProxyGenerator.cs b/src/Proxy/Proxy.Generator/ProxyGenerator.cs
index b34bb547..0dea2c9d 100644
--- a/src/Proxy/Proxy.Generator/ProxyGenerator.cs
+++ b/src/Proxy/Proxy.Generator/ProxyGenerator.cs
@@ -36,6 +36,10 @@ public static HostServices CreateHost(ImmutableArray additionalGenerator
// TODO: error handling
=> CreateHost(additionalGenerators.Select(x => Assembly.LoadFrom(x)).ToArray());
+ public static string GetProxyName(ImmutableArray types) => string.Join("", types.Select(x => x.Name)) + "Proxy";
+
+ public static string GetProxyFullName(ImmutableArray types) => ProxyFactory.ProxyNamespace + "." + GetProxyName(types);
+
///
/// Generates proxies by discovering proxy factory method invocations in the given
/// source documents.
@@ -189,15 +193,35 @@ public async Task GenerateProxyAsync(AdhocWorkspace workspace, Project
cancellationToken.ThrowIfCancellationRequested();
- var name = string.Join("", types.Select(x => x.Name)) + "Proxy";
-
// NOTE: we append the additional interfaces *after* determining the proxy name,
// to avoid including them there.
types = types.Concat(additionalInterfaces).ToImmutableArray();
-
- var (baseType, interfaceTypes) = ValidateTypes(types);
var generator = SyntaxGenerator.GetGenerator(project);
+ var (name, syntax) = CreateProxy(types, generator);
+
+ var services = workspace.Services.GetService();
+ var code = syntax.NormalizeWhitespace().ToFullString();
+ var filePath = Path.GetTempFileName();
+#if DEBUG
+ File.WriteAllText(filePath, code);
+#endif
+
+ var document = workspace.AddDocument(DocumentInfo.Create(
+ DocumentId.CreateNewId(project.Id),
+ name,
+ filePath: filePath,
+ loader: TextLoader.From(TextAndVersion.Create(SourceText.From(code), VersionStamp.Create()))));
+
+ document = await ApplyVisitors(document, services, cancellationToken);
+
+ return document;
+ }
+
+ public static (string name, SyntaxNode syntax) CreateProxy(ImmutableArray types, SyntaxGenerator generator)
+ {
+ var name = GetProxyName(types);
+ var (baseType, interfaceTypes) = ValidateTypes(types);
var syntax = generator.CompilationUnit(types
.Where(x => x.ContainingNamespace != null && x.ContainingNamespace.CanBeReferencedByName)
.Select(x => x.ContainingNamespace.ToDisplayString())
@@ -213,41 +237,48 @@ public async Task GenerateProxyAsync(AdhocWorkspace workspace, Project
.Select(x => generator.NamespaceImportDeclaration(x))
.Concat(new[]
{
- generator.AddAttributes(
- generator.ClassDeclaration(name,
- accessibility: Accessibility.Public,
- modifiers: DeclarationModifiers.Partial,
- baseType: baseType == null ? null : generator.IdentifierName(baseType.Name),
- interfaceTypes: interfaceTypes.Select(x => generator.IdentifierName(x.Name))),
- generator.Attribute(nameof(CompilerGeneratedAttribute)))
+ generator.NamespaceDeclaration(ProxyFactory.ProxyNamespace,
+ generator.AddAttributes(
+ generator.ClassDeclaration(name,
+ accessibility: Accessibility.Public,
+ modifiers: DeclarationModifiers.Partial,
+ baseType: baseType == null ? null : generator.IdentifierName(baseType.Name),
+ interfaceTypes: interfaceTypes.Select(x => generator.IdentifierName(x.Name))),
+ generator.Attribute("CompilerGenerated")
+ )
+ )
}));
- var services = workspace.Services.GetService();
- var code = syntax.NormalizeWhitespace().ToFullString();
- var filePath = Path.GetTempFileName();
+ return (name, syntax);
+ }
+
+ public static async Task ApplyVisitors(Document document, ICodeAnalysisServices services, CancellationToken cancellationToken)
+ {
#if DEBUG
- File.WriteAllText(filePath, code);
+ if (Debugger.IsAttached)
+ cancellationToken = CancellationToken.None;
#endif
- var document = workspace.AddDocument(DocumentInfo.Create(
- DocumentId.CreateNewId(project.Id),
- name,
- filePath: filePath,
- loader: TextLoader.From(TextAndVersion.Create(SourceText.From(code), VersionStamp.Create()))));
+ var language = document.Project.Language;
+ var prepares = services.GetLanguageServices(language, DocumentVisitorLayer.Prepare).ToArray();
+ foreach (var prepare in prepares)
+ {
+ document = await prepare.VisitAsync(document, cancellationToken);
+ }
- var scaffolds = services.GetLanguageServices(project.Language, DocumentVisitorLayer.Scaffold).ToArray();
+ var scaffolds = services.GetLanguageServices(language, DocumentVisitorLayer.Scaffold).ToArray();
foreach (var scaffold in scaffolds)
{
document = await scaffold.VisitAsync(document, cancellationToken);
}
- var rewriters = services.GetLanguageServices(project.Language, DocumentVisitorLayer.Rewrite).ToArray();
+ var rewriters = services.GetLanguageServices(language, DocumentVisitorLayer.Rewrite).ToArray();
foreach (var rewriter in rewriters)
{
document = await rewriter.VisitAsync(document, cancellationToken);
}
- var fixups = services.GetLanguageServices(project.Language, DocumentVisitorLayer.Fixup).ToArray();
+ var fixups = services.GetLanguageServices(language, DocumentVisitorLayer.Fixup).ToArray();
foreach (var fixup in fixups)
{
document = await fixup.VisitAsync(document, cancellationToken);
@@ -256,7 +287,7 @@ public async Task GenerateProxyAsync(AdhocWorkspace workspace, Project
return document;
}
- (ITypeSymbol baseType, ImmutableArray interfaceTypes) ValidateTypes(ImmutableArray types)
+ static (ITypeSymbol baseType, ImmutableArray interfaceTypes) ValidateTypes(ImmutableArray types)
{
var baseType = default(ITypeSymbol);
var interfaceTypes = default(ImmutableArray);
diff --git a/src/Proxy/Proxy.Generator/Rewrite/CSharpProxy.cs b/src/Proxy/Proxy.Generator/Rewrite/CSharpProxy.cs
index a72e3c82..c981d9cb 100644
--- a/src/Proxy/Proxy.Generator/Rewrite/CSharpProxy.cs
+++ b/src/Proxy/Proxy.Generator/Rewrite/CSharpProxy.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Threading;
@@ -45,16 +46,55 @@ public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
.ToArray());
node = (ClassDeclarationSyntax)base.VisitClassDeclaration(node);
-
- // Add the IProxy implementation last so it's not visited.
node = node.AddBaseListTypes(SimpleBaseType(IdentifierName(nameof(IProxy))));
- node = (ClassDeclarationSyntax)generator.InsertMembers(node, 0,
- generator.FieldDeclaration(
- "pipeline",
- generator.IdentifierName(nameof(BehaviorPipeline)),
- initializer: generator.ObjectCreationExpression(generator.IdentifierName(nameof(BehaviorPipeline)))));
- node = node.AddMembers(PropertyDeclaration(
+ node = node.AddMembers(
+ FieldDeclaration(
+ VariableDeclaration(
+ IdentifierName(Identifier(
+ TriviaList(
+ CarriageReturnLineFeed,
+ Trivia(RegionDirectiveTrivia(true)
+ .WithRegionKeyword(Token(
+ TriviaList(),
+ SyntaxKind.RegionKeyword,
+ TriviaList(Space)))
+ .WithEndOfDirectiveToken(Token(
+ TriviaList(PreprocessingMessage(nameof(IProxy))),
+ SyntaxKind.EndOfDirectiveToken,
+ TriviaList(CarriageReturnLineFeed))
+ )
+ )
+ ),
+ nameof(BehaviorPipeline),
+ TriviaList(Space)
+ )
+ )
+ )
+ .WithVariables(
+ SingletonSeparatedList(
+ VariableDeclarator(Identifier(
+ TriviaList(),
+ "pipeline",
+ TriviaList(Space)))
+ .WithInitializer(
+ EqualsValueClause(
+ ObjectCreationExpression(IdentifierName("BehaviorPipeline"))
+ .WithNewKeyword(Token(
+ TriviaList(),
+ SyntaxKind.NewKeyword,
+ TriviaList(Space)))
+ .WithArgumentList(ArgumentList()))
+ .WithEqualsToken(Token(
+ TriviaList(),
+ SyntaxKind.EqualsToken,
+ TriviaList(Space)))))))
+ .WithSemicolonToken(Token(
+ TriviaList(),
+ SyntaxKind.SemicolonToken,
+ TriviaList(LineFeed))
+ ),
+ PropertyDeclaration(
GenericName(Identifier("IList"), TypeArgumentList(SingletonSeparatedList(IdentifierName(nameof(IProxyBehavior))))),
Identifier(nameof(IProxy.Behaviors)))
.WithExplicitInterfaceSpecifier(ExplicitInterfaceSpecifier(IdentifierName(nameof(IProxy))))
@@ -63,7 +103,15 @@ public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName("pipeline"),
IdentifierName("Behaviors"))))
- .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)));
+ .WithSemicolonToken(Token(
+ TriviaList(),
+ SyntaxKind.SemicolonToken,
+ TriviaList(CarriageReturnLineFeed)))
+ // #endregion
+ .WithTrailingTrivia(TriviaList(
+ CarriageReturnLineFeed,
+ Trivia(EndRegionDirectiveTrivia(true)),
+ CarriageReturnLineFeed)));
return node;
}
diff --git a/src/Proxy/Proxy.Generator/Rewrite/VisualBasicProxy.cs b/src/Proxy/Proxy.Generator/Rewrite/VisualBasicProxy.cs
index bcd419a2..4043069b 100644
--- a/src/Proxy/Proxy.Generator/Rewrite/VisualBasicProxy.cs
+++ b/src/Proxy/Proxy.Generator/Rewrite/VisualBasicProxy.cs
@@ -33,6 +33,11 @@ class VisualBasicProxy : VisualBasicSyntaxRewriter, IDocumentVisitor
return document.WithSyntaxRoot(syntax);
}
+ // The namespace for the proxy should be global, just like C#
+ public override SyntaxNode VisitNamespaceStatement(NamespaceStatementSyntax node)
+ => base.VisitNamespaceStatement(node.WithName(ParseName("Global." + ProxyFactory.ProxyNamespace)))
+ .WithTrailingTrivia(CarriageReturnLineFeed);
+
public override SyntaxNode VisitClassBlock(ClassBlockSyntax node)
{
// Turn event fields into event declarations.
@@ -68,10 +73,9 @@ public override SyntaxNode VisitClassBlock(ClassBlockSyntax node)
});
}
- node = (ClassBlockSyntax)base.VisitClassBlock(node);
-
- // Add the IProxy implementation last so it's not visited.
- node = node.AddImplements(ImplementsStatement(IdentifierName(nameof(IProxy))));
+ node = (ClassBlockSyntax)generator.AddInterfaceType(
+ base.VisitClassBlock(node),
+ generator.IdentifierName(nameof(IProxy)));
var field = generator.FieldDeclaration(
"pipeline",
@@ -94,7 +98,7 @@ public override SyntaxNode VisitClassBlock(ClassBlockSyntax node)
property.PropertyStatement.WithImplementsClause(
ImplementsClause(QualifiedName(IdentifierName(nameof(IProxy)), IdentifierName(nameof(IProxy.Behaviors))))));
- return generator.InsertMembers(node, 0, field, property);
+ return generator.AddMembers(node, field, property);
}
public override SyntaxNode VisitMethodBlock(MethodBlockSyntax node)
diff --git a/src/Proxy/Proxy.Generator/Scaffold/CSharpCodeFixes.cs b/src/Proxy/Proxy.Generator/Scaffold/CSharpCodeFixes.cs
index eca80446..366edf70 100644
--- a/src/Proxy/Proxy.Generator/Scaffold/CSharpCodeFixes.cs
+++ b/src/Proxy/Proxy.Generator/Scaffold/CSharpCodeFixes.cs
@@ -15,8 +15,9 @@ class CSharpCodeFixes : CodeFixDocumentVisitor
public CSharpCodeFixes(ICodeAnalysisServices services)
: base(services,
CodeFixNames.CSharp.ImplementAbstractClass,
- CodeFixNames.CSharp.ImplementInterface)
- // NOTE: should we also add CodeFixNames.All.RemoveUnnecessaryImports?
+ CodeFixNames.CSharp.ImplementInterface,
+ CodeFixNames.All.SimplifyNames,
+ CodeFixNames.All.RemoveUnnecessaryImports)
{
}
}
diff --git a/src/Proxy/Proxy.Generator/Scaffold/VisualBasicCodeFixes.cs b/src/Proxy/Proxy.Generator/Scaffold/VisualBasicCodeFixes.cs
index 0d2f269d..736c966c 100644
--- a/src/Proxy/Proxy.Generator/Scaffold/VisualBasicCodeFixes.cs
+++ b/src/Proxy/Proxy.Generator/Scaffold/VisualBasicCodeFixes.cs
@@ -16,8 +16,9 @@ public VisualBasicCodeFixes(ICodeAnalysisServices services)
: base(services,
CodeFixNames.VisualBasic.ImplementAbstractClass,
CodeFixNames.VisualBasic.ImplementInterface,
- CodeFixNames.VisualBasic.AddOverloads)
- // NOTE: should we also add CodeFixNames.All.RemoveUnnecessaryImports?
+ CodeFixNames.VisualBasic.AddOverloads,
+ CodeFixNames.All.SimplifyNames,
+ CodeFixNames.All.RemoveUnnecessaryImports)
{
}
}
diff --git a/src/Proxy/Proxy.Generator/CodeFixDocumentVisitor.cs b/src/Proxy/Proxy.Sdk/CodeFixDocumentVisitor.cs
similarity index 52%
rename from src/Proxy/Proxy.Generator/CodeFixDocumentVisitor.cs
rename to src/Proxy/Proxy.Sdk/CodeFixDocumentVisitor.cs
index dda9f2d5..438627d9 100644
--- a/src/Proxy/Proxy.Generator/CodeFixDocumentVisitor.cs
+++ b/src/Proxy/Proxy.Sdk/CodeFixDocumentVisitor.cs
@@ -2,26 +2,25 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Host;
namespace Moq.Proxy
{
- abstract class CodeFixDocumentVisitor : IDocumentVisitor
+ public abstract class CodeFixDocumentVisitor : IDocumentVisitor
{
ICodeFixService codeFixService;
string[] codeFixNames;
protected CodeFixDocumentVisitor(ICodeAnalysisServices services, params string[] codeFixes)
{
- this.codeFixService = services.GetWorkspaceService();
- this.codeFixNames = codeFixes;
+ codeFixService = services.GetWorkspaceService();
+ codeFixNames = codeFixes;
}
public async Task VisitAsync(Document document, CancellationToken cancellationToken = default(CancellationToken))
{
- var workspace = document.Project.Solution.Workspace;
-
foreach (var codeFixName in codeFixNames)
{
// If we request and process ALL codefixes at once, we'll get one for each
@@ -32,23 +31,19 @@ protected CodeFixDocumentVisitor(ICodeAnalysisServices services, params string[]
var codeFixes = await codeFixService.GetCodeFixes(document, codeFixName, cancellationToken);
while (codeFixes.Length != 0)
{
- // We first try to apply all codefixes that don't involve our IProxy interface.
- var codeFix = codeFixes.FirstOrDefault(x
- => !x.Diagnostics.Any(d
- => d.GetMessage().Contains(nameof(IProxy))));
-
- if (codeFix == null)
+ var operations = await codeFixes[0].Action.GetOperationsAsync(cancellationToken);
+ ApplyChangesOperation operation;
+ if ((operation = operations.OfType().FirstOrDefault()) != null)
{
- // We have at least one codeFix for IProxy, pick last instance, which would be
- // the explicit implementation one.
- codeFix = codeFixes.Last();
+ document = operation.ChangedSolution.GetDocument(document.Id);
+ // Retrieve the codefixes for the updated doc again.
+ codeFixes = await codeFixService.GetCodeFixes(document, codeFixName, cancellationToken);
+ }
+ else
+ {
+ // If we got no applicable code fixes, exit the loop and move on to the next codefix.
+ break;
}
-
- await codeFix.ApplyAsync(workspace, cancellationToken);
- // Retrieve the updated document for the next pass.
- document = workspace.CurrentSolution.GetDocument(document.Id);
- // Retrieve the codefixes for the updated doc again.
- codeFixes = await codeFixService.GetCodeFixes(document, codeFixName, cancellationToken);
}
}
diff --git a/src/Proxy/Proxy.Sdk/DocumentVisitorLayer.cs b/src/Proxy/Proxy.Sdk/DocumentVisitorLayer.cs
new file mode 100644
index 00000000..43f295dd
--- /dev/null
+++ b/src/Proxy/Proxy.Sdk/DocumentVisitorLayer.cs
@@ -0,0 +1,43 @@
+namespace Moq.Proxy
+{
+ ///
+ /// The layer at which an acts.
+ ///
+ public static class DocumentVisitorLayer
+ {
+ ///
+ /// Initial proxy generation phase where the basic imports, namespace and
+ /// blank proxy class declaration and initial base type and implemented interfaces
+ /// are laid out in the doc. This is typically the phase were additional interfaces
+ /// can be injected to participate in the subsequent phases automatically.
+ ///
+ ///
+ /// Moq's IMocked interface is registered in this phase, for example.
+ ///
+ public const string Prepare = nameof(Prepare);
+
+ ///
+ /// Phase that generates the basic boilerplate for all abstract and interface members,
+ /// that typically have default implementations that basically throw .
+ /// For C# and VB, this is achieved by executing the built-in code fixes for abstract
+ /// class and interface default implementations.
+ ///
+ public const string Scaffold = nameof(Scaffold);
+
+ ///
+ /// Executed right after scaffold, this phase performs the initial proxy implementation rewriting
+ /// by replacing methods that throw generated during scaffold
+ /// and invokes the instead for each of them.
+ ///
+ public const string Rewrite = nameof(Rewrite);
+
+ ///
+ /// Final phase that allows generators to perform additional generation beyond scaffold
+ /// and inital proxy rewriting. Members generated in this phase are not rewritten at all
+ /// to use the and can consist of language-specific fixups
+ /// or cleanups to make the generated code more idiomatic than the default code fixes may
+ /// provide.
+ ///
+ public const string Fixup = nameof(Fixup);
+ }
+}
diff --git a/src/Proxy/Proxy.Generator/IDocumentVisitor.cs b/src/Proxy/Proxy.Sdk/IDocumentVisitor.cs
similarity index 100%
rename from src/Proxy/Proxy.Generator/IDocumentVisitor.cs
rename to src/Proxy/Proxy.Sdk/IDocumentVisitor.cs
diff --git a/src/Proxy/Proxy.Sdk/Moq.Proxy.Sdk.csproj b/src/Proxy/Proxy.Sdk/Moq.Proxy.Sdk.csproj
new file mode 100644
index 00000000..512cb702
--- /dev/null
+++ b/src/Proxy/Proxy.Sdk/Moq.Proxy.Sdk.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net461
+ Moq.Proxy
+ true
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Proxy/Proxy/ProxyFactory.cs b/src/Proxy/Proxy/ProxyFactory.cs
index 4bf90351..b4b3b396 100644
--- a/src/Proxy/Proxy/ProxyFactory.cs
+++ b/src/Proxy/Proxy/ProxyFactory.cs
@@ -11,6 +11,11 @@ namespace Moq.Proxy
///
public class ProxyFactory : IProxyFactory
{
+ ///
+ /// The namespace where generated proxies are declared.
+ ///
+ public const string ProxyNamespace = "Proxies";
+
///
/// Gets or sets the default to use
/// to create proxies.
@@ -24,7 +29,7 @@ private ProxyFactory() { }
///
public object CreateProxy(Assembly proxiesAssembly, Type baseType, IEnumerable implementedInterfaces, object[] construtorArguments)
{
- var name = baseType.Name + string.Join("", implementedInterfaces.Select(x => x.Name)) + "Proxy";
+ var name = ProxyNamespace + "." + baseType.Name + string.Join("", implementedInterfaces.Select(x => x.Name)) + "Proxy";
var type = proxiesAssembly.GetType(name, true, false);
return Activator.CreateInstance(type, construtorArguments);
diff --git a/src/Sdk.Build/CSharpMockGenerator.cs b/src/Sdk.Build/CSharpMockGenerator.cs
deleted file mode 100644
index 886064c6..00000000
--- a/src/Sdk.Build/CSharpMockGenerator.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Editing;
-using Microsoft.CodeAnalysis.Host.Mef;
-using Moq.Proxy;
-using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
-
-namespace Moq.Sdk
-{
- [ExportLanguageService(typeof(IDocumentVisitor), LanguageNames.CSharp, DocumentVisitorLayer.Fixup)]
- public class CSharpMockGenerator : CSharpSyntaxRewriter, IDocumentVisitor
- {
- SyntaxGenerator generator;
-
- public async Task VisitAsync(Document document, CancellationToken cancellationToken = default(CancellationToken))
- {
- generator = SyntaxGenerator.GetGenerator(document);
- var syntax = await document.GetSyntaxRootAsync(cancellationToken);
- syntax = Visit(syntax);
-
- return document.WithSyntaxRoot(syntax);
- }
-
- public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node)
- => base.VisitCompilationUnit(node.AddUsings(UsingDirective(IdentifierName(typeof(LazyInitializer).Namespace))));
-
- public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
- => generator.AddMembers(
- base.VisitClassDeclaration(node),
- generator.FieldDeclaration(
- "mock",
- ParseTypeName(nameof(IMock))
- ));
-
- public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node)
- {
- if (generator.GetName(node) == nameof(IMocked.Mock) &&
- generator.GetType(node).ToString() == nameof(IMock))
- return base.VisitPropertyDeclaration(node
- // Make IMocked properties explicit.
- .WithExplicitInterfaceSpecifier(
- ExplicitInterfaceSpecifier(
- IdentifierName(nameof(IMocked))))
- .WithModifiers(TokenList())
- // => LazyInitializer.EnsureInitialized(ref mock, () => new MockInfo(pipeline.Behaviors));
- .WithExpressionBody(ArrowExpressionClause(
- InvocationExpression(
- MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- IdentifierName(nameof(LazyInitializer)),
- IdentifierName(nameof(LazyInitializer.EnsureInitialized))),
- ArgumentList(SeparatedList(new ArgumentSyntax[]
- {
- Argument(RefExpression(IdentifierName("mock"))),
- Argument(ParenthesizedLambdaExpression(
- ObjectCreationExpression(
- IdentifierName(nameof(MockInfo)))
- .WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(
- MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- IdentifierName("pipeline"),
- IdentifierName(nameof(BehaviorPipeline.Behaviors))
- )
- ))))
- ))
- }))
- )
- ))
- );
-
- return base.VisitPropertyDeclaration(node);
- }
- }
-}
\ No newline at end of file
diff --git a/src/Sdk.Build/Moq.Sdk.Build.props b/src/Sdk.Build/Moq.Sdk.Build.props
deleted file mode 100644
index 671a7d9e..00000000
--- a/src/Sdk.Build/Moq.Sdk.Build.props
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Sdk.Generator/CSharpMocked.cs b/src/Sdk.Generator/CSharpMocked.cs
new file mode 100644
index 00000000..33d067f8
--- /dev/null
+++ b/src/Sdk.Generator/CSharpMocked.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Editing;
+using Microsoft.CodeAnalysis.Host.Mef;
+using Moq.Proxy;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+
+namespace Moq.Sdk
+{
+ [ExportLanguageService(typeof(IDocumentVisitor), LanguageNames.CSharp, DocumentVisitorLayer.Fixup)]
+ public class CSharpMocked : CSharpSyntaxRewriter, IDocumentVisitor
+ {
+ SyntaxGenerator generator;
+
+ public async Task VisitAsync(Document document, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ generator = SyntaxGenerator.GetGenerator(document);
+ var syntax = await document.GetSyntaxRootAsync(cancellationToken);
+ syntax = Visit(syntax);
+
+ return document.WithSyntaxRoot(syntax);
+ }
+
+ public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node)
+ => base.VisitCompilationUnit(node.AddUsings(
+ UsingDirective(IdentifierName(typeof(LazyInitializer).Namespace)),
+ UsingDirective(IdentifierName(typeof(IMocked).Namespace))));
+
+ public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
+ {
+ var result = generator.AddInterfaceType(
+ base.VisitClassDeclaration(node),
+ generator.IdentifierName(nameof(IMocked)));
+
+ result = generator.AddMembers(result,
+ generator.FieldDeclaration("mock", ParseTypeName(nameof(IMock)))
+ // #region IMocked
+ .WithLeadingTrivia(
+ CarriageReturnLineFeed,
+ Trivia(RegionDirectiveTrivia(true)
+ .WithRegionKeyword(Token(
+ TriviaList(),
+ SyntaxKind.RegionKeyword,
+ TriviaList(Space)))
+ .WithEndOfDirectiveToken(Token(
+ TriviaList(PreprocessingMessage(nameof(IMocked))),
+ SyntaxKind.EndOfDirectiveToken,
+ TriviaList(CarriageReturnLineFeed))
+ )
+ )
+ )
+ );
+
+ var prop = PropertyDeclaration(IdentifierName(nameof(IMock)), nameof(IMocked.Mock))
+ // Make IMocked properties explicit.
+ .WithExplicitInterfaceSpecifier(
+ ExplicitInterfaceSpecifier(
+ IdentifierName(nameof(IMocked))))
+ .WithModifiers(TokenList())
+ // => LazyInitializer.EnsureInitialized(ref mock, () => new MockInfo(pipeline.Behaviors));
+ .WithExpressionBody(ArrowExpressionClause(
+ InvocationExpression(
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(nameof(LazyInitializer)),
+ IdentifierName(nameof(LazyInitializer.EnsureInitialized))),
+ ArgumentList(SeparatedList(new ArgumentSyntax[]
+ {
+ Argument(RefExpression(IdentifierName("mock"))),
+ Argument(ParenthesizedLambdaExpression(
+ ObjectCreationExpression(
+ IdentifierName(nameof(MockInfo)))
+ .WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName("pipeline"),
+ IdentifierName(nameof(BehaviorPipeline.Behaviors))
+ )
+ ))))
+ ))
+ }))
+ )
+ ))
+ .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
+ // #endregion
+ .WithTrailingTrivia(
+ CarriageReturnLineFeed,
+ Trivia(EndRegionDirectiveTrivia(false)),
+ CarriageReturnLineFeed);
+
+ result = generator.AddMembers(result, prop);
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Sdk.Generator/Moq.Sdk.Build.props b/src/Sdk.Generator/Moq.Sdk.Build.props
new file mode 100644
index 00000000..0dfb6a31
--- /dev/null
+++ b/src/Sdk.Generator/Moq.Sdk.Build.props
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/Sdk.Build/Moq.Sdk.Build.targets b/src/Sdk.Generator/Moq.Sdk.Build.targets
similarity index 86%
rename from src/Sdk.Build/Moq.Sdk.Build.targets
rename to src/Sdk.Generator/Moq.Sdk.Build.targets
index 20fcb318..4220c43f 100644
--- a/src/Sdk.Build/Moq.Sdk.Build.targets
+++ b/src/Sdk.Generator/Moq.Sdk.Build.targets
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/src/Sdk.Build/Moq.Sdk.Build.csproj b/src/Sdk.Generator/Moq.Sdk.Generator.csproj
similarity index 86%
rename from src/Sdk.Build/Moq.Sdk.Build.csproj
rename to src/Sdk.Generator/Moq.Sdk.Generator.csproj
index d1a58de6..91b1b228 100644
--- a/src/Sdk.Build/Moq.Sdk.Build.csproj
+++ b/src/Sdk.Generator/Moq.Sdk.Generator.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Sdk.Build/VisualBasicMockGenerator.cs b/src/Sdk.Generator/VisualBasicMocked.cs
similarity index 64%
rename from src/Sdk.Build/VisualBasicMockGenerator.cs
rename to src/Sdk.Generator/VisualBasicMocked.cs
index f1463e08..8f46e7c8 100644
--- a/src/Sdk.Build/VisualBasicMockGenerator.cs
+++ b/src/Sdk.Generator/VisualBasicMocked.cs
@@ -7,11 +7,12 @@
using Microsoft.CodeAnalysis.Host.Mef;
using Moq.Proxy;
using static Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory;
+using System;
namespace Moq.Sdk
{
[ExportLanguageService(typeof(IDocumentVisitor), LanguageNames.VisualBasic, DocumentVisitorLayer.Fixup)]
- public class VisualBasicMockGenerator : VisualBasicSyntaxRewriter, IDocumentVisitor
+ public class VisualBasicMocked : VisualBasicSyntaxRewriter, IDocumentVisitor
{
SyntaxGenerator generator;
@@ -26,26 +27,24 @@ public class VisualBasicMockGenerator : VisualBasicSyntaxRewriter, IDocumentVisi
public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node)
=> base.VisitCompilationUnit(node.AddImports(
- ImportsStatement(SingletonSeparatedList(
- SimpleImportsClause(IdentifierName(typeof(LazyInitializer).Namespace))))));
+ ImportsStatement(SingletonSeparatedList(SimpleImportsClause(IdentifierName(typeof(LazyInitializer).Namespace)))),
+ ImportsStatement(SingletonSeparatedList(SimpleImportsClause(IdentifierName(typeof(IMocked).Namespace))))));
public override SyntaxNode VisitClassBlock(ClassBlockSyntax node)
- => generator.AddMembers(
+ {
+ var result = generator.AddInterfaceType(
base.VisitClassBlock(node),
- generator.FieldDeclaration(
- "_mock",
- ParseTypeName(nameof(IMock))
- ));
+ generator.IdentifierName(nameof(IMocked)));
- public override SyntaxNode VisitPropertyBlock(PropertyBlockSyntax node)
- {
- var type = (TypeSyntax)generator.GetType(node);
- var name = generator.GetName(node);
+ result = generator.AddMembers(result,
+ generator.FieldDeclaration("_mock", ParseTypeName(nameof(IMock)))
+ .WithLeadingTrivia(Whitespace(Environment.NewLine)));
- if (type.ToString() == nameof(IMock) &&
- name == nameof(IMocked.Mock))
- {
- node = (PropertyBlockSyntax)generator.WithGetAccessorStatements(node, new[]
+ var property = (PropertyBlockSyntax)generator.PropertyDeclaration(
+ nameof(IMocked.Mock),
+ ParseTypeName(nameof(IMock)),
+ modifiers: DeclarationModifiers.ReadOnly,
+ getAccessorStatements: new[]
{
generator.ReturnStatement(
generator.InvocationExpression(
@@ -75,23 +74,15 @@ public override SyntaxNode VisitPropertyBlock(PropertyBlockSyntax node)
)
)
)
- //generator.Argument(
- // RefKind.None
- // generator.ValueReturningLambdaExpression(
- // new []
- // {
- // generator.ObjectCreationExpression(
- // generator.IdentifierName(nameof(MockInfo)),
- // generator.MemberAccessExpression(
- // generator.IdentifierName("pipeline"),
- // nameof(BehaviorPipeline.Behaviors)))
- // }
- // )
- //)
});
- }
- return base.VisitPropertyBlock(node);
+ property = property.WithPropertyStatement(
+ property.PropertyStatement.WithImplementsClause(
+ ImplementsClause(QualifiedName(IdentifierName(nameof(IMocked)), IdentifierName(nameof(IMocked.Mock))))));
+
+ result = generator.AddMembers(result, property);
+
+ return result;
}
}
}
\ No newline at end of file
diff --git a/src/Sdk/InvocationBehavior.cs b/src/Sdk/InvocationBehavior.cs
index 0009e9de..04bf9d17 100644
--- a/src/Sdk/InvocationBehavior.cs
+++ b/src/Sdk/InvocationBehavior.cs
@@ -16,4 +16,4 @@ public InvocationBehavior(InvokeBehavior invoke, string name = null)
public override string ToString() => Name ?? "";
}
-}
+}
\ No newline at end of file
diff --git a/src/Version.targets b/src/Version.targets
index 2e3f6c3c..83c429e6 100644
--- a/src/Version.targets
+++ b/src/Version.targets
@@ -1,5 +1,5 @@
-
+
diff --git a/test/Moq.Tests.Basic/Moq.Tests.Basic.vbproj b/test/Moq.Tests.Basic/Moq.Tests.Basic.vbproj
index 10642dcf..f226e009 100644
--- a/test/Moq.Tests.Basic/Moq.Tests.Basic.vbproj
+++ b/test/Moq.Tests.Basic/Moq.Tests.Basic.vbproj
@@ -8,8 +8,8 @@
..\..\src\Proxy\Proxy.Generator.Build\bin\$(Configuration)\Moq.Proxy.Generator.props
..\..\src\Proxy\Proxy.Generator.Build\bin\$(Configuration)\Moq.Proxy.Generator.targets
- ..\..\src\Sdk.Build\bin\$(Configuration)\Moq.Sdk.Build.props
- ..\..\src\Sdk.Build\bin\$(Configuration)\Moq.Sdk.Build.targets
+ ..\..\src\Sdk.Generator\bin\$(Configuration)\Moq.Sdk.Build.props
+ ..\..\src\Sdk.Generator\bin\$(Configuration)\Moq.Sdk.Build.targets
..\..\src\Proxy\Proxy.Generator.Console\bin\$(Configuration)
diff --git a/test/Moq.Tests/Moq.Tests.csproj b/test/Moq.Tests/Moq.Tests.csproj
index f1bc47ed..aa4d287b 100644
--- a/test/Moq.Tests/Moq.Tests.csproj
+++ b/test/Moq.Tests/Moq.Tests.csproj
@@ -2,18 +2,9 @@
net461
9a09225f-e0bc-4890-bed4-d9f6f5dac146
- true
+ true
false
-
- ..\..\src\Proxy\Proxy.Generator.Build\bin\$(Configuration)\Moq.Proxy.Generator.props
- ..\..\src\Proxy\Proxy.Generator.Build\bin\$(Configuration)\Moq.Proxy.Generator.targets
-
- ..\..\src\Sdk.Build\bin\$(Configuration)\Moq.Sdk.Build.props
- ..\..\src\Sdk.Build\bin\$(Configuration)\Moq.Sdk.Build.targets
- ..\..\src\Proxy\Proxy.Generator.Console\bin\$(Configuration)
-
-
@@ -22,11 +13,5 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/Moq.Tests/MoqTests.cs b/test/Moq.Tests/MoqTests.cs
index c3856c32..3a6babf3 100644
--- a/test/Moq.Tests/MoqTests.cs
+++ b/test/Moq.Tests/MoqTests.cs
@@ -148,7 +148,8 @@ public void CanInvokeTwoCallbacks()
calculator.Add(Any(), Any())
.Callback(() => called1 = true)
.Callback(() => called2 = true)
- .Returns((int x, int y) => x + y);
+ .Returns((int x, int y) => x + y)
+ ;
calculator.Add(2, 2);
@@ -157,7 +158,7 @@ public void CanInvokeTwoCallbacks()
}
[Fact]
- public void CannotInvokeCallbackAfterReturn()
+ public void CanInvokeCallbackAfterReturn()
{
var calculator = Mock.Of();
var called = false;
@@ -171,7 +172,7 @@ public void CannotInvokeCallbackAfterReturn()
calculator.Add(2, 2);
- Assert.False(called);
+ Assert.True(called);
}
}
}
\ No newline at end of file
diff --git a/test/Moq.Tests/Proxies/ICalculatorProxy.cs b/test/Moq.Tests/Proxies/ICalculatorProxy.cs
new file mode 100644
index 00000000..59b6e949
--- /dev/null
+++ b/test/Moq.Tests/Proxies/ICalculatorProxy.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Moq.Proxy;
+using System.Runtime.CompilerServices;
+using System.ComponentModel;
+using System.Threading;
+using Moq.Sdk;
+
+namespace Proxies
+{
+ [CompilerGenerated]
+ public partial class ICalculatorProxy : ICalculator, IProxy, IMocked
+ {
+ public string Mode
+ {
+ get => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod()));
+ set => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value));
+ }
+
+ public int Add(int a, int b) => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), a, b));
+ public void PowerUp() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod()));
+ public bool TryAdd(ref int x, ref int y, out int z)
+ {
+ z = default (int);
+ IMethodReturn returns = pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), x, y, z));
+ x = (int)returns.Outputs["x"];
+ y = (int)returns.Outputs["y"];
+ z = (int)returns.Outputs["z"];
+ return (bool)returns.ReturnValue;
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged
+ {
+ add => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value));
+ remove => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value));
+ }
+
+ public event EventHandler Progress
+ {
+ add => pipeline.Execute>(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value));
+ remove => pipeline.Execute>(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value));
+ }
+
+ public event EventHandler PoweringUp
+ {
+ add => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value));
+ remove => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value));
+ }
+
+#region IProxy
+ BehaviorPipeline pipeline = new BehaviorPipeline();
+ IList IProxy.Behaviors => pipeline.Behaviors;
+#endregion
+#region IMocked
+ IMock mock;
+ IMock IMocked.Mock => LazyInitializer.EnsureInitialized(ref mock, () => new MockInfo(pipeline.Behaviors));
+#endregion
+ }
+}
\ No newline at end of file
diff --git a/test/Proxy.Tests/CompositionTests.cs b/test/Proxy.Tests/CompositionTests.cs
index 51c19918..ffd4661a 100644
--- a/test/Proxy.Tests/CompositionTests.cs
+++ b/test/Proxy.Tests/CompositionTests.cs
@@ -43,7 +43,7 @@ public void CanGetProxyGenerationServices()
var instances = services.GetLanguageServices(group.Key.Item1, group.Key.Item2, group.Key.Item3).ToArray();
Assert.Equal(group.Count(), instances.Length);
- output.WriteLine(group.Key.Item1 + ":" + group.Key.Item2.Substring(0, group.Key.Item2.IndexOf(",")) + ":" + group.Key.Item3 + "=" + instances.Length);
+ //output.WriteLine(group.Key.Item1 + ":" + group.Key.Item2.Substring(0, group.Key.Item2.IndexOf(",")) + ":" + group.Key.Item3 + "=" + instances.Length);
}
}
}
diff --git a/test/Proxy.Tests/ProxyGeneratorTests.cs b/test/Proxy.Tests/ProxyGeneratorTests.cs
index 74db7e36..9d10c90e 100644
--- a/test/Proxy.Tests/ProxyGeneratorTests.cs
+++ b/test/Proxy.Tests/ProxyGeneratorTests.cs
@@ -28,7 +28,7 @@ public class ProxyGeneratorTests
[InlineData(LanguageNames.CSharp)]
[InlineData(LanguageNames.VisualBasic)]
[Theory]
- public async Task GeneratedProxyDoesNotContainAdditionalInterfaceInName(string languageName)
+ public async Task GeneratedProxyNameContainsAdditionalInterfaceInName(string languageName)
{
var compilation = await CanGenerateProxy(languageName, typeof(INotifyPropertyChanged), typeof(IDisposable));
var assembly = compilation.Emit();
@@ -36,8 +36,8 @@ public async Task GeneratedProxyDoesNotContainAdditionalInterfaceInName(string l
Assert.NotNull(proxyType);
Assert.True(typeof(IDisposable).IsAssignableFrom(proxyType));
- Assert.False(proxyType.FullName.Contains(nameof(IDisposable)),
- $"Generated proxy should not contain the additional type {nameof(IDisposable)} in its name.");
+ Assert.True(proxyType.FullName.Contains(nameof(IDisposable)),
+ $"Generated proxy should contain the additional type {nameof(IDisposable)} in its name.");
}
[InlineData(LanguageNames.CSharp)]
diff --git a/test/Sdk.Tests/GeneratorTests.cs b/test/Sdk.Tests/GeneratorTests.cs
index 8307cbbb..28942f8c 100644
--- a/test/Sdk.Tests/GeneratorTests.cs
+++ b/test/Sdk.Tests/GeneratorTests.cs
@@ -35,8 +35,7 @@ public async Task CanGenerateProxies(string languageName)
References = ReferencePaths.Paths
.Concat(new[] { typeof(GeneratorTests).Assembly.ManifestModule.FullyQualifiedName })
.Select(x => new MSBuild.TaskItem(x)).ToArray(),
- AdditionalGenerators = new[] { new MSBuild.TaskItem(typeof(CSharpMockGenerator).Assembly.ManifestModule.FullyQualifiedName) },
- AdditionalInterfaces = new[] { new MSBuild.TaskItem(typeof(IMocked).FullName) },
+ AdditionalGenerators = new[] { new MSBuild.TaskItem(typeof(CSharpMocked).Assembly.ManifestModule.FullyQualifiedName) },
AdditionalProxies = new[] { new MSBuild.TaskItem(typeof(ICalculator).FullName) },
};
diff --git a/test/Sdk.Tests/IntrospectionTests.cs b/test/Sdk.Tests/IntrospectionTests.cs
new file mode 100644
index 00000000..c931105f
--- /dev/null
+++ b/test/Sdk.Tests/IntrospectionTests.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+using Proxies;
+
+namespace Tests
+{
+ public class IntrospectionTests
+ {
+ [Fact]
+ public void CanIntrospectMock()
+ {
+ var mock = new ICalculatorProxy();
+
+ var info = ((Moq.Sdk.IMocked)mock).Mock;
+
+ //info.BehaviorFor()
+
+ }
+ }
+}
diff --git a/test/Sdk.Tests/MockBehaviorTests.cs b/test/Sdk.Tests/MockBehaviorTests.cs
index 41f01d85..e25460d6 100644
--- a/test/Sdk.Tests/MockBehaviorTests.cs
+++ b/test/Sdk.Tests/MockBehaviorTests.cs
@@ -3,6 +3,7 @@
using System.Threading;
using Moq.Proxy;
using Xunit;
+using Proxies;
namespace Moq.Sdk.Tests
{
diff --git a/test/Sdk.Tests/Moq.Sdk.Tests.csproj b/test/Sdk.Tests/Moq.Sdk.Tests.csproj
index d500190b..8d63dabd 100644
--- a/test/Sdk.Tests/Moq.Sdk.Tests.csproj
+++ b/test/Sdk.Tests/Moq.Sdk.Tests.csproj
@@ -8,8 +8,8 @@
..\..\src\Proxy\Proxy.Generator.Build\bin\$(Configuration)\Moq.Proxy.Generator.props
..\..\src\Proxy\Proxy.Generator.Build\bin\$(Configuration)\Moq.Proxy.Generator.targets
- ..\..\src\Sdk.Build\bin\$(Configuration)\Moq.Sdk.Build.props
- ..\..\src\Sdk.Build\bin\$(Configuration)\Moq.Sdk.Build.targets
+ ..\..\src\Sdk.Generator\bin\$(Configuration)\Moq.Sdk.Build.props
+ ..\..\src\Sdk.Generator\bin\$(Configuration)\Moq.Sdk.Build.targets
..\..\src\Proxy\Proxy.Generator.Console\bin\$(Configuration)
@@ -21,7 +21,7 @@
-
+