diff --git a/.gitignore b/.gitignore index 1d48da87..c3f80cb5 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ obj *.log *.binlog *.rsp -/src/Usage \ No newline at end of file +/src/Usage +/src/Backup diff --git a/NuGet.Config b/NuGet.Config index 7d6789ea..a703d7f8 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -1,9 +1,6 @@  - - - - - - \ No newline at end of file + + + diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 49459a6a..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,16 +0,0 @@ -image: Visual Studio 2017 - -init: - - git config --global core.autocrlf input - -build_script: - - cmd: echo /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" >> msbuild.rsp - - cmd: msbuild /t:configure /p:configuration=release - - cmd: msbuild /t:build /p:configuration=release - -nuget: - disable_publish_on_pr: false - -artifacts: - - path: out\*.nupkg - name: Package \ No newline at end of file diff --git a/build.proj b/build.proj index bf30d960..8e897f7f 100644 --- a/build.proj +++ b/build.proj @@ -24,7 +24,6 @@ - diff --git a/src/Moq.sln b/src/Moq.sln index 3bc24e01..e624ab5b 100644 --- a/src/Moq.sln +++ b/src/Moq.sln @@ -42,7 +42,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Tests", "Moq\Moq.Tests\ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{1DBDFC27-21EC-4EAC-B51B-84EDC8DBB9D5}" ProjectSection(SolutionItems) = preProject - build\CI.props = build\CI.props + build\PackageReference.CopyLocal.targets = build\PackageReference.CopyLocal.targets build\PackageReferences.targets = build\PackageReferences.targets build\Packaging.props = build\Packaging.props build\Packaging.targets = build\Packaging.targets diff --git a/src/Moq/Moq.CodeAnalysis/MockDiagnostics.cs b/src/Moq/Moq.CodeAnalysis/MockDiagnostics.cs index a920f2e7..ba543e85 100644 --- a/src/Moq/Moq.CodeAnalysis/MockDiagnostics.cs +++ b/src/Moq/Moq.CodeAnalysis/MockDiagnostics.cs @@ -1,4 +1,5 @@ -using Microsoft.CodeAnalysis; +using System; +using Microsoft.CodeAnalysis; using Moq.Properties; namespace Moq @@ -10,7 +11,7 @@ public static class MockDiagnostics new ResourceString(nameof(Resources.MissingMockAnalyzer_Title)), new ResourceString(nameof(Resources.MissingMockAnalyzer_Message)), "Build", - DiagnosticSeverity.Error, + bool.TryParse(Environment.GetEnvironmentVariable("AutoCodeFix"), out var value) && value ? DiagnosticSeverity.Warning : DiagnosticSeverity.Info, true, new ResourceString(nameof(Resources.MissingMockAnalyzer_Description))); @@ -19,7 +20,7 @@ public static class MockDiagnostics new ResourceString(nameof(Resources.OutdatedMockAnalyzer_Title)), new ResourceString(nameof(Resources.OutdatedMockAnalyzer_Message)), "Build", - DiagnosticSeverity.Error, + bool.TryParse(Environment.GetEnvironmentVariable("AutoCodeFix"), out var value) && value ? DiagnosticSeverity.Warning : DiagnosticSeverity.Info, true, new ResourceString(nameof(Resources.OutdatedMockAnalyzer_Description))); } diff --git a/src/Moq/Moq.CodeAnalysis/MockGeneratorAnalyzer.cs b/src/Moq/Moq.CodeAnalysis/MockGeneratorAnalyzer.cs index 246a4af2..e775ef4b 100644 --- a/src/Moq/Moq.CodeAnalysis/MockGeneratorAnalyzer.cs +++ b/src/Moq/Moq.CodeAnalysis/MockGeneratorAnalyzer.cs @@ -1,6 +1,6 @@ -using Microsoft.CodeAnalysis; + +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Moq.Properties; using Stunts; namespace Moq diff --git a/src/Moq/Moq.CodeAnalysis/MockNamingConvention.cs b/src/Moq/Moq.CodeAnalysis/MockNamingConvention.cs index eb98664b..3218f73f 100644 --- a/src/Moq/Moq.CodeAnalysis/MockNamingConvention.cs +++ b/src/Moq/Moq.CodeAnalysis/MockNamingConvention.cs @@ -3,6 +3,10 @@ namespace Moq { + /// + /// Customizes the code generation naming conventions for target namespace + /// and type suffix. + /// public class MockNamingConvention : NamingConvention { public override string Namespace => MockNaming.Namespace; diff --git a/src/Moq/Moq.CodeFix/CustomMockCodeFixProvider.cs b/src/Moq/Moq.CodeFix/CustomMockCodeFixProvider.cs index 56d87491..2a1df8dd 100644 --- a/src/Moq/Moq.CodeFix/CustomMockCodeFixProvider.cs +++ b/src/Moq/Moq.CodeFix/CustomMockCodeFixProvider.cs @@ -13,10 +13,18 @@ namespace Moq { + /// + /// Custom mocks allow manually creating classes that implement IMocked and the + /// behavior pipeline from the base stunt too. It allows to manually create mocks. + /// [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, Name = "CustomMock")] [ExtensionOrder(Before = "ImplementInterface")] public class CustomMockCodeFixProvider : CodeFixProvider { + /// + /// We fixup the implementation of abstract and interface members by forwarding + /// the implementations through the behavior pipeline. + /// public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create( // See http://source.roslyn.io/#Microsoft.CodeAnalysis.CSharp.Features/ImplementAbstractClass/CSharpImplementAbstractClassCodeFixProvider.cs,15 "CS0534", diff --git a/src/Moq/Moq.CodeFix/MockGenerator.cs b/src/Moq/Moq.CodeFix/MockGenerator.cs index d30839d2..4b78d0dd 100644 --- a/src/Moq/Moq.CodeFix/MockGenerator.cs +++ b/src/Moq/Moq.CodeFix/MockGenerator.cs @@ -16,7 +16,6 @@ class MockGenerator : StuntGenerator public MockGenerator(NamingConvention naming) : base(naming, new IDocumentProcessor[] { - new EnsureSdkReference(), new DefaultImports(typeof(LazyInitializer).Namespace, typeof(IMocked).Namespace), } .Concat(GetDefaultProcessors()) diff --git a/src/Moq/Moq.CodeFix/Moq.CodeFix.csproj b/src/Moq/Moq.CodeFix/Moq.CodeFix.csproj index e06f9807..f7565100 100644 --- a/src/Moq/Moq.CodeFix/Moq.CodeFix.csproj +++ b/src/Moq/Moq.CodeFix/Moq.CodeFix.csproj @@ -31,6 +31,20 @@ + + PreserveNewest + $(PrimaryOutputKind) + true + true + %(Filename)%(Extension) + false + + + PreserveNewest + true + $(PrimaryOutputKind) + true + PreserveNewest $(PrimaryOutputKind) diff --git a/src/Moq/Moq.CodeFix/Moq.props b/src/Moq/Moq.CodeFix/Moq.props new file mode 100644 index 00000000..3a54ed03 --- /dev/null +++ b/src/Moq/Moq.CodeFix/Moq.props @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Moq/Moq.CodeFix/Moq.targets b/src/Moq/Moq.CodeFix/Moq.targets index 8c504e72..d0d20592 100644 --- a/src/Moq/Moq.CodeFix/Moq.targets +++ b/src/Moq/Moq.CodeFix/Moq.targets @@ -1,12 +1,10 @@  - - - + + + + Mocks/%(Filename)%(Extension) + false + + \ No newline at end of file diff --git a/src/Moq/Moq.CodeFix/Processors/CSharpMocked.cs b/src/Moq/Moq.CodeFix/Processors/CSharpMocked.cs index 53c1783f..dd8be342 100644 --- a/src/Moq/Moq.CodeFix/Processors/CSharpMocked.cs +++ b/src/Moq/Moq.CodeFix/Processors/CSharpMocked.cs @@ -11,6 +11,9 @@ namespace Moq.Processors { + /// + /// Generates the C# implementation of the mock interfaces. + /// class CSharpMocked : IDocumentProcessor { public string[] Languages { get; } = new[] { LanguageNames.CSharp }; diff --git a/src/Moq/Moq.CodeFix/Processors/EnsureSdkReference.cs b/src/Moq/Moq.CodeFix/Processors/EnsureSdkReference.cs deleted file mode 100644 index f8ec504a..00000000 --- a/src/Moq/Moq.CodeFix/Processors/EnsureSdkReference.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Moq.Properties; -using Moq.Sdk; -using Stunts; - -namespace Moq.Processors -{ - class EnsureSdkReference : IDocumentProcessor - { - static readonly string MoqSdkAssembly = typeof(IMock).Assembly.GetName().Name; - static readonly string MoqSdkFile = Path.GetFileName(typeof(IMock).Assembly.ManifestModule.FullyQualifiedName); - - public string[] Languages { get; } = new[] { LanguageNames.CSharp, LanguageNames.VisualBasic }; - - public ProcessorPhase Phase => ProcessorPhase.Prepare; - - public Task ProcessAsync(Document document, CancellationToken cancellationToken = default) - { - // TODO: throwing doesn't seem useful at all here. - // Maybe we should have another analyzer that reports at a different level (compilation, semantic?). - // But maybe the whole scenario is moot since the analyzer will come from Moq itself which will bring in the SDK, so... - - // Moq must either be a direct metadata/assembly reference - if (!document.Project.MetadataReferences.Any(r => r.Display.EndsWith(MoqSdkFile, StringComparison.Ordinal)) && - // or a resolved project reference to the Stunts project (if someone is using it as source/submodule - !document.Project.ProjectReferences.Select(r => document.Project.Solution.GetProject(r.ProjectId)?.AssemblyName) - .Any(n => MoqSdkAssembly.Equals(n, StringComparison.Ordinal))) - { - throw new ArgumentException(Strings.MoqRequired(document.Project.Name)); - } - - return Task.FromResult(document); - } - } -} diff --git a/src/Moq/Moq.CodeFix/Processors/VisualBasicMocked.cs b/src/Moq/Moq.CodeFix/Processors/VisualBasicMocked.cs index 566c0783..6af488a4 100644 --- a/src/Moq/Moq.CodeFix/Processors/VisualBasicMocked.cs +++ b/src/Moq/Moq.CodeFix/Processors/VisualBasicMocked.cs @@ -12,6 +12,9 @@ namespace Moq.Processors { + /// + /// Generates the VB implementation of the mock interfaces. + /// class VisualBasicMocked : IDocumentProcessor { public string[] Languages { get; } = new[] { LanguageNames.VisualBasic }; diff --git a/src/Moq/Moq.CodeFix/Properties/Resources.Designer.cs b/src/Moq/Moq.CodeFix/Properties/Resources.Designer.cs index 5c06bfaa..abdda99f 100644 --- a/src/Moq/Moq.CodeFix/Properties/Resources.Designer.cs +++ b/src/Moq/Moq.CodeFix/Properties/Resources.Designer.cs @@ -79,7 +79,7 @@ internal static string CustomMockCodeFix_Implement { } /// - /// Looks up a localized string similar to Generate {0}. + /// Looks up a localized string similar to Add {0} to project. /// internal static string GenerateMockCodeFix_TitleFormat { get { diff --git a/src/Moq/Moq.CodeFix/Properties/Resources.resx b/src/Moq/Moq.CodeFix/Properties/Resources.resx index 760b6388..1bf8b403 100644 --- a/src/Moq/Moq.CodeFix/Properties/Resources.resx +++ b/src/Moq/Moq.CodeFix/Properties/Resources.resx @@ -124,7 +124,7 @@ Implement mock - Generate {0} + Add {0} to project Invoked method requires a mock to be generated at design-time or compile-time. diff --git a/src/Moq/Moq.Package/Moq.Package.nuproj b/src/Moq/Moq.Package/Moq.Package.nuproj index 9bad76ae..7841cdea 100644 --- a/src/Moq/Moq.Package/Moq.Package.nuproj +++ b/src/Moq/Moq.Package/Moq.Package.nuproj @@ -1,4 +1,4 @@ - + @@ -21,13 +21,11 @@ - + - - + <_PackageContent Condition="'%(Kind)' == 'Dependency'"> .NETStandard,Version=v2.0 @@ -36,5 +34,4 @@ - \ No newline at end of file diff --git a/src/Moq/Moq.Sdk.Build.Tests/App.config b/src/Moq/Moq.Sdk.Build.Tests/App.config deleted file mode 100644 index fb1376a7..00000000 --- a/src/Moq/Moq.Sdk.Build.Tests/App.config +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Moq/Moq.Sdk.Build.Tests/GeneratorTests.cs b/src/Moq/Moq.Sdk.Build.Tests/GeneratorTests.cs deleted file mode 100644 index 942fa247..00000000 --- a/src/Moq/Moq.Sdk.Build.Tests/GeneratorTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Immutable; -using System.ComponentModel; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Moq.Proxy; -using Moq.Sdk; -using Xunit; -using Xunit.Abstractions; -using static TestHelpers; - -namespace Moq.Tests -{ - public class GeneratorTests - { - ITestOutputHelper output; - - public GeneratorTests(ITestOutputHelper output) => this.output = output; - - [InlineData(LanguageNames.CSharp)] - [Theory(Skip = "Manual Testing")] - public async Task GeneratesAdditionalStuff(string languageName) - { - var workspace = new AdhocWorkspace(ProxyGenerator.CreateHost(typeof(CSharpMockGenerator).Assembly)); - var projectInfo = CreateProjectInfo(languageName, "code"); - var project = workspace.AddProject(projectInfo); - var compilation = await project.GetCompilationAsync(TimeoutToken(5)); - - var typeToProxy = compilation.GetTypeByMetadataName(typeof(INotifyPropertyChanged).FullName); - var document = await new ProxyGenerator().GenerateProxyAsync(workspace, project, TimeoutToken(5), - new ITypeSymbol[] - { - compilation.GetTypeByMetadataName(typeof(INotifyPropertyChanged).FullName), - compilation.GetTypeByMetadataName(typeof(INotifyPropertyChanging).FullName), - }.ToImmutableArray(), - new ITypeSymbol[] - { - compilation.GetTypeByMetadataName(typeof(IMocked).FullName), - }.ToImmutableArray()); - - await AssertCode.NoErrorsAsync(document); - -#if DEBUG - output.WriteLine((await document.GetSyntaxRootAsync()).NormalizeWhitespace().ToString()); -#endif - - //var assembly = compilation.Emit(); - //var proxyType = assembly.GetExportedTypes().FirstOrDefault(); - - //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."); - } - - } -} diff --git a/src/Moq/Moq.Sdk.Build.Tests/Misc.cs b/src/Moq/Moq.Sdk.Build.Tests/Misc.cs deleted file mode 100644 index 41178aa2..00000000 --- a/src/Moq/Moq.Sdk.Build.Tests/Misc.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.ComponentModel; -using System; -using System.Collections.Generic; -using System.Reflection; -using Moq.Proxy; -using System.Runtime.CompilerServices; - -[CompilerGeneratedAttribute] -public partial class INotifyPropertyChangedINotifyPropertyChangingProxy : INotifyPropertyChanged, INotifyPropertyChanging, IProxy -{ - BehaviorPipeline pipeline = new BehaviorPipeline(); - public event PropertyChangedEventHandler PropertyChanged - { - add - { - pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value)); - propertyChanged = ((PropertyChangedEventHandler)(Delegate.Combine(propertyChanged, value))); - } - - remove - { - pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value)); - propertyChanged = ((PropertyChangedEventHandler)(Delegate.Remove(propertyChanged, value))); - } - } - - public event PropertyChangingEventHandler PropertyChanging - { - add - { - pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value)); - propertyChanging = ((PropertyChangingEventHandler)(Delegate.Combine(propertyChanging, value))); - } - - remove - { - pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value)); - propertyChanging = ((PropertyChangingEventHandler)(Delegate.Remove(propertyChanging, value))); - } - } - - IList IProxy.Behaviors => pipeline.Behaviors; - PropertyChangedEventHandler propertyChanged; - PropertyChangingEventHandler propertyChanging; - - delegate void ChotaHanlder(string name, int index); - - public Delegate GetEvent(string eventName) - { - switch (eventName) - { - case nameof(PropertyChanged): - return propertyChanged; - case nameof(PropertyChanging): - return propertyChanging; - default: - return null; - } - } -} \ No newline at end of file diff --git a/src/Moq/Moq.Sdk.Build.Tests/Moq.Sdk.Build.Tests.csproj b/src/Moq/Moq.Sdk.Build.Tests/Moq.Sdk.Build.Tests.csproj deleted file mode 100644 index bf492d06..00000000 --- a/src/Moq/Moq.Sdk.Build.Tests/Moq.Sdk.Build.Tests.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net461 - true - true - 42415884-4ef7-4c04-8a1d-0faf032e4767 - - - - - - - - - \ No newline at end of file diff --git a/src/Moq/Moq.Sdk.Generator/CSharpMocked.cs b/src/Moq/Moq.Sdk.Generator/CSharpMocked.cs deleted file mode 100644 index 6aca4139..00000000 --- a/src/Moq/Moq.Sdk.Generator/CSharpMocked.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Linq; -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)) - { - var hasSdk = document.Project.MetadataReferences.Any(x => x.Display.EndsWith("Moq.Sdk.dll")) || - document.Project.ProjectReferences.Select(x => document.Project.Solution.GetProject(x.ProjectId).AssemblyName).Any(x => x == "Moq.Sdk"); - - // Only apply the Moq visitor if the project actually contains a Moq.Sdk reference. - if (!hasSdk) - return document; - - generator = SyntaxGenerator.GetGenerator(document); - var syntax = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - 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( - ThisExpression() - )))) - )) - })) - ) - )) - .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/Moq/Moq.Sdk.Generator/Moq.Sdk.Build.props b/src/Moq/Moq.Sdk.Generator/Moq.Sdk.Build.props deleted file mode 100644 index 0dfb6a31..00000000 --- a/src/Moq/Moq.Sdk.Generator/Moq.Sdk.Build.props +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/Moq/Moq.Sdk.Generator/Moq.Sdk.Build.targets b/src/Moq/Moq.Sdk.Generator/Moq.Sdk.Build.targets deleted file mode 100644 index 4220c43f..00000000 --- a/src/Moq/Moq.Sdk.Generator/Moq.Sdk.Build.targets +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/Moq/Moq.Sdk.Generator/Moq.Sdk.Generator.csproj b/src/Moq/Moq.Sdk.Generator/Moq.Sdk.Generator.csproj deleted file mode 100644 index 91b1b228..00000000 --- a/src/Moq/Moq.Sdk.Generator/Moq.Sdk.Generator.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - net461 - - - - PreserveNewest - - - PreserveNewest - - - - - - - - \ No newline at end of file diff --git a/src/Moq/Moq.Sdk.Generator/Readme.txt b/src/Moq/Moq.Sdk.Generator/Readme.txt deleted file mode 100644 index 46c661d8..00000000 --- a/src/Moq/Moq.Sdk.Generator/Readme.txt +++ /dev/null @@ -1,5 +0,0 @@ -This project contains the VB and C# document visitors that inject the -IMocked interface and implementation into the generated proxies. - -This is a great example too on how to extend the proxy generation from -third-parties. \ No newline at end of file diff --git a/src/Moq/Moq.Sdk.Generator/VisualBasicMocked.cs b/src/Moq/Moq.Sdk.Generator/VisualBasicMocked.cs deleted file mode 100644 index f9dd432c..00000000 --- a/src/Moq/Moq.Sdk.Generator/VisualBasicMocked.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.VisualBasic; -using Microsoft.CodeAnalysis.VisualBasic.Syntax; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Host.Mef; -using Moq.Proxy; -using static Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory; -using System; -using System.Linq; - -namespace Moq.Sdk -{ - [ExportLanguageService(typeof(IDocumentVisitor), LanguageNames.VisualBasic, DocumentVisitorLayer.Fixup)] - public class VisualBasicMocked : VisualBasicSyntaxRewriter, IDocumentVisitor - { - SyntaxGenerator generator; - - public async Task VisitAsync(Document document, CancellationToken cancellationToken = default(CancellationToken)) - { - var hasSdk = document.Project.MetadataReferences.Any(x => x.Display.EndsWith("Moq.Sdk.dll")) || - document.Project.ProjectReferences.Select(x => document.Project.Solution.GetProject(x.ProjectId).AssemblyName).Any(x => x == "Moq.Sdk"); - - // Only apply the Moq visitor if the project actually contains a Moq.Sdk reference. - if (!hasSdk) - return document; - - generator = SyntaxGenerator.GetGenerator(document); - var syntax = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - syntax = Visit(syntax); - - return document.WithSyntaxRoot(syntax); - } - - public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node) - => base.VisitCompilationUnit(node.AddImports( - ImportsStatement(SingletonSeparatedList(SimpleImportsClause(IdentifierName(typeof(LazyInitializer).Namespace)))), - ImportsStatement(SingletonSeparatedList(SimpleImportsClause(IdentifierName(typeof(IMocked).Namespace)))))); - - public override SyntaxNode VisitClassBlock(ClassBlockSyntax node) - { - var result = generator.AddInterfaceType( - base.VisitClassBlock(node), - generator.IdentifierName(nameof(IMocked))); - - result = generator.AddMembers(result, - generator.FieldDeclaration("_mock", ParseTypeName(nameof(IMock))) - .WithLeadingTrivia(Whitespace(Environment.NewLine))); - - var property = (PropertyBlockSyntax)generator.PropertyDeclaration( - nameof(IMocked.Mock), - ParseTypeName(nameof(IMock)), - modifiers: DeclarationModifiers.ReadOnly, - getAccessorStatements: new[] - { - generator.ReturnStatement( - generator.InvocationExpression( - generator.MemberAccessExpression( - generator.IdentifierName(nameof(LazyInitializer)), - nameof(LazyInitializer.EnsureInitialized)), - generator.Argument( - RefKind.Ref, - generator.IdentifierName("_mock")), - ParenthesizedExpression( - SingleLineFunctionLambdaExpression( - FunctionLambdaHeader(List(), TokenList(), ParameterList(), null), - ObjectCreationExpression( - List(), - IdentifierName(nameof(MockInfo)), - ArgumentList(SingletonSeparatedList( - SimpleArgument(MeExpression()) - )), - null - ) - ) - ) - ) - ) - }); - - 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/Moq/Moq.Sdk/IMocked.cs b/src/Moq/Moq.Sdk/IMocked.cs index 8ed6909f..3392a811 100644 --- a/src/Moq/Moq.Sdk/IMocked.cs +++ b/src/Moq/Moq.Sdk/IMocked.cs @@ -8,7 +8,9 @@ namespace Moq.Sdk /// the interface for introspecting /// a mock instance. /// - [GeneratedCode("Moq", ThisAssembly.Project.Properties.AssemblyVersion)] // This attribute prevents registering the "Implement through behavior pipeline" codefix. + // These attributes prevent registering the "Implement through behavior pipeline" codefix. + // See CustomMockCodeFixProvider and its base class CustomStuntCodeFixProvider. + [GeneratedCode("Moq", ThisAssembly.Metadata.Version)] [CompilerGenerated] public interface IMocked { diff --git a/src/Moq/Moq.Tests/Moq.Tests.csproj b/src/Moq/Moq.Tests/Moq.Tests.csproj index 86a16b96..f2f08457 100644 --- a/src/Moq/Moq.Tests/Moq.Tests.csproj +++ b/src/Moq/Moq.Tests/Moq.Tests.csproj @@ -9,7 +9,7 @@ false - + diff --git a/src/Moq/Moq/contentFiles/cs/netstandard2.0/Mock.Overloads.cs b/src/Moq/Moq/contentFiles/cs/netstandard2.0/Mocks/Mock.Overloads.cs similarity index 100% rename from src/Moq/Moq/contentFiles/cs/netstandard2.0/Mock.Overloads.cs rename to src/Moq/Moq/contentFiles/cs/netstandard2.0/Mocks/Mock.Overloads.cs diff --git a/src/Moq/Moq/contentFiles/cs/netstandard2.0/Mock.cs b/src/Moq/Moq/contentFiles/cs/netstandard2.0/Mocks/Mock.cs similarity index 69% rename from src/Moq/Moq/contentFiles/cs/netstandard2.0/Mock.cs rename to src/Moq/Moq/contentFiles/cs/netstandard2.0/Mocks/Mock.cs index b9d1860d..3aabbfef 100644 --- a/src/Moq/Moq/contentFiles/cs/netstandard2.0/Mock.cs +++ b/src/Moq/Moq/contentFiles/cs/netstandard2.0/Mocks/Mock.cs @@ -1,19 +1,21 @@ -using System; -using System.Reflection; -using Moq.Sdk; - namespace Moq { + using System; + using System.CodeDom.Compiler; + using System.Reflection; + using Moq.Sdk; + /// /// Instantiates mocks for the specified types. /// + [GeneratedCode("Stunts", "5.0")] partial class Mock { /// /// Creates the mock instance by using the specified types to /// lookup the mock type in the assembly defining this class. /// - static T Create(MockBehavior behavior, object[] constructorArgs, params Type[] interfaces) + private static T Create(MockBehavior behavior, object[] constructorArgs, params Type[] interfaces) { var mocked = (IMocked)MockFactory.Default.CreateMock(typeof(Mock).GetTypeInfo().Assembly, typeof(T), interfaces, constructorArgs); diff --git a/src/Moq/Moq/contentFiles/vb/netstandard2.0/Mock.Overloads.vb b/src/Moq/Moq/contentFiles/vb/netstandard2.0/Mocks/Mock.Overloads.vb similarity index 100% rename from src/Moq/Moq/contentFiles/vb/netstandard2.0/Mock.Overloads.vb rename to src/Moq/Moq/contentFiles/vb/netstandard2.0/Mocks/Mock.Overloads.vb diff --git a/src/Moq/Moq/contentFiles/vb/netstandard2.0/Mock.vb b/src/Moq/Moq/contentFiles/vb/netstandard2.0/Mocks/Mock.vb similarity index 89% rename from src/Moq/Moq/contentFiles/vb/netstandard2.0/Mock.vb rename to src/Moq/Moq/contentFiles/vb/netstandard2.0/Mocks/Mock.vb index 3bf1b5bb..0c8c5261 100644 --- a/src/Moq/Moq/contentFiles/vb/netstandard2.0/Mock.vb +++ b/src/Moq/Moq/contentFiles/vb/netstandard2.0/Mocks/Mock.vb @@ -1,10 +1,12 @@ Option Strict On +Imports System.CodeDom.Compiler Imports System.Reflection Imports Moq.Sdk Namespace Global.Moq + Partial Friend Class Mock Private Shared Function Create(Of T)(ByVal behavior As MockBehavior, ByVal constructorArgs As Object(), ParamArray interfaces As Type()) As T diff --git a/src/Samples/Directory.Build.props b/src/Samples/Directory.Build.props index 994aebec..e017dd40 100644 --- a/src/Samples/Directory.Build.props +++ b/src/Samples/Directory.Build.props @@ -3,5 +3,6 @@ $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), .gitignore))\ $(RootPath)bin\$(Configuration)\ + true \ No newline at end of file diff --git a/src/Samples/Moq.CSharp/Moq.CSharp.csproj b/src/Samples/Moq.CSharp/Moq.CSharp.csproj index 6e483998..cfbfdb36 100644 --- a/src/Samples/Moq.CSharp/Moq.CSharp.csproj +++ b/src/Samples/Moq.CSharp/Moq.CSharp.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Samples/Moq.CSharp/Recursive.cs b/src/Samples/Moq.CSharp/Recursive.cs index 871a0988..af179fff 100644 --- a/src/Samples/Moq.CSharp/Recursive.cs +++ b/src/Samples/Moq.CSharp/Recursive.cs @@ -17,6 +17,9 @@ public void FieldTest() calculator.Setup(m => m.Memory.Recall()).Returns(5); var c = Mock.Of(); + c.Recall("foo").Returns(5); + + c.Setup(x => x.Store("foo", 5)); } } diff --git a/src/Samples/Moq.VisualBasic/Moq.VisualBasic.vbproj b/src/Samples/Moq.VisualBasic/Moq.VisualBasic.vbproj index a938fb84..cf893431 100644 --- a/src/Samples/Moq.VisualBasic/Moq.VisualBasic.vbproj +++ b/src/Samples/Moq.VisualBasic/Moq.VisualBasic.vbproj @@ -5,7 +5,7 @@ - + My Project\%(Filename)%(Extension) false diff --git a/src/Samples/Stunts.CSharp/Class1.cs b/src/Samples/Stunts.CSharp/Class1.cs index a1655ee4..00cb522d 100644 --- a/src/Samples/Stunts.CSharp/Class1.cs +++ b/src/Samples/Stunts.CSharp/Class1.cs @@ -2,6 +2,7 @@ using Sample; using System.Linq; using Stunts; +using System.Reflection; public class Tests { @@ -29,13 +30,43 @@ public void WhenFakingFormatterThenCanInvokeIt() Console.WriteLine(result1); Console.WriteLine(result2); - var foo = Stunt.Of(); + var bar = Stunt.Of(); + } + + public void when_using_custom_generator() + { + var ping = Randomizer.Of(); + + Console.WriteLine(ping.Ping()); + Console.WriteLine(ping.Ping()); } } +public interface IPing +{ + int Ping(); +} + +public static class Randomizer +{ + static readonly Random random = new Random(); + + [StuntGenerator] + public static T Of() + => Stunt.Of() + .AddBehavior( + (invocation, next) => invocation.CreateValueReturn(random.Next()), + invocation => invocation.MethodBase is MethodInfo info && info.ReturnType == typeof(int)); +} + + + namespace Sample { - public interface IBar { } + public interface IBar + { + void Bar(); + } public interface IFoo { diff --git a/src/Samples/Stunts.CSharp/MyClass.cs b/src/Samples/Stunts.CSharp/MyClass.cs new file mode 100644 index 00000000..c264a8f5 --- /dev/null +++ b/src/Samples/Stunts.CSharp/MyClass.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.ObjectModel; +using System.Reflection; + +using Stunts; + +public class MyBase { } + +public class MyClassTest +{ + public void Configure() + { + // var instance = new MyClass(); + + var cloneable = Stunt.Of(); + + } +} + +public class MyClass : IDisposable, IStunt +{ + private readonly BehaviorPipeline pipeline = new BehaviorPipeline(); + + ObservableCollection IStunt.Behaviors => pipeline.Behaviors; + + public void Dispose() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); +} + +//[CompilerGenerated] +//public override bool Equals(object obj) => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), obj)); +//[CompilerGenerated] +//public override int GetHashCode() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); +//[CompilerGenerated] +//public override string ToString() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); diff --git a/src/Samples/Stunts.CSharp/MyProxy.cs b/src/Samples/Stunts.CSharp/MyProxy.cs new file mode 100644 index 00000000..9e383192 --- /dev/null +++ b/src/Samples/Stunts.CSharp/MyProxy.cs @@ -0,0 +1,34 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using Sample; +using Stunts; +using System.Collections.ObjectModel; +using System.Reflection; +using System.Runtime.CompilerServices; + +public class MyProxy : IDisposable, IBar, IStunt +{ + readonly BehaviorPipeline pipeline = new BehaviorPipeline(); + + [CompilerGenerated] + ObservableCollection IStunt.Behaviors => pipeline.Behaviors; + + [CompilerGenerated] + public void Bar() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); + [CompilerGenerated] + public void Dispose() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); + [CompilerGenerated] + public override bool Equals(object obj) => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), obj)); + [CompilerGenerated] + public override int GetHashCode() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); + [CompilerGenerated] + public override string ToString() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); +} diff --git a/src/Samples/Stunts.CSharp/Stunts.CSharp.csproj b/src/Samples/Stunts.CSharp/Stunts.CSharp.csproj index 10413e5a..5b8edb57 100644 --- a/src/Samples/Stunts.CSharp/Stunts.CSharp.csproj +++ b/src/Samples/Stunts.CSharp/Stunts.CSharp.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Samples/Stunts.CSharp/Stunts/IBarStunt.cs b/src/Samples/Stunts.CSharp/Stunts/IBarStunt.cs index b7ee2828..6f977eb8 100644 --- a/src/Samples/Stunts.CSharp/Stunts/IBarStunt.cs +++ b/src/Samples/Stunts.CSharp/Stunts/IBarStunt.cs @@ -23,6 +23,8 @@ public partial class IBarStunt : IBar, IStunt [CompilerGenerated] ObservableCollection IStunt.Behaviors => pipeline.Behaviors; + [CompilerGenerated] + public void Bar() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); [CompilerGenerated] public override bool Equals(object obj) => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), obj)); [CompilerGenerated] diff --git a/src/Samples/Stunts.CSharp/Stunts/ICalculatorStunt.cs b/src/Samples/Stunts.CSharp/Stunts/ICalculatorStunt.cs new file mode 100644 index 00000000..00b0431a --- /dev/null +++ b/src/Samples/Stunts.CSharp/Stunts/ICalculatorStunt.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using Sample; +using System; +using System.Collections.ObjectModel; +using System.Reflection; +using Stunts; +using System.Runtime.CompilerServices; + +namespace Stunts +{ + public partial class ICalculatorStunt : ICalculator, IStunt + { + readonly BehaviorPipeline pipeline = new BehaviorPipeline(); + + [CompilerGenerated] + ObservableCollection IStunt.Behaviors => pipeline.Behaviors; + + [CompilerGenerated] + public int? this[string name] { get => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), name)); set => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), name, value)); } + + [CompilerGenerated] + public bool IsOn => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); + + [CompilerGenerated] + public CalculatorMode Mode { get => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); set => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value)); } + + [CompilerGenerated] + public ICalculatorMemory Memory => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); + + [CompilerGenerated] + public int Add(int x, int y) => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), x, y)); + [CompilerGenerated] + public int Add(int x, int y, int z) => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), x, y, z)); + [CompilerGenerated] + public void Clear(string name) => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), name)); + [CompilerGenerated] + public override bool Equals(object obj) => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), obj)); + [CompilerGenerated] + public override int GetHashCode() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); + [CompilerGenerated] + public int? Recall(string name) => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), name)); + [CompilerGenerated] + public void Store(string name, int value) => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), name, value)); + [CompilerGenerated] + public override string ToString() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); + + [CompilerGenerated] + public bool TryAdd(ref int x, ref int y, out int z) + { + z = default(int); + var 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; + } + + [CompilerGenerated] + public void TurnOn() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); + + [CompilerGenerated] + public event EventHandler TurnedOn { add => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value)); remove => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value)); } + } +} \ No newline at end of file diff --git a/src/Samples/Stunts.CSharp/Stunts/ICloneableStunt.cs b/src/Samples/Stunts.CSharp/Stunts/ICloneableStunt.cs new file mode 100644 index 00000000..a7300c7b --- /dev/null +++ b/src/Samples/Stunts.CSharp/Stunts/ICloneableStunt.cs @@ -0,0 +1,34 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.ObjectModel; +using System.Reflection; +using Stunts; +using System.Runtime.CompilerServices; + +namespace Stunts +{ + public partial class ICloneableStunt : ICloneable, IStunt + { + readonly BehaviorPipeline pipeline = new BehaviorPipeline(); + + [CompilerGenerated] + ObservableCollection IStunt.Behaviors => pipeline.Behaviors; + + [CompilerGenerated] + public object Clone() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); + [CompilerGenerated] + public override bool Equals(object obj) => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), obj)); + [CompilerGenerated] + public override int GetHashCode() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); + [CompilerGenerated] + public override string ToString() => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod())); + } +} \ No newline at end of file diff --git a/src/Samples/Stunts.VisualBasic/Stunts.VisualBasic.vbproj b/src/Samples/Stunts.VisualBasic/Stunts.VisualBasic.vbproj index 3b6f83a2..cefbf587 100644 --- a/src/Samples/Stunts.VisualBasic/Stunts.VisualBasic.vbproj +++ b/src/Samples/Stunts.VisualBasic/Stunts.VisualBasic.vbproj @@ -6,7 +6,7 @@ - + diff --git a/src/Stunts.sln b/src/Stunts.sln deleted file mode 100644 index 7d01f2ac..00000000 --- a/src/Stunts.sln +++ /dev/null @@ -1,69 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27110.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stunts", "Stunts\Stunts\Stunts.csproj", "{F15D4684-C048-4349-9EFA-D2C036C43E9C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stunts.Internal", "Stunts\Stunts.Internal\Stunts.Internal.csproj", "{6AD80F39-063B-4F37-9624-7955EC0E6008}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stunts.Sdk", "Stunts\Stunts.Sdk\Stunts.Sdk.csproj", "{80B6A279-ED30-4069-8510-EAF3A139EA7D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stunts.Tests", "Stunts\Stunts.Tests\Stunts.Tests.csproj", "{1320496D-6348-42B0-95F5-BA7891EF0096}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stunts.CodeAnalysis", "Stunts\Stunts.CodeAnalysis\Stunts.CodeAnalysis.csproj", "{3C2F7D4C-D10A-439C-B986-FF51664CD895}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "Samples\Sample\Sample.csproj", "{F801B6E7-70DA-46FE-937D-199BB00E7647}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F21FA4AF-78AF-49BA-8D5E-C45C95365326}" -EndProject -Project("{5DD5E4FA-CB73-4610-85AB-557B54E96AA9}") = "Stunts.Package", "Stunts\Stunts.Package\Stunts.Package.nuproj", "{91F9A526-E62C-491B-8774-88A61BDF1E1A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stunts.CodeFix", "Stunts\Stunts.CodeFix\Stunts.CodeFix.csproj", "{AE3450C8-CB6E-4DA9-A0F0-977F82FFE6F9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F15D4684-C048-4349-9EFA-D2C036C43E9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F15D4684-C048-4349-9EFA-D2C036C43E9C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F15D4684-C048-4349-9EFA-D2C036C43E9C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F15D4684-C048-4349-9EFA-D2C036C43E9C}.Release|Any CPU.Build.0 = Release|Any CPU - {6AD80F39-063B-4F37-9624-7955EC0E6008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6AD80F39-063B-4F37-9624-7955EC0E6008}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6AD80F39-063B-4F37-9624-7955EC0E6008}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6AD80F39-063B-4F37-9624-7955EC0E6008}.Release|Any CPU.Build.0 = Release|Any CPU - {80B6A279-ED30-4069-8510-EAF3A139EA7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {80B6A279-ED30-4069-8510-EAF3A139EA7D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {80B6A279-ED30-4069-8510-EAF3A139EA7D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {80B6A279-ED30-4069-8510-EAF3A139EA7D}.Release|Any CPU.Build.0 = Release|Any CPU - {1320496D-6348-42B0-95F5-BA7891EF0096}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1320496D-6348-42B0-95F5-BA7891EF0096}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1320496D-6348-42B0-95F5-BA7891EF0096}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1320496D-6348-42B0-95F5-BA7891EF0096}.Release|Any CPU.Build.0 = Release|Any CPU - {3C2F7D4C-D10A-439C-B986-FF51664CD895}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3C2F7D4C-D10A-439C-B986-FF51664CD895}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3C2F7D4C-D10A-439C-B986-FF51664CD895}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3C2F7D4C-D10A-439C-B986-FF51664CD895}.Release|Any CPU.Build.0 = Release|Any CPU - {F801B6E7-70DA-46FE-937D-199BB00E7647}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F801B6E7-70DA-46FE-937D-199BB00E7647}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F801B6E7-70DA-46FE-937D-199BB00E7647}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F801B6E7-70DA-46FE-937D-199BB00E7647}.Release|Any CPU.Build.0 = Release|Any CPU - {91F9A526-E62C-491B-8774-88A61BDF1E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {91F9A526-E62C-491B-8774-88A61BDF1E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {91F9A526-E62C-491B-8774-88A61BDF1E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {91F9A526-E62C-491B-8774-88A61BDF1E1A}.Release|Any CPU.Build.0 = Release|Any CPU - {AE3450C8-CB6E-4DA9-A0F0-977F82FFE6F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AE3450C8-CB6E-4DA9-A0F0-977F82FFE6F9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AE3450C8-CB6E-4DA9-A0F0-977F82FFE6F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AE3450C8-CB6E-4DA9-A0F0-977F82FFE6F9}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {BA56F22C-8978-49E8-B6C1-5D28B14D6C1D} - EndGlobalSection -EndGlobal diff --git a/src/Stunts/Stunts.CodeAnalysis/AnalyzerOptionsExtensions.cs b/src/Stunts/Stunts.CodeAnalysis/AnalyzerOptionsExtensions.cs new file mode 100644 index 00000000..60d3638f --- /dev/null +++ b/src/Stunts/Stunts.CodeAnalysis/AnalyzerOptionsExtensions.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Stunts +{ + /// + /// Allows retrieving code fix settings from the configured analyzer options. + /// + public static class AnalyzerOptionsExtensions + { + static readonly Dictionary emptySettings = new Dictionary(); + + /// + /// Gets the optional generator settings from the configured analyzer options. + /// + public static IDictionary GetCodeFixSettings(this AnalyzerOptions options) + { + var metadataFile = options.AdditionalFiles.FirstOrDefault(x => x.Path.EndsWith("AutoCodeFix.ini", StringComparison.OrdinalIgnoreCase)); + if (metadataFile != null) + { + // We can pass ourselves arbitrary settings by adding items. + // If items are calculated, you can create a target and run BeforeTargets="SaveAutoFixSettings". + return File.ReadAllLines(metadataFile.Path) + .Where(line => !string.IsNullOrEmpty(line)) + .Select(line => line.Split('=')) + .Where(pair => pair.Length == 2) + .ToDictionary(pair => pair[0].Trim(), pair => pair[1].Trim()); + } + + return emptySettings; + } + } +} diff --git a/src/Stunts/Stunts.CodeAnalysis/Properties/Resources.Designer.cs b/src/Stunts/Stunts.CodeAnalysis/Properties/Resources.Designer.cs index bfedd370..7f006e48 100644 --- a/src/Stunts/Stunts.CodeAnalysis/Properties/Resources.Designer.cs +++ b/src/Stunts/Stunts.CodeAnalysis/Properties/Resources.Designer.cs @@ -142,7 +142,7 @@ internal static string GenerateStuntCodeFix_FixAllTitle { } /// - /// Looks up a localized string similar to Generate Stunt. + /// Looks up a localized string similar to Add Stunt. /// internal static string GenerateStuntCodeFix_Title { get { @@ -151,7 +151,7 @@ internal static string GenerateStuntCodeFix_Title { } /// - /// Looks up a localized string similar to Generate {0}. + /// Looks up a localized string similar to Add {0} to {1}. /// internal static string GenerateStuntCodeFix_TitleFormat { get { diff --git a/src/Stunts/Stunts.CodeAnalysis/Properties/Resources.resx b/src/Stunts/Stunts.CodeAnalysis/Properties/Resources.resx index 9aae03a4..fb34bb6e 100644 --- a/src/Stunts/Stunts.CodeAnalysis/Properties/Resources.resx +++ b/src/Stunts/Stunts.CodeAnalysis/Properties/Resources.resx @@ -145,10 +145,10 @@ Generate all stunts in {0} {1} - Generate Stunt + Add Stunt - Generate {0} + Add {0} to {1} Only classes and interfaces can be used in stunts. Invalid set of symbols: {0}. diff --git a/src/Stunts/Stunts.CodeAnalysis/StuntDiagnostics.cs b/src/Stunts/Stunts.CodeAnalysis/StuntDiagnostics.cs index 404fc60e..9c79005b 100644 --- a/src/Stunts/Stunts.CodeAnalysis/StuntDiagnostics.cs +++ b/src/Stunts/Stunts.CodeAnalysis/StuntDiagnostics.cs @@ -1,4 +1,5 @@ -using Microsoft.CodeAnalysis; +using System; +using Microsoft.CodeAnalysis; using Stunts.Properties; namespace Stunts @@ -10,7 +11,7 @@ public static class StuntDiagnostics new ResourceString(nameof(Resources.MissingStunt_Title)), new ResourceString(nameof(Resources.MissingStunt_Message)), "Build", - DiagnosticSeverity.Error, + bool.TryParse(Environment.GetEnvironmentVariable("AutoCodeFix"), out var value) && value ? DiagnosticSeverity.Warning : DiagnosticSeverity.Info, true, new ResourceString(nameof(Resources.MissingStunt_Description))); @@ -19,7 +20,7 @@ public static class StuntDiagnostics new ResourceString(nameof(Resources.OutdatedStunt_Title)), new ResourceString(nameof(Resources.OutdatedStunt_Message)), "Build", - DiagnosticSeverity.Error, + bool.TryParse(Environment.GetEnvironmentVariable("AutoCodeFix"), out var value) && value ? DiagnosticSeverity.Warning : DiagnosticSeverity.Info, true, new ResourceString(nameof(Resources.OutdatedStunt_Description))); diff --git a/src/Stunts/Stunts.CodeAnalysis/StuntGeneratorAnalyzer.cs b/src/Stunts/Stunts.CodeAnalysis/StuntGeneratorAnalyzer.cs index ca88f156..34b109fb 100644 --- a/src/Stunts/Stunts.CodeAnalysis/StuntGeneratorAnalyzer.cs +++ b/src/Stunts/Stunts.CodeAnalysis/StuntGeneratorAnalyzer.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Stunts.Properties; namespace Stunts { @@ -18,9 +18,9 @@ namespace Stunts [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class StuntGeneratorAnalyzer : DiagnosticAnalyzer { - readonly NamingConvention naming; - readonly bool recursive; - Type generatorAttribute; + private readonly NamingConvention naming; + private readonly bool recursive; + private Type generatorAttribute; /// /// Instantiates the analyzer with the default and @@ -68,92 +68,123 @@ public override void Initialize(AnalysisContext context) context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.SimpleMemberAccessExpression); } - void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) + private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) { // Get the matching symbol for the given generator attribute from the current compilation. var generator = context.Compilation.GetTypeByMetadataName(generatorAttribute.FullName); if (generator == null) + { // This may be an extender authoring error, but another analyzer should ensure proper // metadata references exist. Typically, the same NuGet package that adds this analyzer // also adds the required assembly references, so this should never happen anyway. return; + } var symbol = context.Compilation.GetSemanticModel(context.Node.SyntaxTree).GetSymbolInfo(context.Node); - if (symbol.Symbol?.Kind == SymbolKind.Method) + if (symbol.Symbol?.Kind != SymbolKind.Method) { - var method = (IMethodSymbol)symbol.Symbol; - if (method.GetAttributes().Any(x => x.AttributeClass == generator) && - // We don't generate anything if generator is applied to a non-generic method. - !method.TypeArguments.IsDefaultOrEmpty && - method.TypeArguments.TryValidateGeneratorTypes(out _)) + return; + } + + var method = (IMethodSymbol)symbol.Symbol; + if (method.GetAttributes().Any(x => x.AttributeClass == generator) && + // We don't generate anything if generator is applied to a non-generic method. + !method.TypeArguments.IsDefaultOrEmpty && + method.TypeArguments.TryValidateGeneratorTypes(out _)) + { + var name = naming.GetFullName(method.TypeArguments.OfType()); + var compilationErrors = new Lazy(() => context.Compilation.GetCompilationErrors()); + HashSet recursiveSymbols; + + if (recursive) { - var name = naming.GetFullName(method.TypeArguments.OfType()); - var compilationErrors = new Lazy(() => context.Compilation.GetCompilationErrors()); - HashSet recursiveSymbols; + // Collect recursive symbols to generate/update as needed. + recursiveSymbols = new HashSet(method.TypeArguments.OfType().InterceptableRecursively() + .Where(x => + { + var candidate = context.Compilation.GetTypeByMetadataName(naming.GetFullName(new[] { x })); + return candidate == null || candidate.HasDiagnostic(compilationErrors.Value); + }) + .Select(x => x.ToFullMetadataName())); + } + else + { + recursiveSymbols = new HashSet(); + } - if (recursive) - { - // Collect recursive symbols to generate/update as needed. - recursiveSymbols = new HashSet(method.TypeArguments.OfType().InterceptableRecursively() - .Where(x => - { - var candidate = context.Compilation.GetTypeByMetadataName(naming.GetFullName(new[] { x })); - return candidate == null || candidate.HasDiagnostic(compilationErrors.Value); - }) - .Select(x => x.ToFullMetadataName())); - } - else + // See if the stunt already exists + var stunt = context.Compilation.GetTypeByMetadataName(name); + if (stunt == null) + { + var diagnostic = Diagnostic.Create( + MissingDiagnostic, + context.Node.GetLocation(), + new Dictionary + { + { "TargetFullName", name }, + { "Symbols", string.Join("|", method.TypeArguments + .OfType().Select(x => x.ToFullMetadataName())) }, + // By passing the detected recursive symbols to update/generate, + // we avoid doing all the work we already did during analysis. + // The code action can therefore simply act on them, without + // further inquiries to the compilation. + { "RecursiveSymbols", string.Join("|", recursiveSymbols) }, + }.ToImmutableDictionary(), + name); + + // Flag AutoCodeFix that the next build should generate the type. + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AutoCodeFix")) && + // We only flag if we're not being run by AutoCodeFix itself + context.Options.GetCodeFixSettings().TryGetValue("IntermediateOutputPath", out var intermediateOutputPath)) { - recursiveSymbols = new HashSet(); + File.WriteAllText(Path.Combine(intermediateOutputPath, "AutoCodeFixBeforeCompile.flag"), ""); } - // See if the stunt already exists - var stunt = context.Compilation.GetTypeByMetadataName(name); - if (stunt == null) + context.ReportDiagnostic(diagnostic); + } + else + { + // See if the symbol has any compilation error diagnostics associated + if (stunt.HasDiagnostic(compilationErrors.Value) || + (recursive && recursiveSymbols.Any())) { + var location = stunt.Locations.Length == 1 + ? stunt.Locations[0].GetLineSpan().Path + : stunt.Locations.FirstOrDefault(loc => + loc.GetLineSpan().Path.EndsWith(".g.cs", StringComparison.OrdinalIgnoreCase) || + loc.GetLineSpan().Path.EndsWith(".g.vb", StringComparison.OrdinalIgnoreCase)) + ?.GetLineSpan().Path + ?? ""; + + // Any outdated files should be automatically updated on the next build, + // so signal this to the build targets with a text file that the targets + // can use to determine a pre-compile analysis and codefix phase is needed. + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AutoCodeFix")) && + // We only flag if we're not being run by AutoCodeFix itself + context.Options.GetCodeFixSettings().TryGetValue("IntermediateOutputPath", out var intermediateOutputPath)) + { + File.WriteAllText(Path.Combine(intermediateOutputPath, "AutoCodeFixBeforeCompile.flag"), ""); + } + + // If there are compilation errors, we should update the proxy. var diagnostic = Diagnostic.Create( - MissingDiagnostic, + OutdatedDiagnostic, context.Node.GetLocation(), new Dictionary { { "TargetFullName", name }, + { "Location", location }, { "Symbols", string.Join("|", method.TypeArguments .OfType().Select(x => x.ToFullMetadataName())) }, - // By passing the detected recursive symbols to update/generate, - // we avoid doing all the work we already did during analysis. - // The code action can therefore simply act on them, without - // further inquiries to the compilation. + // We pass the same recursive symbols in either case. The + // Different diagnostics exist only to customize the message + // displayed to the user. { "RecursiveSymbols", string.Join("|", recursiveSymbols) }, }.ToImmutableDictionary(), name); context.ReportDiagnostic(diagnostic); } - else - { - // See if the symbol has any compilation error diagnostics associated - if (stunt.HasDiagnostic(compilationErrors.Value) || - (recursive && recursiveSymbols.Any())) - { - // If there are compilation errors, we should update the proxy. - var diagnostic = Diagnostic.Create( - OutdatedDiagnostic, - context.Node.GetLocation(), - new Dictionary - { - { "TargetFullName", name }, - { "Symbols", string.Join("|", method.TypeArguments - .OfType().Select(x => x.ToFullMetadataName())) }, - // We pass the same recursive symbols in either case. The - // Different diagnostics exist only to customize the message - // displayed to the user. - { "RecursiveSymbols", string.Join("|", recursiveSymbols) }, - }.ToImmutableDictionary(), - name); - - context.ReportDiagnostic(diagnostic); - } - } } } } diff --git a/src/Stunts/Stunts.CodeAnalysis/Stunts.CodeAnalysis.csproj b/src/Stunts/Stunts.CodeAnalysis/Stunts.CodeAnalysis.csproj index be873bdf..85e348a5 100644 --- a/src/Stunts/Stunts.CodeAnalysis/Stunts.CodeAnalysis.csproj +++ b/src/Stunts/Stunts.CodeAnalysis/Stunts.CodeAnalysis.csproj @@ -38,7 +38,7 @@ - analyzers\netstandard1.3\%(Filename)%(Extension) + $(PrimaryOutputKind.ToLowerInvariant())\netstandard1.3\%(Filename)%(Extension) diff --git a/src/Stunts/Stunts.CodeAnalysis/Stunts.targets b/src/Stunts/Stunts.CodeAnalysis/Stunts.targets deleted file mode 100644 index 8c504e72..00000000 --- a/src/Stunts/Stunts.CodeAnalysis/Stunts.targets +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/Stunts/Stunts.CodeAnalysis/UnsupportedNestedTypeAnalyzer.cs b/src/Stunts/Stunts.CodeAnalysis/UnsupportedNestedTypeAnalyzer.cs index f47c11e5..0e484eee 100644 --- a/src/Stunts/Stunts.CodeAnalysis/UnsupportedNestedTypeAnalyzer.cs +++ b/src/Stunts/Stunts.CodeAnalysis/UnsupportedNestedTypeAnalyzer.cs @@ -23,8 +23,7 @@ namespace Stunts // however, that the same reason the codefix isn't showing up when we ask for it, will cause that service to return // an empty list of code actions too. // It may be costly to investigate, and it doesn't seem like a core scenario anyway. - // Another workaround might be to let the user manually implement stunts/mocks and detect the presence of - // the IStunt interface and offer the rewriting codefix. This might be interesting too in general. + // It can be worked around by creating a custom mock. // TODO: F# [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class UnsupportedNestedTypeAnalyzer : DiagnosticAnalyzer diff --git a/src/Stunts/Stunts.CodeAnalysis/ValidateTypesAnalyzer.cs b/src/Stunts/Stunts.CodeAnalysis/ValidateTypesAnalyzer.cs index 3d60026e..ef3975ad 100644 --- a/src/Stunts/Stunts.CodeAnalysis/ValidateTypesAnalyzer.cs +++ b/src/Stunts/Stunts.CodeAnalysis/ValidateTypesAnalyzer.cs @@ -8,9 +8,7 @@ namespace Stunts { /// /// Analyzes source code looking for method invocations to methods annotated with - /// the and reports any missing or outdated - /// generated types using the given to locate the - /// generated types. + /// the and reports unsupported scenarios. /// // TODO: F# [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] diff --git a/src/Stunts/Stunts.CodeFix/CustomStuntCodeFixProvider.cs b/src/Stunts/Stunts.CodeFix/CustomStuntCodeFixProvider.cs index 63439c99..cd05ef44 100644 --- a/src/Stunts/Stunts.CodeFix/CustomStuntCodeFixProvider.cs +++ b/src/Stunts/Stunts.CodeFix/CustomStuntCodeFixProvider.cs @@ -58,7 +58,7 @@ bool isStunt(SyntaxNode n) bool isGenerated(SyntaxNode n) { var symbol = model.GetSymbolInfo(n, context.CancellationToken); - // [CompilerGenerated] is used in IStunt and IMocked to signal that + // [CompilerGenerated] and [GeneratedCode] are used in IStunt and IMocked to signal that // no implementation should be offered thought the behavior pipeline // because a custom code fix provides the implementation instead. return diff --git a/src/Stunts/Stunts.CodeFix/Properties/Resources.Designer.cs b/src/Stunts/Stunts.CodeFix/Properties/Resources.Designer.cs index 783bf085..ce5f9198 100644 --- a/src/Stunts/Stunts.CodeFix/Properties/Resources.Designer.cs +++ b/src/Stunts/Stunts.CodeFix/Properties/Resources.Designer.cs @@ -151,7 +151,7 @@ internal static string GenerateStuntCodeFix_Title { } /// - /// Looks up a localized string similar to Generate {0}. + /// Looks up a localized string similar to Add {0} to project. /// internal static string GenerateStuntCodeFix_TitleFormat { get { diff --git a/src/Stunts/Stunts.CodeFix/Properties/Resources.resx b/src/Stunts/Stunts.CodeFix/Properties/Resources.resx index 8e1a6e10..9b85d860 100644 --- a/src/Stunts/Stunts.CodeFix/Properties/Resources.resx +++ b/src/Stunts/Stunts.CodeFix/Properties/Resources.resx @@ -148,7 +148,7 @@ Generate Stunt - Generate {0} + Add {0} to project Only classes and interfaces can be used in stunts. Invalid set of symbols: {0}. diff --git a/src/Stunts/Stunts.CodeFix/StuntCodeAction.cs b/src/Stunts/Stunts.CodeFix/StuntCodeAction.cs index 3c498706..87dbe46b 100644 --- a/src/Stunts/Stunts.CodeFix/StuntCodeAction.cs +++ b/src/Stunts/Stunts.CodeFix/StuntCodeAction.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; @@ -61,10 +62,44 @@ async Task CreateStunt(IEnumerable symbols, SyntaxGe // TODO: F# var extension = document.Project.Language == LanguageNames.CSharp ? ".cs" : ".vb"; - var file = Path.Combine(Path.GetDirectoryName(document.Project.FilePath), naming.Namespace, name + extension); - var folders = naming.Namespace.Split('.'); + string[] folders; + if (!diagnostic.Properties.TryGetValue("Location", out var file) || + string.IsNullOrEmpty(file)) + { + file = Path.Combine(Path.GetDirectoryName(document.Project.FilePath), naming.Namespace, name + extension); + folders = naming.Namespace.Split('.'); + } + else + { + folders = Path.GetDirectoryName(file) + .Replace(Path.GetDirectoryName(document.Project.FilePath), "") + .Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + .Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + } var stuntDoc = document.Project.Documents.FirstOrDefault(d => d.Name == Path.GetFileName(file) && d.Folders.SequenceEqual(folders)); + + // NOTE: the environment variable tells us we're being run with AutoCodeFix enabled and + // active, meaning we should generate files into the intermediate output path instead. + var autoCodeFixEnabled = bool.TryParse(Environment.GetEnvironmentVariable("AutoCodeFix"), out var value) && value; + + // Also probe intermediate output path for build-time codegen. + if (stuntDoc == null && autoCodeFixEnabled) + { + // Get configured generator options + if (document.Project.AnalyzerOptions.GetCodeFixSettings().TryGetValue("IntermediateOutputPath", out var intermediateOutputPath)) + { + folders = intermediateOutputPath.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries) + .Concat(folders) + .ToArray(); + + file = Path.Combine(Path.GetDirectoryName(document.Project.FilePath), intermediateOutputPath, naming.Namespace, name + ".g" + extension); + + // Search for the doc at the new location. + stuntDoc = document.Project.Documents.FirstOrDefault(d => d.Name == Path.GetFileName(file) && d.Folders.SequenceEqual(folders)); + } + } + if (stuntDoc == null) { if (document.Project.Solution.Workspace is AdhocWorkspace workspace) @@ -73,7 +108,7 @@ async Task CreateStunt(IEnumerable symbols, SyntaxGe .Create( DocumentId.CreateNewId(document.Project.Id), Path.GetFileName(file), - folders: naming.Namespace.Split('.'), + folders: folders, filePath: file)) .WithSyntaxRoot(syntax); } @@ -92,15 +127,16 @@ async Task CreateStunt(IEnumerable symbols, SyntaxGe } stuntDoc = await stunts.ApplyProcessors(stuntDoc, cancellationToken).ConfigureAwait(false); - // This is somewhat expensive, but since we're adding it to the user' solution, we might - // as well make it look great ;) + + // This is somewhat expensive, but with the formatting, the code doesn't even compile :/ stuntDoc = await Simplifier.ReduceAsync(stuntDoc).ConfigureAwait(false); if (document.Project.Language != LanguageNames.VisualBasic) stuntDoc = await Formatter.FormatAsync(stuntDoc, Formatter.Annotation).ConfigureAwait(false); syntax = await stuntDoc.GetSyntaxRootAsync().ConfigureAwait(false); + stuntDoc = stuntDoc.WithSyntaxRoot(syntax); - return stuntDoc.WithSyntaxRoot(syntax); + return stuntDoc; } } } diff --git a/src/Stunts/Stunts.CodeFix/StuntCodeFixProvider.cs b/src/Stunts/Stunts.CodeFix/StuntCodeFixProvider.cs index fc9a57ed..cdcc2cdd 100644 --- a/src/Stunts/Stunts.CodeFix/StuntCodeFixProvider.cs +++ b/src/Stunts/Stunts.CodeFix/StuntCodeFixProvider.cs @@ -43,6 +43,7 @@ public abstract class StuntCodeFixProvider : CodeFixProvider public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + // TODO: should we instead try to register for all diagnostics matching our fixable ones instead of just the first one? var diagnostic = context.Diagnostics.FirstOrDefault(d => FixableDiagnosticIds.Contains(d.Id)); if (diagnostic == null) return; @@ -59,6 +60,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) protected abstract CodeAction CreateCodeAction(Document document, Diagnostic diagnostic); // 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() => WellKnownFixAllProviders.BatchFixer; + public override FixAllProvider GetFixAllProvider() => null; } } \ No newline at end of file diff --git a/src/Stunts/Stunts.CodeFix/Stunts.CodeFix.csproj b/src/Stunts/Stunts.CodeFix/Stunts.CodeFix.csproj index c61d9eba..ffd4ebdc 100644 --- a/src/Stunts/Stunts.CodeFix/Stunts.CodeFix.csproj +++ b/src/Stunts/Stunts.CodeFix/Stunts.CodeFix.csproj @@ -5,19 +5,18 @@ Stunts true Build + true - PrimaryOutputKind=$(PrimaryOutputKind) + PrimaryOutputKind=$(PrimaryOutputKind);IncludeApi=false - - IncludeApi=false - + @@ -38,16 +37,15 @@ - + PreserveNewest - true $(PrimaryOutputKind) - %(Filename)%(Extension) + true PreserveNewest - true $(PrimaryOutputKind) + true diff --git a/src/Stunts/Stunts.CodeFix/Stunts.props b/src/Stunts/Stunts.CodeFix/Stunts.props new file mode 100644 index 00000000..01bed369 --- /dev/null +++ b/src/Stunts/Stunts.CodeFix/Stunts.props @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Stunts/Stunts.CodeFix/Stunts.targets b/src/Stunts/Stunts.CodeFix/Stunts.targets index 8c504e72..8872ffef 100644 --- a/src/Stunts/Stunts.CodeFix/Stunts.targets +++ b/src/Stunts/Stunts.CodeFix/Stunts.targets @@ -1,12 +1,22 @@  + + + + Stunts/%(Filename)%(Extension) + false + + + - - - + analyers to run as part of the design-time build only. --> + + + + + + \ No newline at end of file diff --git a/src/Stunts/Stunts.Generator.Build/GenerateProxies.cs b/src/Stunts/Stunts.Generator.Build/GenerateProxies.cs deleted file mode 100644 index 38c1e1ec..00000000 --- a/src/Stunts/Stunts.Generator.Build/GenerateProxies.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Moq.Proxy -{ - /// - /// Task that generates the proxies. - /// - public class GenerateProxies : ToolTask - { - List proxies = new List(); - - /// - /// The extension of the generated proxy files. - /// - public string FileExtension { get; set; } - - /// - /// Name of the target language, which must be one known to Rosly. - /// . - /// - [Required] - public string LanguageName { get; set; } - - /// - /// The target directory where generated proxies should be written to. - /// - [Required] - public string OutputPath { get; set; } - - /// - /// Optional working directory to run the tool from. - /// - public string WorkingDirectory { get; set; } - - /// - /// Assembly references for the code generation. - /// - public ITaskItem[] References { get; set; } - - /// - /// Source files to inspect to discover types that need proxies, denoted - /// by invocations to methods that are annotated with Moq.Proxy.ProxyGeneratorAttribute. - /// - public ITaskItem[] Sources { get; set; } - - /// - /// Additional interfaces (by full type name) that should be implemented by generated - /// proxies. - /// - public ITaskItem[] AdditionalInterfaces { get; set; } - - /// - /// Additional types (by full type name) that should be proxied. - /// - public ITaskItem[] AdditionalProxies { get; set; } - - /// - /// Additional generator assemblies that participate in the code generation composition. - /// - public ITaskItem[] AdditionalGenerators { get; set; } - - /// - /// Whether to cause the console program to launch a debugger on run - /// for troubleshooting purposes. - /// - public bool Debug { get; set; } - - [Output] - public ITaskItem[] Proxies => proxies.ToArray(); - - protected override bool HandleTaskExecutionErrors() - { - var responseFile = Path.Combine(OutputPath, "pgen.rsp"); - - // When we fail, we create a pgen.rsp file for easy repro'ing of the - // failure if necessary. - Log.LogWarning($@"Proxy generation failed. To re-run and debug the generation, please execute the following command: -""{Path.GetFullPath(GenerateFullPathToTool())}"" @{responseFile} -debug"); - - Directory.CreateDirectory(OutputPath); - File.WriteAllText(responseFile, GenerateCommandLineCommands() + Environment.NewLine + GenerateResponseFileCommands()); - - return base.HandleTaskExecutionErrors(); - } - - protected override string ToolName => "pgen.exe"; - - // NOTE: this allows the Mono or .NETCore targets to override 'pgen.exe' by setting ToolExe differently - // (i.e. 'mono pgen.exe' or 'dotnet pgen.dll'). - protected override string GenerateFullPathToTool() => Path.Combine(ToolPath, ToolExe ?? ToolName); - - protected override string GetWorkingDirectory() => WorkingDirectory ?? ToolPath; - - protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance) - { - // The pgen tool writes to standard output the proxy files it writes out. - if (File.Exists(singleLine)) - proxies.Add(new TaskItem(singleLine)); - - base.Log.LogMessageFromText(singleLine, messageImportance); - } - - protected override void LogToolCommand(string message) - { - Log.LogCommandLine(MessageImportance.Low, message); - } - - protected override string GenerateCommandLineCommands() - { - var builder = new CommandLineBuilder(); - - // Command line arguments that are short and single, we pass them directly - // as a command line. - if (!string.IsNullOrEmpty(FileExtension)) - { - builder.AppendSwitch("-extension"); - builder.AppendSwitch(FileExtension); - } - - builder.AppendSwitch("-language"); - builder.AppendFileNameIfNotNull(LanguageName); - - builder.AppendSwitch("-output"); - builder.AppendFileNameIfNotNull(OutputPath); - - if (Debug) - builder.AppendSwitch("-debug"); - - return builder.ToString(); - } - - protected override string GenerateResponseFileCommands() - { - // Arguments that can potentially be many and go over the - // command line args length are passed via a response file, - // which the pgen tool reads from the file automatically via - // Mono.Options - var builder = new StringBuilder(); - - if (AdditionalInterfaces != null) - { - foreach (var additionalInterface in AdditionalInterfaces) - { - builder.AppendLine("-interface") - .AppendLine(additionalInterface.ItemSpec); - } - } - - if (AdditionalProxies != null) - { - foreach (var additionalProxy in AdditionalProxies) - { - builder.AppendLine("-proxy") - .AppendLine(additionalProxy.ItemSpec); - } - } - - if (AdditionalGenerators != null) - { - foreach (var additionalGenerator in AdditionalGenerators) - { - builder.AppendLine("-generator") - .AppendLine(additionalGenerator.ItemSpec); - } - } - - if (References != null) - { - foreach (var reference in References) - { - builder.AppendLine("-reference") - .AppendLine("\"" + reference.GetMetadata("FullPath") + "\""); - } - } - - if (Sources != null) - { - foreach (var source in Sources) - { - builder.AppendLine("-source") - .AppendLine("\"" + source.GetMetadata("FullPath") + "\""); - } - } - - return builder.ToString(); - } - } -} \ No newline at end of file diff --git a/src/Stunts/Stunts.Generator.Build/Properties/launchSettings.json b/src/Stunts/Stunts.Generator.Build/Properties/launchSettings.json deleted file mode 100644 index 04cd8afd..00000000 --- a/src/Stunts/Stunts.Generator.Build/Properties/launchSettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "profiles": { - "Moq.Proxy.Generator.Build": { - "commandName": "Executable", - "executablePath": "$(VsInstallRoot)\\..\\Enterprise\\MSBuild\\15.0\\Bin\\MSBuild.exe", - "commandLineArgs": "..\\..\\..\\test\\Moq.Tests\\Moq.Tests.csproj /t:GenerateProxies" - } - } -} \ No newline at end of file diff --git a/src/Stunts/Stunts.Generator.Build/Stunts.Generator.Build.csproj b/src/Stunts/Stunts.Generator.Build/Stunts.Generator.Build.csproj deleted file mode 100644 index 15fd02ba..00000000 --- a/src/Stunts/Stunts.Generator.Build/Stunts.Generator.Build.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - net461 - - - - - PreserveNewest - - - PreserveNewest - - - - - - - - - \ No newline at end of file diff --git a/src/Stunts/Stunts.Generator.Build/Stunts.Generator.props b/src/Stunts/Stunts.Generator.Build/Stunts.Generator.props deleted file mode 100644 index b43e53b0..00000000 --- a/src/Stunts/Stunts.Generator.Build/Stunts.Generator.props +++ /dev/null @@ -1,9 +0,0 @@ - - - - - $(MSBuildThisFileDirectory) - - $(MSBuildThisFileDirectory)..\tools\ - - \ No newline at end of file diff --git a/src/Stunts/Stunts.Generator.Build/Stunts.Generator.targets b/src/Stunts/Stunts.Generator.Build/Stunts.Generator.targets deleted file mode 100644 index a64095dd..00000000 --- a/src/Stunts/Stunts.Generator.Build/Stunts.Generator.targets +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - C# - Visual Basic - - - - false - - - - - - - - - - - - - - - - $(MSBuildProjectDirectory)\$(IntermediateOutputPath)Proxies - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Stunts/Stunts.Generator.Console/App.old.config b/src/Stunts/Stunts.Generator.Console/App.old.config deleted file mode 100644 index 29da5a87..00000000 --- a/src/Stunts/Stunts.Generator.Console/App.old.config +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Stunts/Stunts.Generator.Console/ILRepack.targets b/src/Stunts/Stunts.Generator.Console/ILRepack.targets deleted file mode 100644 index 01603ab8..00000000 --- a/src/Stunts/Stunts.Generator.Console/ILRepack.targets +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - /keyfile:"$(AssemblyOriginatorKeyFile)" - "@(IntermediateAssembly->'%(FullPath)')" - @(MergedAssemblies->'"%(FullPath)"', ' ') - - - - - - - - - \ No newline at end of file diff --git a/src/Stunts/Stunts.Generator.Console/Program.cs b/src/Stunts/Stunts.Generator.Console/Program.cs deleted file mode 100644 index 2aa56c86..00000000 --- a/src/Stunts/Stunts.Generator.Console/Program.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Simplification; -using Mono.Options; - -namespace Moq.Proxy -{ - class Program - { - static int Main(string[] args) => RunAsync(args).Result; - - static async Task RunAsync(string[] args) - { - var shouldShowHelp = false; - var extension = ""; - var languageName = ""; - var outputPath = ""; - var references = new List(); - var sources = new List(); - var additionalInterfaces = new List(); - var additionalProxies = new List(); - var additionalGenerators = new List(); - var debug = false; - - var options = new OptionSet - { - { "e|extension=", "optional file extension of the generated proxy documents, such as '.cs' or '.vb'. Inferred from language if not specified.", e => extension = e }, - { "l|language=", "a Roslyn-supported language name, such as 'C#' or 'Visual Basic'", l => languageName = l }, - { "o|output=", "the output directory to write the proxy files to", o => outputPath = o }, - { "r|reference=", "an assembly reference required for compiling the sources", r => references.Add(r.Trim()) }, - { "s|source=", "a source file in the language specified which should be processed for proxy generation", s => sources.Add(s.Trim()) }, - { "i|interface=", "optional additional interface to implement in all generated proxies", i => additionalInterfaces.Add(i.Trim()) }, - { "p|proxy=", "optional additional proxy type to generate a proxy for", p => additionalProxies.Add(p.Trim()) }, - { "g|generator=", "optional additional generator assembly to participate in proxy generation composition", g => additionalGenerators.Add(g.Trim()) }, - new ResponseFileSource(), - { "d|debug", "launch a debugger before performing the code generation.", _ => debug = true }, - { "h|help", "show this message and exit", h => shouldShowHelp = h != null }, - }; - - List extra; - var appName = Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().ManifestModule.FullyQualifiedName); - - try - { - extra = options.Parse(args); - - if (shouldShowHelp || - string.IsNullOrEmpty(outputPath) || - string.IsNullOrEmpty(languageName) || - (sources.Count == 0 && references.Count == 0)) - { - var writer = shouldShowHelp ? Console.Out : Console.Error; - - // show some app description message - writer.WriteLine($"Usage: {appName} [OPTIONS]+"); - writer.WriteLine(); - - writer.WriteLine("Options:"); - options.WriteOptionDescriptions(writer); - return -1; - } - - if (string.IsNullOrEmpty(extension)) - extension = languageName == LanguageNames.VisualBasic ? ".vb" : ".cs"; - - if (!Directory.Exists(outputPath)) - Directory.CreateDirectory(outputPath); - - if (debug) - Debugger.Launch(); - - var generator = new ProxyGenerator(); - var proxies = await generator.GenerateProxiesAsync(languageName, - references.ToImmutableArray(), - sources.ToImmutableArray(), - additionalInterfaces.ToImmutableArray(), - additionalProxies.ToImmutableArray(), - additionalGenerators.ToImmutableArray(), - CancellationToken.None) - .ConfigureAwait(false); - - foreach (var proxy in proxies) - { - var proxyFile = Path.Combine(outputPath, proxy.Name + extension); - // NOTE: should we provide a -pretty to skip this step? - var document = await Simplifier.ReduceAsync(proxy).ConfigureAwait(false); - var syntax = await document.GetSyntaxRootAsync().ConfigureAwait(false); - var output = syntax.NormalizeWhitespace().ToFullString(); - if (!File.Exists(proxyFile) || !File.ReadAllText(proxyFile).Equals(output, StringComparison.Ordinal)) - { - File.WriteAllText(proxyFile, output); - } - - Console.WriteLine(proxyFile); - } - - return 0; - } - catch (OptionException e) - { - Console.Error.WriteLine($"{appName}: {e.Message}"); - Console.Error.WriteLine($"Try '{appName} --help' for more information."); - return -1; - } - catch (Exception e) - { - // TODO: should we render something different in this case? (non-options exception?) - Console.Error.WriteLine($"{appName}: {e.Message}"); - return -1; - } - } - } -} \ No newline at end of file diff --git a/src/Stunts/Stunts.Generator.Console/Properties/launchSettings.json b/src/Stunts/Stunts.Generator.Console/Properties/launchSettings.json deleted file mode 100644 index 0b030229..00000000 --- a/src/Stunts/Stunts.Generator.Console/Properties/launchSettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "profiles": { - "Moq.Proxy.Generator.Console": { - "commandName": "Project", - "commandLineArgs": "@pgen.rsp", - "workingDirectory": "$(OutputPath)" - } - } -} \ No newline at end of file diff --git a/src/Stunts/Stunts.Generator.Console/Stunts.Generator.Console.csproj b/src/Stunts/Stunts.Generator.Console/Stunts.Generator.Console.csproj deleted file mode 100644 index a3796d2b..00000000 --- a/src/Stunts/Stunts.Generator.Console/Stunts.Generator.Console.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - Exe - net47 - - - pgen - Moq.Proxy - true - - true - true - - - - - - - - - - - true - - - true - - - - - - \ No newline at end of file diff --git a/src/Stunts/Stunts.Internal/DocumentExtensions.cs b/src/Stunts/Stunts.Internal/DocumentExtensions.cs index 82fdbe54..3f92ecf5 100644 --- a/src/Stunts/Stunts.Internal/DocumentExtensions.cs +++ b/src/Stunts/Stunts.Internal/DocumentExtensions.cs @@ -25,17 +25,20 @@ public static class DocumentExtensions .Concat(new[] { new OverridableMembersAnalyzer() }) .ToImmutableArray()); + // TODO: see if this should be moved elsewhere. + public static ImmutableArray BuiltInAnalyzers => builtInAnalyzers.Value; + /// /// Applies the given named code fix to a document. /// - public static async Task ApplyCodeFixAsync(this Document document, string codeFixName, CancellationToken cancellationToken = default) + public static async Task ApplyCodeFixAsync(this Document document, string codeFixName, ImmutableArray analyzers = default, CancellationToken cancellationToken = default) { // If we request and process ALL codefixes at once, we'll get one for each // diagnostics, which is one per non-implemented member of the interface/abstract // base class, so we'd be applying unnecessary fixes after the first one. // So we re-retrieve them after each Apply, which will leave only the remaining // ones. - var codeFixes = await GetCodeFixes(document, codeFixName, cancellationToken).ConfigureAwait(false); + var codeFixes = await GetCodeFixes(document, codeFixName, analyzers, cancellationToken).ConfigureAwait(false); while (codeFixes.Length != 0) { var operations = await codeFixes[0].Action.GetOperationsAsync(cancellationToken); @@ -44,7 +47,7 @@ public static async Task ApplyCodeFixAsync(this Document document, str { document = operation.ChangedSolution.GetDocument(document.Id); // Retrieve the codefixes for the updated doc again. - codeFixes = await GetCodeFixes(document, codeFixName, cancellationToken).ConfigureAwait(false); + codeFixes = await GetCodeFixes(document, codeFixName, analyzers, cancellationToken).ConfigureAwait(false); } else { @@ -57,8 +60,8 @@ public static async Task ApplyCodeFixAsync(this Document document, str } static async Task> GetCodeFixes( - Document document, string codeFixName, - CancellationToken cancellationToken = default) + Document document, string codeFixName, + ImmutableArray analyzers = default, CancellationToken cancellationToken = default) { var provider = GetCodeFixProvider(document, codeFixName); if (provider == null) @@ -66,7 +69,10 @@ static async Task> GetCodeFixes( var compilation = await document.Project.GetCompilationAsync(cancellationToken); // TODO: should we allow extending the set of built-in analyzers being added? - var analyerCompilation = compilation.WithAnalyzers(builtInAnalyzers.Value, cancellationToken: cancellationToken); + if (analyzers.IsDefaultOrEmpty) + analyzers = builtInAnalyzers.Value; + + var analyerCompilation = compilation.WithAnalyzers(analyzers, cancellationToken: cancellationToken); var allDiagnostics = await analyerCompilation.GetAllDiagnosticsAsync(cancellationToken); var diagnostics = allDiagnostics .Where(x => provider.FixableDiagnosticIds.Contains(x.Id)) diff --git a/src/Stunts/Stunts.Package/Stunts.Package.nuproj b/src/Stunts/Stunts.Package/Stunts.Package.nuproj index ec54c40a..aa3e3f97 100644 --- a/src/Stunts/Stunts.Package/Stunts.Package.nuproj +++ b/src/Stunts/Stunts.Package/Stunts.Package.nuproj @@ -1,4 +1,4 @@ - + @@ -17,9 +17,12 @@ net11;net20;net35;net40;net403;net45;net451;net452;net46;net461;net462;net47;net471;net472;netcore;netcore45;netcore451;netcore50;win8;win81;win10;sl4;sl5;wp;wp7;wp75;wp8;wp81;wpa81;uap;uap10;netstandard1.0;netstandard1.1;netstandard1.2;netstandard1.3;netstandard1.4;netstandard1.5;netstandard1.6;netstandard2.0;netcoreapp1.0;netcoreapp2.0;monoandroid;monotouch;monomac;xamarinios;xamarinmac;xamarinpsthree;xamarinpsfour;xamarinpsvita;xamarinwatchos;xamarintvos;xamarinxboxthreesixty;xamarinxboxone + - + + + diff --git a/src/Stunts/Stunts.Sdk/Processors/CSharpRewrite.cs b/src/Stunts/Stunts.Sdk/Processors/CSharpRewrite.cs index 8fa450ff..a2b99127 100644 --- a/src/Stunts/Stunts.Sdk/Processors/CSharpRewrite.cs +++ b/src/Stunts/Stunts.Sdk/Processors/CSharpRewrite.cs @@ -77,8 +77,10 @@ public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) } else { - node = node.RemoveNodes(new SyntaxNode[] { node.Body }, SyntaxRemoveOptions.KeepNoTrivia) - .WithExpressionBody( + if (node.Body != null) + node = node.RemoveNodes(new SyntaxNode[] { node.Body }, SyntaxRemoveOptions.KeepNoTrivia); + + node = node.WithExpressionBody( ArrowExpressionClause(ExecutePipeline(node.ReturnType, node.ParameterList.Parameters))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); } diff --git a/src/Stunts/Stunts.Sdk/Processors/CSharpScaffold.cs b/src/Stunts/Stunts.Sdk/Processors/CSharpScaffold.cs index 0ab580b1..94dd69bd 100644 --- a/src/Stunts/Stunts.Sdk/Processors/CSharpScaffold.cs +++ b/src/Stunts/Stunts.Sdk/Processors/CSharpScaffold.cs @@ -52,7 +52,7 @@ public async Task ProcessAsync(Document document, CancellationToken ca foreach (var codeFixName in codeFixNames) { - document = await document.ApplyCodeFixAsync(codeFixName, cancellationToken).ConfigureAwait(false); + document = await document.ApplyCodeFixAsync(codeFixName, cancellationToken: cancellationToken).ConfigureAwait(false); } return document; diff --git a/src/Stunts/Stunts.Sdk/Processors/EnsureStuntsReference.cs b/src/Stunts/Stunts.Sdk/Processors/EnsureStuntsReference.cs deleted file mode 100644 index ebc51539..00000000 --- a/src/Stunts/Stunts.Sdk/Processors/EnsureStuntsReference.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Stunts.Properties; - -namespace Stunts.Processors -{ - /// - /// Verifies that the document's project has the required reference to the Stunts assembly. - /// - public class EnsureStuntsReference : IDocumentProcessor - { - static readonly string StuntsAssembly = typeof(IStunt).Assembly.GetName().Name; - static readonly string StuntsFile = Path.GetFileName(typeof(IStunt).Assembly.ManifestModule.FullyQualifiedName); - - /// - /// Applies to both and . - /// - public string[] Languages { get; } = new[] { LanguageNames.CSharp, LanguageNames.VisualBasic }; - - /// - /// Runs in the first phase of code gen, . - /// - public ProcessorPhase Phase => ProcessorPhase.Prepare; - - /// - /// Throws if the document's project does not - /// have a reference to the Stunts assembly. - /// - public Task ProcessAsync(Document document, CancellationToken cancellationToken = default) - { - // Stunts must either be a direct metadata/assembly reference - if (!document.Project.MetadataReferences.Any(r => r.Display.EndsWith(StuntsFile, StringComparison.Ordinal)) && - // or a resolved project reference to the Stunts project (if someone is using it as source/submodule - !document.Project.ProjectReferences.Select(r => document.Project.Solution.GetProject(r.ProjectId)?.AssemblyName) - .Any(n => StuntsAssembly.Equals(n, StringComparison.Ordinal))) - { - throw new ArgumentException(Strings.StuntsRequired(document.Project.Name)); - } - - return Task.FromResult(document); - } - } -} diff --git a/src/Stunts/Stunts.Sdk/Processors/VisualBasicScaffold.cs b/src/Stunts/Stunts.Sdk/Processors/VisualBasicScaffold.cs index 69a1b6fe..ec4b68ab 100644 --- a/src/Stunts/Stunts.Sdk/Processors/VisualBasicScaffold.cs +++ b/src/Stunts/Stunts.Sdk/Processors/VisualBasicScaffold.cs @@ -50,7 +50,7 @@ public async Task ProcessAsync(Document document, CancellationToken ca { foreach (var codeFixName in codeFixNames) { - document = await document.ApplyCodeFixAsync(codeFixName, cancellationToken).ConfigureAwait(false); + document = await document.ApplyCodeFixAsync(codeFixName, cancellationToken: cancellationToken).ConfigureAwait(false); } return document; diff --git a/src/Stunts/Stunts.Sdk/StuntGenerator.cs b/src/Stunts/Stunts.Sdk/StuntGenerator.cs index 3824a15c..5a2dea00 100644 --- a/src/Stunts/Stunts.Sdk/StuntGenerator.cs +++ b/src/Stunts/Stunts.Sdk/StuntGenerator.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.IO; @@ -29,7 +30,6 @@ public class StuntGenerator /// public static IDocumentProcessor[] GetDefaultProcessors() => new IDocumentProcessor[] { - new EnsureStuntsReference(), new DefaultImports(), new CSharpFileHeader(), new CSharpScaffold(), @@ -111,6 +111,7 @@ public async Task GenerateDocumentAsync(Project project, ITypeSymbol[] #endif Document document; + EnsureTargetDirectory(project); if (project.Solution.Workspace is AdhocWorkspace workspace) { @@ -194,6 +195,7 @@ public async Task ApplyProcessors(Document document, CancellationToken cancellationToken = CancellationToken.None; #endif + EnsureTargetDirectory(document.Project); var language = document.Project.Language; if (!processors.TryGetValue(language, out var supportedProcessors)) return document; @@ -232,5 +234,18 @@ public async Task ApplyProcessors(Document document, CancellationToken return document; } + + void EnsureTargetDirectory(Project project) + { + var autoCodeFixEnabled = bool.TryParse(Environment.GetEnvironmentVariable("AutoCodeFix"), out var value) && value; + // When running the generator from build-time, ensure the folder exists. + if (!autoCodeFixEnabled) + { + // Ensure target directory exists since a linked file in teh same folder + // may already exist in the project and the file adding fails in that case. + var directory = Path.Combine(Path.GetDirectoryName(project.FilePath), Path.Combine(naming.Namespace.Split('.'))); + Directory.CreateDirectory(directory); + } + } } } diff --git a/src/Stunts/Stunts.Tests/Stunts.Tests.csproj b/src/Stunts/Stunts.Tests/Stunts.Tests.csproj index 301734cc..6ccca760 100644 --- a/src/Stunts/Stunts.Tests/Stunts.Tests.csproj +++ b/src/Stunts/Stunts.Tests/Stunts.Tests.csproj @@ -1,20 +1,21 @@  - - net471 - true + + net471 + Latest + true true true - - - - - - - - + + + + + + + + @@ -22,20 +23,35 @@ - - PreserveNewest - - + + PreserveNewest + + PreserveNewest - + + + <_Parameter1>MSBuildBinPath + <_Parameter2>$(MSBuildBinPath) + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Stunts/Stunts/IStunt.cs b/src/Stunts/Stunts/IStunt.cs index 0f3327d0..10cb5fdd 100644 --- a/src/Stunts/Stunts/IStunt.cs +++ b/src/Stunts/Stunts/IStunt.cs @@ -7,7 +7,9 @@ namespace Stunts /// /// Interface implemented by all stunts. /// - [GeneratedCode("Stunts", ThisAssembly.Project.Properties.AssemblyVersion)] // This attribute prevents registering the "Implement through behavior pipeline" codefix. + // These attributes prevent registering the "Implement through behavior pipeline" codefix. + // See CustomMockCodeFixProvider and its base class CustomStuntCodeFixProvider. + [GeneratedCode("Stunts", ThisAssembly.Metadata.Version)] [CompilerGenerated] public interface IStunt { diff --git a/src/Stunts/Stunts/contentFiles/cs/netstandard1.3/Stunt.cs b/src/Stunts/Stunts/contentFiles/cs/netstandard1.3/Stunts/Stunt.cs similarity index 90% rename from src/Stunts/Stunts/contentFiles/cs/netstandard1.3/Stunt.cs rename to src/Stunts/Stunts/contentFiles/cs/netstandard1.3/Stunts/Stunt.cs index 1d06e123..dce422c2 100644 --- a/src/Stunts/Stunts/contentFiles/cs/netstandard1.3/Stunt.cs +++ b/src/Stunts/Stunts/contentFiles/cs/netstandard1.3/Stunts/Stunt.cs @@ -1,14 +1,16 @@ -using System; -using System.Reflection; - namespace Stunts { + using System; + using System.CodeDom.Compiler; + using System.Reflection; + /// /// Instantiates stunts for the specified types. /// + [GeneratedCode("Stunts", "5.0")] internal partial class Stunt { - static T Create(object[] constructorArgs, params Type[] interfaces) => + private static T Create(object[] constructorArgs, params Type[] interfaces) => (T)StuntFactory.Default.CreateStunt(typeof(Stunt).GetTypeInfo().Assembly, typeof(T), interfaces, constructorArgs); /// @@ -41,4 +43,4 @@ static T Create(object[] constructorArgs, params Type[] interfaces) => [StuntGenerator] public static T Of(params object[] constructorArgs) => Create(constructorArgs, typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8)); } -} +} \ No newline at end of file diff --git a/src/Stunts/Stunts/contentFiles/vb/netstandard1.3/Stunt.vb b/src/Stunts/Stunts/contentFiles/vb/netstandard1.3/Stunts/Stunt.vb similarity index 97% rename from src/Stunts/Stunts/contentFiles/vb/netstandard1.3/Stunt.vb rename to src/Stunts/Stunts/contentFiles/vb/netstandard1.3/Stunts/Stunt.vb index 1f371a8c..4e798669 100644 --- a/src/Stunts/Stunts/contentFiles/vb/netstandard1.3/Stunt.vb +++ b/src/Stunts/Stunts/contentFiles/vb/netstandard1.3/Stunts/Stunt.vb @@ -1,8 +1,10 @@ Imports System +Imports System.CodeDom.Compiler Imports System.Reflection Namespace Global.Stunts + Partial Friend Class Stunt Private Shared Function Create(Of T)(ByVal constructorArgs As Object(), ParamArray interfaces As Type()) As T diff --git a/src/Testing/TestHelpers.cs b/src/Testing/TestHelpers.cs index 8749fd88..e1b8893d 100644 --- a/src/Testing/TestHelpers.cs +++ b/src/Testing/TestHelpers.cs @@ -65,8 +65,8 @@ public static ProjectInfo CreateProjectInfo(string language, string assemblyName if (includeStuntApi) { var stuntApi = language == LanguageNames.CSharp ? - new FileInfo(@"contentFiles\cs\netstandard1.3\Stunt.cs").FullName : - new FileInfo(@"contentFiles\vb\netstandard1.3\Stunt.vb").FullName; + new FileInfo(@"contentFiles\cs\netstandard1.3\Stunts\Stunt.cs").FullName : + new FileInfo(@"contentFiles\vb\netstandard1.3\Stunts\Stunt.vb").FullName; Assert.True(File.Exists(stuntApi)); documents.Add(DocumentInfo.Create( @@ -79,8 +79,8 @@ public static ProjectInfo CreateProjectInfo(string language, string assemblyName if (includeMockApi) { var mockApi = language == LanguageNames.CSharp ? - new FileInfo(@"contentFiles\cs\netstandard2.0\Mock.cs").FullName : - new FileInfo(@"contentFiles\vb\netstandard2.0\Mock.vb").FullName; + new FileInfo(@"contentFiles\cs\netstandard2.0\Mocks\Mock.cs").FullName : + new FileInfo(@"contentFiles\vb\netstandard2.0\Mocks\Mock.vb").FullName; var mockApiOverloads = Path.ChangeExtension(mockApi, ".Overloads") + Path.GetExtension(mockApi); Assert.True(File.Exists(mockApi)); diff --git a/src/build/CI.props b/src/build/CI.props deleted file mode 100644 index 304be40a..00000000 --- a/src/build/CI.props +++ /dev/null @@ -1,27 +0,0 @@ - - - - - true - false - - - - - - - \ No newline at end of file diff --git a/src/build/PackageReference.CopyLocal.targets b/src/build/PackageReference.CopyLocal.targets new file mode 100644 index 00000000..3fd29248 --- /dev/null +++ b/src/build/PackageReference.CopyLocal.targets @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + <_CopyLocalPackageId>@(CopyLocalPackageId) + + + + true + + + + + + + + + + + + + + + + + diff --git a/src/build/PackageReferences.targets b/src/build/PackageReferences.targets index bb67dbbe..7403ad23 100644 --- a/src/build/PackageReferences.targets +++ b/src/build/PackageReferences.targets @@ -1,23 +1,24 @@ - 15.* - 2.3.1 - 2.4.0 + 15.7.179 + 2.9.0 + 2.4.0 - - + + + - + - - + + @@ -35,6 +36,11 @@ + + + + - + + \ No newline at end of file diff --git a/src/build/Packaging.targets b/src/build/Packaging.targets index d3005c85..8ba99927 100644 --- a/src/build/Packaging.targets +++ b/src/build/Packaging.targets @@ -27,4 +27,5 @@ + \ No newline at end of file diff --git a/src/build/Settings.props b/src/build/Settings.props index c2a6648b..02990783 100644 --- a/src/build/Settings.props +++ b/src/build/Settings.props @@ -1,8 +1,15 @@ - + + + false + true + + + true + None Latest @@ -10,9 +17,12 @@ false + false + + $(DefaultExcludeItems);*.binlog true - $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\..\')) + $([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)..\..')) true @@ -27,13 +37,8 @@ - - - - - - + diff --git a/src/build/Settings.targets b/src/build/Settings.targets index 090848cf..2779c65f 100644 --- a/src/build/Settings.targets +++ b/src/build/Settings.targets @@ -24,7 +24,7 @@ - diff --git a/src/build/Version.targets b/src/build/Version.targets index d33f7780..d3c29a2e 100644 --- a/src/build/Version.targets +++ b/src/build/Version.targets @@ -5,52 +5,56 @@ true - + + SetVersions;$(GenerateNuspecDependsOn) + SetVersions;$(GetPackageVersionDependsOn) + + + + - + + - - $([MSBuild]::Add('$(GitCommits)', '1')) + $(SYSTEM_PULLREQUEST_TARGETBRANCH) + $(BUILD_SOURCEBRANCHNAME) - - - + + + + + + + + - - - $(GitSemVerDashLabel)-pr$(BUILD_SOURCEBRANCH.Substring(10).TrimEnd('/merge')) - $(GitSemVerDashLabel)-pr$(APPVEYOR_PULL_REQUEST_NUMBER) - - - $(SYSTEM_PULLREQUEST_TARGETBRANCH) - $(BUILD_SOURCEBRANCHNAME) - $(APPVEYOR_REPO_BRANCH) - - - <_IndexOfBranchSlash>$(GitBranch.LastIndexOf('/')) - <_IndexOfBranchSubstring>$([MSBuild]::Add('$(_IndexOfBranchSlash)', '1')) - <_GitBranch Condition="'$(_IndexOfBranchSlash)' != '0'">$(GitBranch.Substring($(_IndexOfBranchSubstring))) - <_GitBranch Condition="'$(_IndexOfBranchSlash)' == '0'">$(GitBranch) - - - $(_GitBranch). - $(SemVerMetadata)g$(GitCommit) - - - $(GitBaseVersionMajor).$(GitBaseVersionMinor).$(GitBaseVersionPatch)$(GitSemVerDashLabel).$(GitCommits)+$(SemVerMetadata) - - $(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch)+$(SemVerMetadata) - $(GitBaseVersionMajor).$(GitBaseVersionMinor).$(GitBaseVersionPatch) - $(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch) - $(PackageVersion) + @(VersionMetadata -> '%(Identity)', '-') + +$(VersionMetadataLabel) + $(GitBaseVersionMajor).$(GitBaseVersionMinor).$(GitBaseVersionPatch)$(GitSemVerDashLabel)$(VersionMetadataPlusLabel) + $(PackageVersion) + + + + <_Parameter1>Version + <_Parameter2>$(Version) + + + <_Parameter1>PackageVersion + <_Parameter2>$(PackageVersion) + + + \ No newline at end of file