├── .editorconfig ├── .gitattributes ├── .gitignore ├── .gitmodules ├── AutoCodeFix.sln ├── CODE_OF_CONDUCT.md ├── LICENSE ├── NuGet.Config ├── README.md ├── azure-build.yml ├── azure-pipelines.yml ├── external └── Directory.Build.targets ├── icon ├── 32.png ├── 48.png ├── 64.png ├── noun_Gear_2069169.png ├── noun_Gear_2069169.svg └── readme.md └── src ├── AutoCodeFix.Package └── AutoCodeFix.Package.msbuildproj ├── AutoCodeFix.ProjectReader ├── App.config ├── AutoCodeFix.ProjectReader.csproj ├── Empty.csproj ├── Empty.vbproj └── Program.cs ├── AutoCodeFix.Tasks ├── ApplyCodeFixes.cs ├── AssemblyResolver.cs ├── AutoCodeFix.Core.targets ├── AutoCodeFix.DesignTime.targets ├── AutoCodeFix.Tasks.csproj ├── AutoCodeFix.props ├── AutoCodeFix.targets ├── BuildExtensions.cs ├── BuildWorkspace.cs ├── CollectAutoCodeFixWarnings.cs ├── DisposableAction.cs ├── DocumentExtensions.cs ├── IBuildConfiguration.cs ├── IWorkspace.cs ├── InitializeBuildEnvironment.cs ├── Internals.cs ├── ProjectReader.cs ├── Properties │ ├── Resources.Designer.cs │ └── Resources.resx ├── RecordAutoCodeFixWarnings.cs ├── ResolveLocalAssemblies.cs ├── SetAutoCodeFixEnvironment.cs ├── StyleCop │ ├── CodeFixEquivalenceGroup.cs │ └── TesterDiagnosticProvider.cs └── WarningsRecorder.cs ├── AutoCodeFix.Tests ├── AutoCodeFix.Tests.csproj ├── AutoFix.cs ├── AutoFix.feature ├── GherkinatorExtensions.cs └── Virtualizer.cs ├── Directory.Build.props ├── Directory.Build.targets ├── GitInfo.txt ├── Packages.props ├── Version.targets └── global.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome:http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Don't use tabs for indentation. 7 | [*] 8 | indent_style = space 9 | # (Please don't specify an indent_size here; that has too many unintended consequences.) 10 | 11 | # Code files 12 | [*.{cs,csx,vb,vbx}] 13 | indent_size = 4 14 | 15 | # Xml project files 16 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 17 | indent_size = 2 18 | 19 | # Xml config files 20 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 21 | indent_size = 2 22 | 23 | # Yaml files 24 | [*.yml] 25 | indent_size = 2 26 | 27 | # JSON files 28 | [*.json] 29 | indent_size = 2 30 | 31 | # Dotnet code style settings: 32 | [*.{cs,vb}] 33 | # Sort using and Import directives with System.* appearing first 34 | dotnet_sort_system_directives_first = true 35 | # Avoid "this." and "Me." if not necessary 36 | dotnet_style_qualification_for_field = false:suggestion 37 | dotnet_style_qualification_for_property = false:suggestion 38 | dotnet_style_qualification_for_method = false:suggestion 39 | dotnet_style_qualification_for_event = false:suggestion 40 | 41 | # Use language keywords instead of framework type names for type references 42 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 43 | dotnet_style_predefined_type_for_member_access = true:suggestion 44 | 45 | # Suggest more modern language features when available 46 | dotnet_style_object_initializer = true:suggestion 47 | dotnet_style_collection_initializer = true:suggestion 48 | dotnet_style_coalesce_expression = true:suggestion 49 | dotnet_style_null_propagation = true:suggestion 50 | dotnet_style_explicit_tuple_names = true:suggestion 51 | 52 | # CSharp code style settings: 53 | [*.cs] 54 | # Prefer "var" everywhere 55 | csharp_style_var_for_built_in_types = true:suggestion 56 | csharp_style_var_when_type_is_apparent = true:suggestion 57 | csharp_style_var_elsewhere = true:suggestion 58 | 59 | # Prefer method-like constructs to have a block body 60 | csharp_style_expression_bodied_methods = false:none 61 | csharp_style_expression_bodied_constructors = false:none 62 | csharp_style_expression_bodied_operators = false:none 63 | 64 | # Prefer property-like constructs to have an expression-body 65 | csharp_style_expression_bodied_properties = true:none 66 | csharp_style_expression_bodied_indexers = true:none 67 | csharp_style_expression_bodied_accessors = true:none 68 | 69 | # Suggest more modern language features when available 70 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 71 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 72 | csharp_style_inlined_variable_declaration = true:suggestion 73 | csharp_style_throw_expression = true:suggestion 74 | csharp_style_conditional_delegate_call = true:suggestion 75 | 76 | # Newline settings 77 | csharp_new_line_before_open_brace = all 78 | csharp_new_line_before_else = true 79 | csharp_new_line_before_catch = true 80 | csharp_new_line_before_finally = true 81 | csharp_new_line_before_members_in_object_initializers = true 82 | csharp_new_line_before_members_in_anonymous_types = true 83 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nuget 2 | .vs 3 | .idea 4 | out 5 | bin 6 | obj 7 | *.nupkg 8 | *.lock.json 9 | *.nuget.props 10 | *.nuget.targets 11 | *.suo 12 | *.user 13 | *.cache 14 | *.log 15 | *.binlog 16 | *.rsp -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/Gherkinator"] 2 | path = external/Gherkinator 3 | url = https://github.com/kzu/Gherkinator.git 4 | branch = master -------------------------------------------------------------------------------- /AutoCodeFix.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28417.167 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoCodeFix.ProjectReader", "src\AutoCodeFix.ProjectReader\AutoCodeFix.ProjectReader.csproj", "{B83184E2-5D3E-48BA-AC9E-0A1EF131A108}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoCodeFix.Tasks", "src\AutoCodeFix.Tasks\AutoCodeFix.Tasks.csproj", "{135CD01C-AC26-4FA4-99AD-B490AFA8B062}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3836DA4E-9300-4587-8C82-0399EB7350D1}" 11 | ProjectSection(SolutionItems) = preProject 12 | azure-build.yml = azure-build.yml 13 | azure-pipelines.yml = azure-pipelines.yml 14 | src\Directory.Build.props = src\Directory.Build.props 15 | src\Directory.Build.targets = src\Directory.Build.targets 16 | src\global.json = src\global.json 17 | NuGet.Config = NuGet.Config 18 | src\Packages.props = src\Packages.props 19 | README.md = README.md 20 | src\Version.targets = src\Version.targets 21 | EndProjectSection 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoCodeFix.Tests", "src\AutoCodeFix.Tests\AutoCodeFix.Tests.csproj", "{7B3F4E2F-3B9D-46B3-A3E8-46194CC61B73}" 24 | EndProject 25 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gherkinator", "Gherkinator", "{45AD3328-A714-4CE9-B47D-7097D7676283}" 26 | ProjectSection(SolutionItems) = preProject 27 | external\Gherkinator\src\Directory.Build.props = external\Gherkinator\src\Directory.Build.props 28 | external\Gherkinator\src\Directory.Build.targets = external\Gherkinator\src\Directory.Build.targets 29 | EndProjectSection 30 | EndProject 31 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gherkinator", "external\Gherkinator\src\Gherkinator\Gherkinator.csproj", "{C4E88EF7-9B83-40C0-B085-90D18FF3A7D8}" 32 | EndProject 33 | Project("{13B669BE-BB05-4DDF-9536-439F39A36129}") = "AutoCodeFix.Package", "src\AutoCodeFix.Package\AutoCodeFix.Package.msbuildproj", "{13956D6F-A714-4E60-9D17-656ADC02C851}" 34 | EndProject 35 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gherkinator.Build", "external\Gherkinator\src\Gherkinator.Build\Gherkinator.Build.csproj", "{53E3D3A1-E86F-45FE-B06E-5D10EBE589E9}" 36 | EndProject 37 | Global 38 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 39 | Debug|Any CPU = Debug|Any CPU 40 | Release|Any CPU = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 43 | {B83184E2-5D3E-48BA-AC9E-0A1EF131A108}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {B83184E2-5D3E-48BA-AC9E-0A1EF131A108}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {B83184E2-5D3E-48BA-AC9E-0A1EF131A108}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {B83184E2-5D3E-48BA-AC9E-0A1EF131A108}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {135CD01C-AC26-4FA4-99AD-B490AFA8B062}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {135CD01C-AC26-4FA4-99AD-B490AFA8B062}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {135CD01C-AC26-4FA4-99AD-B490AFA8B062}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {135CD01C-AC26-4FA4-99AD-B490AFA8B062}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {7B3F4E2F-3B9D-46B3-A3E8-46194CC61B73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {7B3F4E2F-3B9D-46B3-A3E8-46194CC61B73}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {7B3F4E2F-3B9D-46B3-A3E8-46194CC61B73}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {7B3F4E2F-3B9D-46B3-A3E8-46194CC61B73}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {C4E88EF7-9B83-40C0-B085-90D18FF3A7D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {C4E88EF7-9B83-40C0-B085-90D18FF3A7D8}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {C4E88EF7-9B83-40C0-B085-90D18FF3A7D8}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {C4E88EF7-9B83-40C0-B085-90D18FF3A7D8}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {13956D6F-A714-4E60-9D17-656ADC02C851}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {13956D6F-A714-4E60-9D17-656ADC02C851}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {13956D6F-A714-4E60-9D17-656ADC02C851}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {13956D6F-A714-4E60-9D17-656ADC02C851}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {53E3D3A1-E86F-45FE-B06E-5D10EBE589E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 64 | {53E3D3A1-E86F-45FE-B06E-5D10EBE589E9}.Debug|Any CPU.Build.0 = Debug|Any CPU 65 | {53E3D3A1-E86F-45FE-B06E-5D10EBE589E9}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {53E3D3A1-E86F-45FE-B06E-5D10EBE589E9}.Release|Any CPU.Build.0 = Release|Any CPU 67 | EndGlobalSection 68 | GlobalSection(SolutionProperties) = preSolution 69 | HideSolutionNode = FALSE 70 | EndGlobalSection 71 | GlobalSection(NestedProjects) = preSolution 72 | {C4E88EF7-9B83-40C0-B085-90D18FF3A7D8} = {45AD3328-A714-4CE9-B47D-7097D7676283} 73 | {53E3D3A1-E86F-45FE-B06E-5D10EBE589E9} = {45AD3328-A714-4CE9-B47D-7097D7676283} 74 | EndGlobalSection 75 | GlobalSection(ExtensibilityGlobals) = postSolution 76 | SolutionGuid = {0D057138-81AC-4232-B280-867A24BA4E13} 77 | EndGlobalSection 78 | EndGlobal 79 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at daniel@cazzulino.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Daniel Cazzulino 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Icon](https://raw.github.com/kzu/AutoCodeFix/master/icon/32.png) AutoCodeFix 2 | ============ 3 | 4 | [![Version](https://img.shields.io/nuget/vpre/AutoCodeFix.svg)](https://www.nuget.org/packages/AutoCodeFix) 5 | [![Downloads](https://img.shields.io/nuget/dt/AutoCodeFix)](https://www.nuget.org/packages/AutoCodeFix) 6 | [![Build Status](https://dev.azure.com/kzu/oss/_apis/build/status/AutoCodeFix?branchName=master)](http://build.azdo.io/kzu/oss/19) 7 | 8 | Applies Roslyn code fixes automatically during build for the chosen "auto codefix" diagnostics, fixing 9 | the code for you automatically, instead of having to manually apply code fixes in the IDE. 10 | 11 | In the project file, specify the diagnostic identifiers you want to apply code fixes automatically to. 12 | This example uses two diagnostics from the [StyleCop.Analyzers](https://www.nuget.org/packages/StyleCop.Analyzers) 13 | package: 14 | 15 | ```xml 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ``` 28 | 29 | The analyzers and code fixes available during build are the same used during design time in the 30 | IDE, added to the project via the `` item group. Analyzers 31 | distributed via nuget packages already add those automatically to your project (such as the 32 | [StyleCop.Analyzers](https://www.nuget.org/packages/StyleCop.Analyzers) , 33 | [RefactoringEssentials](https://www.nuget.org/packages/RefactoringEssentials), 34 | [Roslynator.Analyzers](https://www.nuget.org/packages/Roslynator.Analyzers) and 35 | [Roslynator.CodeFixes](https://www.nuget.org/Roslynator.CodeFixes), etc). 36 | 37 | It's important to note that by default, the compiler *has* to emit the diagnostics you want them 38 | fixed automatically. For diagnostics that are of `Info` severity by default (i.e. [RCS1003: Add braces to if-else](https://github.com/JosefPihrt/Roslynator/blob/master/docs/analyzers/RCS1003.md)) 39 | you can bump its severity to `Warning` so that `AutoCodeFix` can properly process them automatically on the next build. 40 | 41 | You [configure analyzers](https://docs.microsoft.com/en-us/visualstudio/code-quality/use-roslyn-analyzers?view=vs-2017) using 42 | the built-in editor in VS, which for the example above, would result in a rule set like the following: 43 | 44 | ```xml 45 | 46 | 47 | 48 | 49 | 50 | ``` 51 | 52 | With that configuration in place, you can add the `AutoCodeFix` just like before: 53 | 54 | ```xml 55 | 56 | 57 | 58 | ``` 59 | 60 | When no fixable diagnostics are emitted by the compiler, the impact of `AutoCodeFix` on build times 61 | is virtually none. 62 | 63 | 64 | > NOTE: the main use case for `AutoCodeFix` is to fix the code as you go, on every build. Therefore, it performs 65 | > best when warnings to fix are few and introduced in between builds. Although it can be used to apply fixes to 66 | > entire code bases for normalization/compliance purposes as a one-time fixup, that can take some time, even if 67 | > a [Fix All](https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md) provider exists 68 | > for the diagnostics. Run time is also impacted by the complexity of the code fix itself. As an example, 69 | > the StyleCop code fix for [usings sorting](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1208.md) 70 | > can fix ~60 instances per second on my Dell XPS 13 9370. 71 | 72 | 73 | > Icon [Gear](https://thenounproject.com/term/gear/2069169/) by 74 | > [Putra Theoo](https://thenounproject.com/tnputra555), 75 | > from [The Noun Project](https://thenounproject.com/) 76 | -------------------------------------------------------------------------------- /azure-build.yml: -------------------------------------------------------------------------------- 1 | # Builds and tests AutoCodeFix for the specified 2 | # versions of Roslyn 3 | 4 | parameters: 5 | roslyn: '' 6 | 7 | steps: 8 | - task: MSBuild@1 9 | displayName: Restore ${{ parameters.roslyn }} 10 | inputs: 11 | solution: AutoCodeFix.sln 12 | configuration: $(Configuration) 13 | msbuildArguments: -t:restore -p:RoslynVersion=${{ parameters.roslyn }} 14 | 15 | - task: MSBuild@1 16 | displayName: Build ${{ parameters.roslyn }} 17 | inputs: 18 | solution: AutoCodeFix.sln 19 | configuration: $(Configuration) 20 | msbuildArguments: -p:GeneratePackageOnBuild=true -p:RoslynVersion=${{ parameters.roslyn }} -p:PackageOutputPath=$(Build.ArtifactStagingDirectory)/pkgs -bl:"$(Build.ArtifactStagingDirectory)/logs/build.${{ parameters.roslyn }}.binlog" 21 | 22 | - task: VSTest@2 23 | displayName: Test ${{ parameters.roslyn }} 24 | timeoutInMinutes: 5 25 | inputs: 26 | testAssemblyVer2: src/AutoCodeFix.Tests/bin/*/AutoCodeFix.Tests.dll 27 | runInParallel: true 28 | codeCoverageEnabled: true 29 | publishRunAttachments: true 30 | diagnosticsEnabled: false 31 | rerunFailedTests: true 32 | 33 | - task: PublishBuildArtifacts@1 34 | displayName: Upload Packages 35 | condition: succeeded() 36 | inputs: 37 | PathtoPublish: $(Build.ArtifactStagingDirectory)/pkgs 38 | ArtifactName: packages 39 | ArtifactType: Container 40 | 41 | - task: PublishBuildArtifacts@1 42 | displayName: Upload Logs 43 | condition: always() 44 | inputs: 45 | PathtoPublish: $(Build.ArtifactStagingDirectory)/logs 46 | ArtifactName: logs 47 | ArtifactType: Container 48 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | batch: false 3 | branches: 4 | include: 5 | - master 6 | - features/* 7 | - releases/* 8 | - dev/* 9 | paths: 10 | exclude: 11 | - docs 12 | pr: 13 | - master 14 | - features/* 15 | - releases/* 16 | 17 | variables: 18 | - group: Sleet 19 | - name: Configuration 20 | value: Release 21 | - name: ReleaseLabel 22 | value: beta 23 | 24 | stages: 25 | 26 | - stage: Build2017 27 | dependsOn: [] 28 | jobs: 29 | - job: Build 30 | pool: 31 | vmImage: 'windows-2019' 32 | steps: 33 | - checkout: self 34 | clean: true 35 | 36 | - template: azure-build.yml 37 | parameters: 38 | roslyn: '2.10.0' 39 | 40 | - stage: Build2019_3_0 41 | dependsOn: [] 42 | jobs: 43 | - job: Build 44 | pool: 45 | vmImage: 'windows-2019' 46 | steps: 47 | - checkout: self 48 | clean: true 49 | 50 | - template: azure-build.yml 51 | parameters: 52 | roslyn: '3.0.0' 53 | 54 | - stage: Build2019_3_1 55 | dependsOn: [] 56 | jobs: 57 | - job: Build 58 | pool: 59 | vmImage: 'windows-2019' 60 | steps: 61 | - checkout: self 62 | clean: true 63 | 64 | - template: azure-build.yml 65 | parameters: 66 | roslyn: '3.1.0' 67 | 68 | - stage: Deploy 69 | dependsOn: 70 | - Build2017 71 | - Build2019_3_0 72 | - Build2019_3_1 73 | variables: 74 | - name: SleetVersion 75 | value: 2.3.33 76 | jobs: 77 | - deployment: Deploy 78 | pool: 79 | vmImage: 'windows-2019' 80 | environment: sleet 81 | strategy: 82 | runOnce: 83 | deploy: 84 | steps: 85 | - pwsh: | 86 | $anyinstalled = (dotnet tool list -g | select-string sleet) -ne $null 87 | Write-Host "##vso[task.setvariable variable=Sleet.AnyInstalled;]$anyinstalled" 88 | 89 | $sameinstalled = (dotnet tool list -g | select-string sleet | select-string $(SleetVersion)) -ne $null 90 | Write-Host "##vso[task.setvariable variable=Sleet.SameInstalled;]$sameinstalled" 91 | displayName: 'Check Sleet installed version' 92 | 93 | - task: DotNetCoreCLI@2 94 | displayName: 'Uninstall Sleet if necessary' 95 | continueOnError: true 96 | condition: and(eq(variables['Sleet.AnyInstalled'], 'True'), eq(variables['Sleet.SameInstalled'], 'False')) 97 | inputs: 98 | command: custom 99 | custom: tool 100 | arguments: 'uninstall -g Sleet' 101 | 102 | - task: DotNetCoreCLI@2 103 | displayName: 'Install Sleet if necessary' 104 | condition: eq(variables['Sleet.SameInstalled'], 'False') 105 | inputs: 106 | command: custom 107 | custom: tool 108 | arguments: 'install --global Sleet --version $(SleetVersion)' 109 | 110 | - task: DownloadPipelineArtifact@2 111 | inputs: 112 | artifactName: packages 113 | 114 | - script: 'sleet push --config none $(Pipeline.Workspace)/packages -f --verbose -p "SLEET_FEED_CONNECTIONSTRING=$(SLEET_FEED_CONNECTIONSTRING)"' 115 | displayName: 'Push package via Sleet' -------------------------------------------------------------------------------- /external/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | false 6 | 7 | 8 | -------------------------------------------------------------------------------- /icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kzu/AutoCodeFix/50aace00922eb35d89bcdc77ef83da50e06fd50c/icon/32.png -------------------------------------------------------------------------------- /icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kzu/AutoCodeFix/50aace00922eb35d89bcdc77ef83da50e06fd50c/icon/48.png -------------------------------------------------------------------------------- /icon/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kzu/AutoCodeFix/50aace00922eb35d89bcdc77ef83da50e06fd50c/icon/64.png -------------------------------------------------------------------------------- /icon/noun_Gear_2069169.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kzu/AutoCodeFix/50aace00922eb35d89bcdc77ef83da50e06fd50c/icon/noun_Gear_2069169.png -------------------------------------------------------------------------------- /icon/noun_Gear_2069169.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icon/readme.md: -------------------------------------------------------------------------------- 1 | [Gear](https://thenounproject.com/term/gear/2069169/) by 2 | [Putra Theoo](https://thenounproject.com/tnputra555), 3 | from [The Noun Project](https://thenounproject.com/) -------------------------------------------------------------------------------- /src/AutoCodeFix.Package/AutoCodeFix.Package.msbuildproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | net472 6 | 7 | 8 | 9 | AutoCodeFix 10 | Applies code fixes automatically during build. 11 | AutoCodeFix 12 | https://raw.github.com/kzu/AutoCodeFix/master/icon/64.png 13 | https://github.com/kzu/AutoCodeFix 14 | MIT 15 | https://raw.githubusercontent.com/kzu/AutoCodeFix/master/LICENSE 16 | roslyn codegen codefix analyzer 17 | 18 | 19 | 20 | true 21 | false 22 | false 23 | PackageReference 24 | NO-SDK-PACK 25 | true 26 | false 27 | false 28 | true 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | <_ToDelete Include="$(TEMP)\packages\$(PackageId)*.nupkg" /> 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/AutoCodeFix.ProjectReader/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/AutoCodeFix.ProjectReader/AutoCodeFix.ProjectReader.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | ProjectReader 6 | AutoCodeFix 7 | Exe 8 | false 9 | 10 | tools 11 | false 12 | false 13 | true 14 | true 15 | 16 | net472 17 | net46 18 | 19 | 20 | 21 | 22 | all 23 | false 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/AutoCodeFix.ProjectReader/Empty.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/AutoCodeFix.ProjectReader/Empty.vbproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/AutoCodeFix.ProjectReader/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Dynamic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Microsoft.Build.Locator; 11 | using Microsoft.CodeAnalysis; 12 | using Microsoft.CodeAnalysis.MSBuild; 13 | using Mono.Options; 14 | using Newtonsoft.Json; 15 | using StreamJsonRpc; 16 | 17 | namespace AutoCodeFix 18 | { 19 | internal class Program 20 | { 21 | private static readonly TraceSource tracer = new TraceSource(nameof(AutoCodeFix)); 22 | private static ManualResetEventSlim exit = new ManualResetEventSlim(); 23 | private static bool ready; 24 | private static Task initializer = Task.CompletedTask; 25 | 26 | public static async Task Main(string[] args) 27 | { 28 | var help = false; 29 | var msbuild = default(string); 30 | var wait = false; 31 | var parent = default(Process); 32 | var project = default(string); 33 | var properties = new Dictionary(); 34 | var output = default(string); 35 | var parentId = 0; 36 | var parentNotFound = false; 37 | 38 | var options = new OptionSet 39 | { 40 | { "m|msbuild=", "MSBuild root directory.", m => msbuild = m }, 41 | { "p|project:", "MSBuild project file to read", s => project = s }, 42 | { "o|output:", "Output file to emit. Emits to standard output if not specified", o => output = o }, 43 | { "<>", "Additional MSBuild global properties to set", v => 44 | { 45 | var values = v.Split(new[] { '=', ':' }, 2); 46 | properties[values[0].Trim()] = values[1].Trim().Trim('\"'); 47 | } 48 | }, 49 | new ResponseFileSource(), 50 | { "parent:", "Parent process ID to monitor to automatically exit", (int p) => 51 | { 52 | try 53 | { 54 | parentId = p; 55 | parent = Process.GetProcessById(p); 56 | } 57 | catch (ArgumentException) 58 | { 59 | parentNotFound = true; 60 | } 61 | } 62 | }, 63 | { "w|wait", "Wait before exiting when a source project or solution is specified", w => wait = true }, 64 | { "d|debug", "Attach a debugger to the process", d => Debugger.Launch() }, 65 | { "h|?|help", "Show this message and exit", h => help = true }, 66 | }; 67 | 68 | List extra; 69 | var appName = Path.GetFileName(Assembly.GetExecutingAssembly().ManifestModule.FullyQualifiedName); 70 | 71 | try 72 | { 73 | extra = options.Parse(args); 74 | if (parentNotFound) 75 | { 76 | Console.Error.WriteLine($"Specified parent process ID {parentId} is not running. Exiting."); 77 | return -1; 78 | } 79 | 80 | if (help || string.IsNullOrEmpty(msbuild)) 81 | { 82 | var writer = help ? Console.Out : Console.Error; 83 | 84 | // show some app description message 85 | writer.WriteLine($"Usage: {appName} [OPTIONS]+"); 86 | writer.WriteLine(); 87 | 88 | writer.WriteLine("Options:"); 89 | options.WriteOptionDescriptions(writer); 90 | 91 | return -1; 92 | } 93 | 94 | MSBuildLocator.RegisterMSBuildPath(msbuild); 95 | 96 | if (parent != null) 97 | { 98 | if (parent.HasExited) 99 | return 0; 100 | 101 | parent.EnableRaisingEvents = true; 102 | parent.Exited += (_, __) => exit.Set(); 103 | } 104 | 105 | if (!string.IsNullOrEmpty(project)) 106 | { 107 | // The intent is to operate in standalone commandline, 108 | // so no JsonRpc will be set up. 109 | var program = new Program(); 110 | 111 | if (properties.Count == 0) 112 | Console.WriteLine($@"Creating workspace..."); 113 | else 114 | Console.WriteLine($@"Creating workspace with properties: 115 | {properties.Select(pair => $"\t{pair.Key}={pair.Value}")}"); 116 | 117 | await program.CreateWorkspaceAsync(properties); 118 | Console.WriteLine($@"Opening project {Path.GetFileName(project)}..."); 119 | var metadata = await program.OpenProjectAsync(project); 120 | if (string.IsNullOrEmpty(output)) 121 | Console.WriteLine(JsonConvert.SerializeObject(metadata, Formatting.Indented, new EnumConverter())); 122 | else 123 | File.WriteAllText(output, JsonConvert.SerializeObject(metadata, Formatting.Indented, new EnumConverter())); 124 | 125 | Console.WriteLine("Done"); 126 | 127 | if (wait) 128 | Console.ReadLine(); 129 | 130 | return 0; 131 | } 132 | else 133 | { 134 | // When no source is passed in, we assume the mode will be RPC and start 135 | // listening 136 | var program = new Program(); 137 | var rpc = new JsonRpc(Console.OpenStandardOutput(), Console.OpenStandardInput(), program); 138 | rpc.StartListening(); 139 | 140 | // Force resolving right-away. 141 | initializer = Task.Run(Init); 142 | exit.Wait(); 143 | 144 | program.workspace?.Dispose(); 145 | 146 | return 0; 147 | } 148 | } 149 | catch (OptionException e) 150 | { 151 | Console.Error.WriteLine($"{appName}: {e.Message}"); 152 | Console.Error.WriteLine($"Try '{appName} -?' for more information."); 153 | 154 | if (wait) 155 | { 156 | Console.ReadLine(); 157 | } 158 | 159 | return -1; 160 | } 161 | catch (ReflectionTypeLoadException re) 162 | { 163 | #if DEBUG 164 | if (!Debugger.IsAttached) 165 | { 166 | Debugger.Launch(); 167 | } 168 | #endif 169 | 170 | // TODO: should we render something different in this case? (non-options exception?) 171 | Console.Error.WriteLine($"{appName}: {string.Join(Environment.NewLine, re.LoaderExceptions.Select(e => e.ToString()))}"); 172 | 173 | if (wait) 174 | { 175 | Console.ReadLine(); 176 | } 177 | 178 | return -1; 179 | } 180 | catch (Exception e) 181 | { 182 | #if DEBUG 183 | if (!Debugger.IsAttached) 184 | { 185 | Debugger.Launch(); 186 | } 187 | #endif 188 | 189 | // TODO: should we render something different in this case? (non-options exception?) 190 | Console.Error.WriteLine($"{appName}: {e.Message}"); 191 | 192 | if (wait) 193 | { 194 | Console.ReadLine(); 195 | } 196 | 197 | return -1; 198 | } 199 | } 200 | 201 | private static async Task Init() 202 | { 203 | var workspace = MSBuildWorkspace.Create(); 204 | await workspace.OpenProjectAsync( 205 | Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().ManifestModule.FullyQualifiedName), "Empty.csproj")); 206 | await workspace.OpenProjectAsync( 207 | Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().ManifestModule.FullyQualifiedName), "Empty.vbproj")); 208 | ready = true; 209 | } 210 | 211 | private MSBuildWorkspace workspace; 212 | 213 | public void Debug() => Debugger.Launch(); 214 | 215 | public void Exit() => Task.Delay(200).ContinueWith(_ => exit.Set()); 216 | 217 | public bool Ping() => ready; 218 | 219 | public async Task CreateWorkspaceAsync(Dictionary properties) 220 | { 221 | await initializer; 222 | workspace?.Dispose(); 223 | try 224 | { 225 | workspace = MSBuildWorkspace.Create(properties); 226 | workspace.WorkspaceFailed += (sender, args) => tracer.TraceEvent(TraceEventType.Error, 0, $"{args.Diagnostic.Kind}: {args.Diagnostic.Message}"); 227 | workspace.SkipUnrecognizedProjects = true; 228 | } 229 | catch (Exception e) 230 | { 231 | tracer.TraceEvent(TraceEventType.Error, 0, e.ToString()); 232 | throw; 233 | } 234 | } 235 | 236 | public async Task CloseWorkspaceAsync() 237 | { 238 | await initializer; 239 | workspace?.Dispose(); 240 | } 241 | 242 | public async Task OpenProjectAsync(string projectFile, CancellationToken cancellation = default) 243 | { 244 | if (workspace == null) 245 | { 246 | throw new InvalidOperationException($"No active workspace. Invoke {nameof(CreateWorkspaceAsync)} first."); 247 | } 248 | 249 | var projectFullPath = new FileInfo(projectFile).FullName; 250 | var project = workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == projectFullPath); 251 | if (project == null) 252 | { 253 | tracer.TraceInformation($"Project {Path.GetFileName(projectFile)} not found, opening..."); 254 | project = await workspace.OpenProjectAsync(projectFullPath, new LoadProgress(), cancellation); 255 | } 256 | else 257 | { 258 | tracer.TraceInformation($"Existing project found in current workspace solution for {Path.GetFileName(projectFile)}"); 259 | } 260 | 261 | return ProjectToMetadata(project); 262 | } 263 | 264 | class LoadProgress : IProgress 265 | { 266 | public void Report(ProjectLoadProgress value) 267 | => tracer.TraceEvent(TraceEventType.Verbose, 0, $"{Path.GetFileName(value.FilePath)} ({value.Operation}): {value.ElapsedTime}"); 268 | } 269 | 270 | private object ProjectToMetadata(Project project) 271 | { 272 | return new 273 | { 274 | project.Id.Id, 275 | project.Name, 276 | project.AssemblyName, 277 | project.Language, 278 | project.FilePath, 279 | project.OutputFilePath, 280 | CompilationOptions = new 281 | { 282 | project.CompilationOptions.OutputKind, 283 | project.CompilationOptions.Platform, 284 | project.CompilationOptions.CryptoKeyFile, 285 | project.CompilationOptions.DelaySign, 286 | project.CompilationOptions.PublicSign, 287 | }, 288 | ProjectReferences = project.ProjectReferences 289 | .Where(x => workspace.CurrentSolution.ProjectIds.Contains(x.ProjectId)) 290 | .Select(x => ProjectToMetadata(workspace.CurrentSolution.Projects.First(p => p.Id == x.ProjectId))) 291 | .ToArray(), 292 | MetadataReferences = project.MetadataReferences.OfType() 293 | .Select(x => x.FilePath).ToArray(), 294 | Documents = project.Documents 295 | .Select(x => new Document 296 | { 297 | FilePath = x.FilePath, 298 | Folders = x.Folders.ToArray() 299 | }) 300 | .ToArray(), 301 | AdditionalDocuments = project.AdditionalDocuments 302 | .Select(x => new Document 303 | { 304 | FilePath = x.FilePath, 305 | Folders = x.Folders.ToArray() 306 | }) 307 | .ToArray() 308 | }; 309 | } 310 | 311 | public class Document 312 | { 313 | public string FilePath { get; set; } 314 | public string[] Folders { get; set; } 315 | } 316 | 317 | public class EnumConverter : JsonConverter 318 | { 319 | public override bool CanConvert(Type objectType) 320 | => objectType.IsEnum; 321 | 322 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 323 | => throw new NotImplementedException(); 324 | 325 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 326 | => writer.WriteValue(value.ToString()); 327 | } 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/ApplyCodeFixes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Collections.Immutable; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using AutoCodeFix.Properties; 12 | using Microsoft.Build.Framework; 13 | using Microsoft.CodeAnalysis; 14 | using Microsoft.CodeAnalysis.CodeActions; 15 | using Microsoft.CodeAnalysis.CodeFixes; 16 | using Microsoft.CodeAnalysis.Diagnostics; 17 | using Microsoft.CodeAnalysis.Host.Mef; 18 | using Microsoft.CodeAnalysis.Text; 19 | using StyleCopTester; 20 | using Xamarin.Build; 21 | using Humanizer; 22 | 23 | namespace AutoCodeFix 24 | { 25 | public class ApplyCodeFixes : AsyncTask 26 | { 27 | static readonly Version MinRoslynVersion = new Version(1, 2); 28 | 29 | LoggerVerbosity? verbosity = null; 30 | 31 | [Required] 32 | public string ProjectFullPath { get; set; } 33 | 34 | /// 35 | /// Gets or sets the paths to directories to search for dependencies. 36 | /// 37 | [Required] 38 | public ITaskItem[] AssemblySearchPath { get; set; } 39 | 40 | [Required] 41 | public ITaskItem[] AutoCodeFixIds { get; set; } 42 | 43 | [Required] 44 | public string Language { get; set; } 45 | 46 | //[Required] 47 | public ITaskItem[] Analyzers { get; set; } = new ITaskItem[0]; 48 | 49 | public ITaskItem[] SkipAnalyzers { get; set; } = new ITaskItem[0]; 50 | 51 | public ITaskItem[] AdditionalFiles { get; set; } = new ITaskItem[0]; 52 | 53 | public ITaskItem[] NoWarn { get; set; } = new ITaskItem[0]; 54 | 55 | public ITaskItem[] WarningsAsErrors { get; set; } = new ITaskItem[0]; 56 | 57 | public string PreprocessorSymbols { get; set; } = ""; 58 | 59 | public string CodeAnalysisRuleSet { get; set; } 60 | 61 | public bool BuildingInsideVisualStudio { get; set; } 62 | 63 | /// 64 | /// Logging verbosity for reporting. 65 | /// 66 | public string Verbosity { get; set; } = LoggerVerbosity.Normal.ToString(); 67 | 68 | [Output] 69 | public ITaskItem[] FailedAnalyzers { get; set; } = new ITaskItem[0]; 70 | 71 | public override bool Execute() 72 | { 73 | if (AutoCodeFixIds?.Length == 0) 74 | return true; 75 | 76 | if (Verbosity != null && Verbosity.Length > 0) 77 | { 78 | if (!Enum.TryParse(Verbosity, out var value)) 79 | { 80 | Log.LogError($"Invalid {nameof(Verbosity)} value '{Verbosity}'. Expected values: {string.Join(", ", Enum.GetNames(typeof(LoggerVerbosity)))}"); 81 | return false; 82 | } 83 | verbosity = value; 84 | } 85 | 86 | using (var resolver = new AssemblyResolver(AssemblySearchPath, (i, m) => Log.LogMessage(i, m))) 87 | { 88 | Task.Run(ExecuteAsync).ConfigureAwait(false); 89 | return base.Execute(); 90 | } 91 | } 92 | 93 | private async Task ExecuteAsync() 94 | { 95 | try 96 | { 97 | LogMessage("Applying code fixes...", MessageImportance.Low.ForVerbosity(verbosity)); 98 | var overallTime = Stopwatch.StartNew(); 99 | var appliedFixes = new ConcurrentDictionary(); 100 | 101 | LogMessage("Getting Workspace...", MessageImportance.Low.ForVerbosity(verbosity)); 102 | var workspace = BuildEngine4.GetRegisteredTaskObject(BuildingInsideVisualStudio); 103 | 104 | var watch = Stopwatch.StartNew(); 105 | LogMessage("Getting Project...", MessageImportance.Low.ForVerbosity(verbosity)); 106 | var project = await workspace.GetOrAddProjectAsync( 107 | BuildEngine4.GetRegisteredTaskObject(BuildingInsideVisualStudio), 108 | ProjectFullPath, 109 | PreprocessorSymbols.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries), 110 | (i, m) => LogMessage(m, i), Token); 111 | 112 | // Locate our settings ini file 113 | var settings = AdditionalFiles.Select(x => x.GetMetadata("FullPath")).FirstOrDefault(x => x.EndsWith("AutoCodeFix.ini")); 114 | if (settings != null && !project.AdditionalDocuments.Any(d => d.FilePath != null && d.FilePath.Equals(settings))) 115 | { 116 | Debug.Assert(workspace 117 | .TryApplyChanges(project 118 | .AddAdditionalDocument(Path.GetFileName(settings), File.ReadAllText(settings), filePath: settings) 119 | .Project.Solution), 120 | "Failed to apply changes to workspace"); 121 | 122 | project = workspace.CurrentSolution.GetProject(project.Id); 123 | Debug.Assert(project != null, "Failed to retrieve project from current workspace solution."); 124 | } 125 | 126 | watch.Stop(); 127 | 128 | LogMessage($"Loaded {project.Name} in {TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).Humanize()}", MessageImportance.Low.ForVerbosity(verbosity)); 129 | 130 | var fixableIds = new HashSet(AutoCodeFixIds.Select(x => x.ItemSpec)); 131 | 132 | watch.Restart(); 133 | 134 | var analyzerAssemblies = LoadAnalyzers().Concat(MefHostServices.DefaultAssemblies).ToList(); 135 | var failedAssemblies = new List(); 136 | 137 | Dictionary allProviders = default; 138 | 139 | while (allProviders == null) 140 | { 141 | // We filter all available codefix providers to only those that support the project language and can 142 | // fix any of the generator diagnostic codes we received. 143 | 144 | try 145 | { 146 | allProviders = MefHostServices.Create(analyzerAssemblies) 147 | .GetExports>() 148 | .Where(x => ((string[])x.Metadata["Languages"]).Contains(project.Language)) 149 | .SelectMany(x => x.Value.FixableDiagnosticIds.Select(id => new { Id = id, Provider = x.Value })) 150 | .Where(x => fixableIds.Contains(x.Id)) 151 | .GroupBy(x => x.Id) 152 | .ToDictionary(x => x.Key, x => x.Select(y => y.Provider).ToArray()); 153 | break; 154 | } 155 | catch (ReflectionTypeLoadException rle) 156 | { 157 | foreach (var assembly in rle.Types 158 | .Where(t => t != null) 159 | .GroupBy(t => t.Assembly) 160 | .Select(g => g.Key)) 161 | { 162 | Log.LogWarning( 163 | nameof(AutoCodeFix), 164 | nameof(Resources.ACF002), 165 | null, null, 0, 0, 0, 0, 166 | Resources.ACF002, 167 | assembly.Location, "Skipping analyer for AutoCodeFix"); 168 | 169 | failedAssemblies.Add(assembly); 170 | analyzerAssemblies.Remove(assembly); 171 | } 172 | } 173 | } 174 | 175 | FailedAnalyzers = failedAssemblies 176 | .Select(asm => Analyzers.FirstOrDefault(i => i.GetMetadata("FullPath") == asm.Location)) 177 | .Where(i => i != null) 178 | .Concat(SkipAnalyzers) 179 | .ToArray(); 180 | 181 | watch.Stop(); 182 | LogMessage($"Loaded {allProviders.SelectMany(x => x.Value).Distinct().Count()} applicable code fix providers in {TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).Humanize()}", MessageImportance.Low.ForVerbosity(verbosity)); 183 | 184 | Token.ThrowIfCancellationRequested(); 185 | 186 | watch.Restart(); 187 | 188 | var analyzers = analyzerAssemblies 189 | .SelectMany(GetTypes) 190 | .Where(t => !t.IsAbstract && typeof(DiagnosticAnalyzer).IsAssignableFrom(t)) 191 | .Where(t => 192 | { 193 | try 194 | { 195 | return t.GetConstructor(Type.EmptyTypes) != null; 196 | } 197 | catch (TargetInvocationException tie) 198 | { 199 | Log.LogWarning( 200 | nameof(AutoCodeFix), 201 | nameof(Resources.ACF004), 202 | null, null, 0, 0, 0, 0, 203 | Resources.ACF004, 204 | t.FullName, tie.InnerException); 205 | return false; 206 | } 207 | }) 208 | .Select(CreateAnalyzer) 209 | // Only keep the analyzers that can fix the diagnostics we were given. 210 | .Where(d => d != null && d.SupportedDiagnostics.Any(s => fixableIds.Contains(s.Id))) 211 | .ToImmutableArray(); 212 | 213 | watch.Stop(); 214 | LogMessage($"Loaded {analyzers.Length} applicable analyzers in {TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).Humanize()}", MessageImportance.Low.ForVerbosity(verbosity)); 215 | 216 | Token.ThrowIfCancellationRequested(); 217 | 218 | // Report errors for the fixable ids that don't have a corresponding codefix provider. 219 | var unfixable = fixableIds.Where(id => !allProviders.ContainsKey(id)).ToArray(); 220 | if (unfixable.Any()) 221 | { 222 | LogCodedError(nameof(Resources.ACF005), Resources.ACF005, string.Join(", ", unfixable)); 223 | Cancel(); 224 | return; 225 | } 226 | 227 | // Report errors for the fixable ids that don't have a corresponding analyzer. 228 | unfixable = fixableIds.Where(id => !analyzers.Any(x => x.SupportedDiagnostics.Any(d => d.Id == id))).ToArray(); 229 | if (unfixable.Any()) 230 | { 231 | LogCodedError(nameof(Resources.ACF010), Resources.ACF010, string.Join(", ", unfixable)); 232 | Cancel(); 233 | return; 234 | } 235 | 236 | fixableIds = new HashSet(fixableIds.Where(id => allProviders.ContainsKey(id))); 237 | 238 | var compilationOptions = CreateCompilationOptions(project); 239 | if (!workspace.TryApplyChanges(project.Solution.WithProjectCompilationOptions(project.Id, compilationOptions))) 240 | { 241 | throw new NotSupportedException("Workspace does not support supplying project compilation options."); 242 | } 243 | 244 | project = workspace.CurrentSolution.GetProject(project.Id); 245 | 246 | watch.Restart(); 247 | 248 | var additionalFiles = ImmutableArray.Create(AdditionalFiles == null ? Array.Empty() : 249 | AdditionalFiles 250 | .Select(x => x.GetMetadata("FullPath")) 251 | .Distinct() 252 | .Select(x => AdditionalTextFile.Create(x)).ToArray()); 253 | 254 | // TODO: upcoming editorconfig-powered options available to Analyzers would 255 | // not work if we invoke them like this. See how the editor options can be 256 | // retrieved and passed properly here. See https://github.com/dotnet/roslyn/projects/18 257 | var options = new AnalyzerOptions(additionalFiles); 258 | 259 | async Task<(ImmutableArray, Diagnostic, CodeFixProvider[])> GetNextFixableDiagnostic() 260 | { 261 | var getNextWatch = Stopwatch.StartNew(); 262 | var compilation = await project.GetCompilationAsync(Token); 263 | 264 | var analyzed = compilation.WithAnalyzers(analyzers, options); 265 | var allDiagnostics = await analyzed.GetAnalyzerDiagnosticsAsync(analyzers, Token); 266 | var nextDiagnostic = allDiagnostics.FirstOrDefault(d => fixableIds.Contains(d.Id)); 267 | var nextProviders = nextDiagnostic == null ? null : allProviders[nextDiagnostic.Id]; 268 | 269 | getNextWatch.Stop(); 270 | if (nextDiagnostic == null) 271 | LogMessage($"Did not find more fixable diagnostics in {TimeSpan.FromMilliseconds(getNextWatch.ElapsedMilliseconds).Humanize()}", MessageImportance.Low.ForVerbosity(verbosity)); 272 | else 273 | LogMessage($"Found fixable diagnostic {nextDiagnostic.Id} in {TimeSpan.FromMilliseconds(getNextWatch.ElapsedMilliseconds).Humanize()}", MessageImportance.Low.ForVerbosity(verbosity)); 274 | 275 | return (allDiagnostics, nextDiagnostic, nextProviders); 276 | } 277 | 278 | var (diagnostics, diagnostic, providers) = await GetNextFixableDiagnostic(); 279 | 280 | while (diagnostic != null && providers?.Length != 0) 281 | { 282 | var document = project.GetDocument(diagnostic.Location.SourceTree); 283 | Debug.Assert(document != null, "Failed to locate document from diagnostic."); 284 | 285 | CodeAction codeAction = null; 286 | 287 | var fixApplied = false; 288 | 289 | foreach (var provider in providers) 290 | { 291 | // TODO: add support for using the provider.GetFixAllProvider() if one is returned, 292 | // which should boost performance when the FixAllProvider is tunned for performance. 293 | // See https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/StyleCop.Analyzers/StyleCopTester/Program.cs#L314 294 | // for inspiration and in-depth knowledge of how it should be applied/supported. 295 | 296 | await provider.RegisterCodeFixesAsync( 297 | new CodeFixContext(document, diagnostic, 298 | (action, diag) => codeAction = action, 299 | Token)); 300 | 301 | if (codeAction == null) 302 | continue; 303 | 304 | // See if we can get a FixAll provider for the diagnostic we are trying to fix. 305 | if (provider.GetFixAllProvider() is FixAllProvider fixAll && 306 | fixAll != null && 307 | fixAll.GetSupportedFixAllDiagnosticIds(provider).Contains(diagnostic.Id) && 308 | fixAll.GetSupportedFixAllScopes().Contains(FixAllScope.Project)) 309 | { 310 | var group = await CodeFixEquivalenceGroup.CreateAsync(provider, ImmutableDictionary.CreateRange(new [] 311 | { 312 | new KeyValuePair>(project.Id, diagnostics) 313 | }), project.Solution, Token); 314 | 315 | // TODO: should we only apply one equivalence group at a time? See https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/StyleCop.Analyzers/StyleCopTester/Program.cs#L330 316 | if (group.Length > 0) 317 | { 318 | LogMessage($"Applying batch code fix for {diagnostic.Id}: {diagnostic.Descriptor.Title}", MessageImportance.Normal.ForVerbosity(verbosity)); 319 | var fixAllWatch = Stopwatch.StartNew(); 320 | foreach (var fix in group) 321 | { 322 | try 323 | { 324 | LogMessage($"Calculating fix for {fix.NumberOfDiagnostics} instances.", MessageImportance.Low.ForVerbosity(verbosity)); 325 | var operations = await fix.GetOperationsAsync(Token); 326 | var fixAllChanges = operations.OfType().FirstOrDefault(); 327 | if (fixAllChanges != null) 328 | { 329 | fixAllChanges.Apply(workspace, Token); 330 | fixApplied = true; 331 | appliedFixes[diagnostic.Id] = appliedFixes.GetOrAdd(diagnostic.Id, 0) + fix.NumberOfDiagnostics; 332 | project = workspace.CurrentSolution.GetProject(project.Id); 333 | watch.Stop(); 334 | } 335 | 336 | LogMessage($"Applied batch changes in {TimeSpan.FromMilliseconds(fixAllWatch.ElapsedMilliseconds).Humanize()}. This is {fix.NumberOfDiagnostics / fixAllWatch.Elapsed.TotalSeconds:0.000} instances/second.", MessageImportance.Low.ForVerbosity(verbosity)); 337 | } 338 | catch (Exception ex) 339 | { 340 | // Report thrown exceptions 341 | LogMessage($"The fix '{fix.CodeFixEquivalenceKey}' failed after {TimeSpan.FromMilliseconds(fixAllWatch.ElapsedMilliseconds).Humanize()}: {ex.ToString()}", MessageImportance.High.ForVerbosity(verbosity)); 342 | } 343 | } 344 | } 345 | } 346 | 347 | // Try applying the individual fix in a specific document 348 | if (!fixApplied) 349 | { 350 | try 351 | { 352 | LogMessage($"Applying code fix for {diagnostic}", MessageImportance.Normal.ForVerbosity(verbosity)); 353 | var operations = await codeAction.GetOperationsAsync(Token); 354 | var applyChanges = operations.OfType().FirstOrDefault(); 355 | 356 | if (applyChanges != null) 357 | { 358 | applyChanges.Apply(workspace, Token); 359 | 360 | // According to https://github.com/DotNetAnalyzers/StyleCopAnalyzers/pull/935 and 361 | // https://github.com/dotnet/roslyn-sdk/issues/140, Sam Harwell mentioned that we should 362 | // be forcing a re-parse of the document syntax tree at this point. 363 | var newDoc = await workspace.CurrentSolution.GetDocument(document.Id).RecreateDocumentAsync(Token); 364 | 365 | // TODO: what happens if we can't apply? 366 | Debug.Assert(workspace.TryApplyChanges(newDoc.Project.Solution), "Failed to apply changes to workspace."); 367 | 368 | fixApplied = true; 369 | appliedFixes[diagnostic.Id] = appliedFixes.GetOrAdd(diagnostic.Id, 0) + 1; 370 | watch.Stop(); 371 | 372 | project = await workspace.GetOrAddProjectAsync( 373 | BuildEngine4.GetRegisteredTaskObject(BuildingInsideVisualStudio), 374 | ProjectFullPath, 375 | PreprocessorSymbols.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries), 376 | (i, m) => LogMessage(m, i), Token); 377 | 378 | // We successfully applied one code action for the given diagnostics, 379 | // consider it fixed even if there are other providers. 380 | break; 381 | } 382 | else 383 | { 384 | Log.LogError( 385 | nameof(AutoCodeFix), 386 | nameof(Resources.ACF008), 387 | diagnostic.Location.GetLineSpan().Path, 388 | diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1, 389 | diagnostic.Location.GetLineSpan().StartLinePosition.Character + 1, 390 | diagnostic.Location.GetLineSpan().EndLinePosition.Line + 1, 391 | diagnostic.Location.GetLineSpan().EndLinePosition.Character + 1, 392 | Resources.ACF008, 393 | codeAction.Title, diagnostic.Id, diagnostic.GetMessage()); 394 | Cancel(); 395 | return; 396 | } 397 | } 398 | catch (Exception e) 399 | { 400 | Log.LogError( 401 | nameof(AutoCodeFix), 402 | nameof(Resources.ACF006), 403 | diagnostic.Location.GetLineSpan().Path, 404 | diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1, 405 | diagnostic.Location.GetLineSpan().StartLinePosition.Character + 1, 406 | diagnostic.Location.GetLineSpan().EndLinePosition.Line + 1, 407 | diagnostic.Location.GetLineSpan().EndLinePosition.Character + 1, 408 | Resources.ACF006, 409 | codeAction.Title, diagnostic.Id, diagnostic.GetMessage(), e); 410 | Cancel(); 411 | return; 412 | } 413 | } 414 | } 415 | 416 | if (!fixApplied) 417 | { 418 | Log.LogError( 419 | nameof(AutoCodeFix), 420 | nameof(Resources.ACF007), 421 | null, 422 | diagnostic.Location.GetLineSpan().Path, 423 | diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1, 424 | diagnostic.Location.GetLineSpan().StartLinePosition.Character + 1, 425 | diagnostic.Location.GetLineSpan().EndLinePosition.Line + 1, 426 | diagnostic.Location.GetLineSpan().EndLinePosition.Character + 1, 427 | Resources.ACF007, 428 | diagnostic.Id, diagnostic.GetMessage()); 429 | Cancel(); 430 | return; 431 | } 432 | else 433 | { 434 | LogMessage($"Fixed {diagnostic.Id} in {TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).Humanize()}", MessageImportance.Low.ForVerbosity(verbosity)); 435 | } 436 | 437 | watch.Restart(); 438 | 439 | (diagnostics, diagnostic, providers) = await GetNextFixableDiagnostic(); 440 | } 441 | 442 | overallTime.Stop(); 443 | LogMessage($"Overall processing for {appliedFixes.Values.Sum()} fixes took {TimeSpan.FromMilliseconds(overallTime.ElapsedMilliseconds).Humanize()}", MessageImportance.Low.ForVerbosity(verbosity)); 444 | if (appliedFixes.Count == 1) 445 | { 446 | var appliedFix = appliedFixes.First(); 447 | LogMessage($"Fixed {appliedFix.Key} ({appliedFix.Value} {(appliedFix.Value > 1 ? "instances" : "instance")})", MessageImportance.High.ForVerbosity(verbosity)); 448 | } 449 | else 450 | { 451 | LogMessage($"Fixed {string.Join(", ", appliedFixes.Select(x => $"{x.Key} ({x.Value} {(x.Value > 1 ? "instances" : "instance")})"))}", MessageImportance.High.ForVerbosity(verbosity)); 452 | } 453 | } 454 | catch (Exception e) 455 | { 456 | LogErrorFromException(e); 457 | Cancel(); 458 | } 459 | finally 460 | { 461 | Complete(); 462 | } 463 | } 464 | 465 | CompilationOptions CreateCompilationOptions(Project project) 466 | { 467 | // Process diagnostic options and rule set 468 | var diagnosticOptions = new Dictionary(); 469 | if (!string.IsNullOrEmpty(CodeAnalysisRuleSet)) 470 | { 471 | _ = RuleSet.GetDiagnosticOptionsFromRulesetFile(CodeAnalysisRuleSet, out diagnosticOptions); 472 | } 473 | 474 | // Explicitly supress the diagnostics in NoWarn 475 | foreach (var noWarn in NoWarn) 476 | { 477 | diagnosticOptions[noWarn.ItemSpec] = ReportDiagnostic.Suppress; 478 | } 479 | // Explicitly report as errors the specificied WarningsAsErrors 480 | foreach (var warnAsError in WarningsAsErrors) 481 | { 482 | diagnosticOptions[warnAsError.ItemSpec] = ReportDiagnostic.Error; 483 | } 484 | 485 | // Merge with existing compilation options 486 | var immutableOptions = diagnosticOptions.ToImmutableDictionary().SetItems(project.CompilationOptions.SpecificDiagnosticOptions); 487 | var compilationOptions = project.CompilationOptions.WithSpecificDiagnosticOptions(immutableOptions); 488 | 489 | return compilationOptions; 490 | } 491 | 492 | IEnumerable LoadAnalyzers(bool warn = true) 493 | { 494 | var analyzers = new List(Analyzers.Length); 495 | foreach (var item in Analyzers.Where(x => !SkipAnalyzers.Any(s => s.ItemSpec == x.ItemSpec))) 496 | { 497 | try 498 | { 499 | var assembly = Assembly.LoadFrom(item.GetMetadata("FullPath")); 500 | var roslyn = assembly.GetReferencedAssemblies().FirstOrDefault(x => x.Name == "Microsoft.CodeAnalysis"); 501 | if (roslyn != null && roslyn.Version < MinRoslynVersion) 502 | { 503 | if (warn) 504 | { 505 | Log.LogWarning( 506 | nameof(AutoCodeFix), 507 | nameof(Resources.ACF001), 508 | null, null, 0, 0, 0, 0, 509 | Resources.ACF001, 510 | item.ItemSpec, nameof(AutoCodeFix), MinRoslynVersion); 511 | } 512 | } 513 | else 514 | { 515 | analyzers.Add(assembly); 516 | } 517 | } 518 | catch (Exception e) 519 | { 520 | Log.LogWarning( 521 | nameof(AutoCodeFix), 522 | nameof(Resources.ACF002), 523 | null, null, 0, 0, 0, 0, 524 | Resources.ACF002, 525 | item.ItemSpec, e); 526 | } 527 | } 528 | 529 | return analyzers; 530 | } 531 | 532 | IEnumerable GetTypes(Assembly assembly) 533 | { 534 | try 535 | { 536 | return assembly.GetTypes(); 537 | } 538 | catch (Exception e) 539 | { 540 | Log.LogWarning( 541 | nameof(AutoCodeFix), 542 | nameof(Resources.ACF003), 543 | null, null, 0, 0, 0, 0, 544 | Resources.ACF003, 545 | assembly.FullName, e); 546 | return Enumerable.Empty(); 547 | } 548 | } 549 | 550 | DiagnosticAnalyzer CreateAnalyzer(Type type) 551 | { 552 | try 553 | { 554 | return (DiagnosticAnalyzer)Activator.CreateInstance(type); 555 | } 556 | catch (TargetInvocationException tie) 557 | { 558 | Log.LogWarning( 559 | nameof(AutoCodeFix), 560 | nameof(Resources.ACF004), 561 | null, null, 0, 0, 0, 0, 562 | Resources.ACF004, 563 | type.FullName, tie.InnerException); 564 | 565 | return null; 566 | } 567 | } 568 | 569 | class AdditionalTextFile : AdditionalText 570 | { 571 | public AdditionalTextFile(string path) => Path = path; 572 | 573 | public override string Path { get; } 574 | 575 | public static AdditionalText Create(string path) => new AdditionalTextFile(path); 576 | 577 | public override SourceText GetText(CancellationToken cancellationToken = default) 578 | => SourceText.From(File.ReadAllText(Path)); 579 | } 580 | } 581 | } 582 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/AssemblyResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Microsoft.Build.Framework; 6 | 7 | namespace AutoCodeFix 8 | { 9 | /// 10 | /// Performs initial assembly resolving from the local execution path, as well 11 | /// as per-task resolving when constructed. 12 | /// 13 | public class AssemblyResolver : IDisposable 14 | { 15 | readonly Action logMessage; 16 | 17 | /// 18 | /// Initial resolve hookup to allow initial load to succeed. 19 | /// 20 | static AssemblyResolver() 21 | => AppDomain.CurrentDomain.AssemblyResolve += OnInitialAssemblyResolve; 22 | 23 | /// 24 | /// Forces static initialization/hookup to . 25 | /// 26 | public static void Init() { } 27 | 28 | /// 29 | /// Initial probing is naive, just looking for assemblies alongside the 30 | /// currently executing assembly. 31 | /// 32 | static Assembly OnInitialAssemblyResolve(object sender, ResolveEventArgs args) 33 | { 34 | var directory = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath); 35 | var assemblyFile = Path.Combine(directory, new AssemblyName(args.Name).Name + ".dll"); 36 | if (File.Exists(assemblyFile)) 37 | return Assembly.LoadFrom(assemblyFile); 38 | 39 | return null; 40 | } 41 | 42 | /// 43 | /// Gets or sets the paths to directories to search for dependencies. 44 | /// 45 | public ITaskItem[] AssemblySearchPath { get; } 46 | 47 | public AssemblyResolver(ITaskItem[] assemblySearchPath, Action logMessage) 48 | { 49 | AssemblySearchPath = assemblySearchPath ?? new ITaskItem[0]; 50 | this.logMessage = logMessage; 51 | 52 | AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly; 53 | // Once we're successfully loaded, don't resolve copy-local naively anymore. 54 | AppDomain.CurrentDomain.AssemblyResolve -= OnInitialAssemblyResolve; 55 | } 56 | 57 | private Assembly ResolveAssembly(object sender, ResolveEventArgs args) 58 | { 59 | logMessage(MessageImportance.Low, $"Resolving {args.Name}"); 60 | 61 | var name = new AssemblyName(args.Name); 62 | var assembly = LoadAssembly(name); 63 | 64 | if (assembly != null) 65 | logMessage(MessageImportance.Low, $"Resolved to {assembly.FullName}"); 66 | // We don't care about localized resource assembly loading failures 67 | else if (!name.Name.EndsWith(".resources")) 68 | logMessage(MessageImportance.Low, $"Assembly not found: {args.Name}"); 69 | 70 | return assembly; 71 | } 72 | 73 | private Assembly LoadAssembly(AssemblyName assemblyName) 74 | { 75 | var searchedAssemblies = from pathItem in AssemblySearchPath 76 | let path = pathItem.GetMetadata("FullPath") 77 | where Directory.Exists(path) 78 | from file in Directory.EnumerateFiles(path, $"{assemblyName.Name}.dll", SearchOption.TopDirectoryOnly) 79 | select AssemblyName.GetAssemblyName(file); 80 | 81 | return searchedAssemblies 82 | // Be more strict and just allow same-major? 83 | .Where(name => 84 | // Either major version is greater 85 | name.Version.Major > assemblyName.Version.Major || 86 | // Or it's equal but the minor version is also equal or greater 87 | (name.Version.Major == assemblyName.Version.Major && name.Version.Minor >= assemblyName.Version.Minor)) 88 | .Select(name => 89 | { 90 | logMessage(MessageImportance.Low, $"Loading {name.Name} from {new Uri(name.CodeBase).LocalPath}"); 91 | // LoadFrom should be compatible with how MSBuild itself loads custom tasks assemblies. 92 | try 93 | { 94 | return Assembly.LoadFrom(new Uri(name.CodeBase).LocalPath); 95 | } 96 | catch (Exception e) 97 | { 98 | logMessage(MessageImportance.Normal, $"Failed to load {name.Name}: {e.Message}"); 99 | return null; 100 | } 101 | }) 102 | .FirstOrDefault(); 103 | } 104 | 105 | /// 106 | /// Stop doing custom assembly resolution. 107 | /// 108 | public void Dispose() 109 | => AppDomain.CurrentDomain.AssemblyResolve -= ResolveAssembly; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/AutoCodeFix.Core.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <_NewLineSeparator Condition="'$(OS)' == 'Windows_NT'">%0d 12 | <_NewLineSeparator>$(_NewLineSeparator)%0a 13 | 14 | 21 | <_AutoCodeFixBeforeCompileFlag>$(IntermediateOutputPath)AutoCodeFixBeforeCompile.flag 22 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 55 | 59 | 60 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 72 | 73 | 74 | 75 | 76 | 77 | $(FinalDefineConstants.Replace(',', ';')) 78 | $(DefineConstants) 79 | 80 | 81 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | <_AutoCodeFixesApplied>true 108 | 109 | 110 | 111 | 112 | 113 | 114 | 116 | 117 | 118 | <_OriginalWarningsNotAsErrors>$(WarningsNotAsErrors) 119 | $(WarningsNotAsErrors);@(AutoCodeFix) 120 | 121 | 122 | 123 | 124 | 129 | 130 | 131 | 132 | 133 | 134 | $(_OriginalWarningsNotAsErrors) 135 | 136 | 137 | 138 | 139 | 140 | 141 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 155 | 156 | 165 | 166 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 178 | 179 | 180 | 181 | 182 | 183 | $(FinalDefineConstants.Replace(',', ';')) 184 | $(DefineConstants) 185 | 186 | 187 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 211 | 214 | 215 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/AutoCodeFix.DesignTime.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | _EnsureDesignTimeBuildForBuild; 9 | $(BuildDependsOn) 10 | 11 | 12 | _EnsureDesignTimeBuildForCompile; 13 | $(CompileDependsOn) 14 | 15 | 16 | 17 | 18 | 19 | false 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | true 33 | 34 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/AutoCodeFix.Tasks.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | netstandard2.0 6 | AutoCodeFix 7 | Latest 8 | 9 | Build 10 | false 11 | true 12 | true 13 | 14 | 15 | 16 | 17 | all 18 | false 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | True 56 | True 57 | Resources.resx 58 | 59 | 60 | 61 | 62 | 63 | ResXFileCodeGenerator 64 | Resources.Designer.cs 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/AutoCodeFix.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AutoCodeFix.Tasks.dll 6 | 7 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/AutoCodeFix.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 29 | false 30 | 31 | 32 | false 33 | 34 | 35 | false 36 | 37 | false 38 | 39 | 40 | $(MSBuildThisFileDirectory)..\tools 41 | 42 | 43 | Normal 44 | 45 | 46 | <_AutoCodeFixIni>$(IntermediateOutputPath)AutoCodeFix.ini 47 | 48 | 49 | 50 | 51 | 52 | 53 | 61 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 77 | 78 | 79 | 81 | 82 | 83 | 84 | 85 | 88 | 89 | 90 | $(WarningsAsErrors);@(AutoCodeFix) 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/BuildExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using AutoCodeFix.Properties; 5 | using Microsoft.Build.Execution; 6 | using Microsoft.Build.Framework; 7 | 8 | namespace AutoCodeFix 9 | { 10 | static class BuildExtensions 11 | { 12 | public static IDictionary GetGlobalProperties(this IBuildEngine buildEngine) 13 | { 14 | ProjectInstance project; 15 | 16 | var flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; 17 | var engineType = buildEngine.GetType(); 18 | var callbackField = engineType.GetField("targetBuilderCallback", flags); 19 | 20 | if (callbackField != null) 21 | { 22 | // .NET field naming convention. 23 | var callback = callbackField.GetValue(buildEngine); 24 | var projectField = callback.GetType().GetField("projectInstance", flags); 25 | project = (ProjectInstance)projectField.GetValue(callback); 26 | } 27 | else 28 | { 29 | callbackField = engineType.GetField("_targetBuilderCallback", flags); 30 | if (callbackField == null) 31 | throw new NotSupportedException($"Failed to introspect current MSBuild Engine '{engineType.AssemblyQualifiedName}'."); 32 | 33 | // OSS field naming convention. 34 | var callback = callbackField.GetValue(buildEngine); 35 | var projectField = callback.GetType().GetField("_projectInstance", flags); 36 | project = (ProjectInstance)projectField.GetValue(callback); 37 | } 38 | 39 | return project.GlobalProperties; 40 | } 41 | 42 | /// 43 | /// Gets a previously registered task object of a given type, 44 | /// with a lifetime determined by the 45 | /// value ( if ). 46 | /// 47 | /// The type of object to retrieve, which is also the registration key. 48 | /// The MSBuild engine. 49 | /// Whether the build is being run from Visual Studio. 50 | /// The registered object. 51 | /// The expected object is not registered with the build engine. 52 | /// The registered object under the full type name of is of the wrong type. 53 | public static T GetRegisteredTaskObject(this IBuildEngine4 buildEngine, bool buildingInsideVisualStudio) 54 | { 55 | var lifetime = buildingInsideVisualStudio ? 56 | RegisteredTaskObjectLifetime.AppDomain : 57 | RegisteredTaskObjectLifetime.Build; 58 | var key = typeof(T).FullName; 59 | var registered = buildEngine.GetRegisteredTaskObject(key, lifetime); 60 | 61 | if (registered == null) 62 | throw new InvalidOperationException(string.Format(Resources.TaskObjectNotRegistered, key)); 63 | 64 | if (!(registered is T)) 65 | throw new InvalidOperationException(string.Format(Resources.TaskObjectWrongType, key, registered.GetType().FullName)); 66 | 67 | return (T)registered; 68 | } 69 | 70 | public static MessageImportance ForVerbosity(this MessageImportance importance, LoggerVerbosity? verbosity) 71 | { 72 | if (verbosity == null) 73 | return importance; 74 | 75 | if (verbosity == LoggerVerbosity.Quiet) 76 | return MessageImportance.Low; 77 | 78 | if (verbosity >= LoggerVerbosity.Detailed) 79 | return MessageImportance.High; 80 | 81 | return importance; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/BuildWorkspace.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Microsoft.Build.Framework; 11 | using Microsoft.CodeAnalysis; 12 | using Microsoft.CodeAnalysis.CSharp; 13 | using Microsoft.CodeAnalysis.Host.Mef; 14 | using Microsoft.CodeAnalysis.Text; 15 | using Microsoft.CodeAnalysis.VisualBasic; 16 | 17 | namespace AutoCodeFix 18 | { 19 | internal class BuildWorkspace : Workspace 20 | { 21 | static BuildWorkspace() 22 | => AppContext.SetSwitch("IsBuildTime", true); 23 | 24 | readonly IBuildConfiguration configuration; 25 | 26 | public BuildWorkspace(IBuildConfiguration configuration) 27 | : base(MefHostServices.Create(MefHostServices.DefaultAssemblies), 28 | "BuildWorkspace") 29 | { 30 | this.configuration = configuration; 31 | var properties = new Dictionary(configuration.GlobalProperties); 32 | 33 | // We *never* want do any auto fixing in the project reader. 34 | properties["DisableAutoCodeFix"] = bool.TrueString; 35 | } 36 | 37 | public override bool CanApplyChange(ApplyChangesKind feature) 38 | { 39 | switch (feature) 40 | { 41 | case ApplyChangesKind.AddProject: 42 | case ApplyChangesKind.AddProjectReference: 43 | case ApplyChangesKind.AddDocument: 44 | case ApplyChangesKind.AddAdditionalDocument: 45 | case ApplyChangesKind.ChangeDocument: 46 | case ApplyChangesKind.ChangeCompilationOptions: 47 | case ApplyChangesKind.RemoveDocument: 48 | return true; 49 | } 50 | 51 | return false; 52 | } 53 | 54 | protected override void ApplyDocumentAdded(DocumentInfo info, SourceText text) 55 | { 56 | base.ApplyDocumentAdded(info, text); 57 | Directory.CreateDirectory(Path.GetDirectoryName(info.FilePath)); 58 | using (var writer = new StreamWriter(info.FilePath, false, text.Encoding ?? Encoding.UTF8)) 59 | { 60 | text.Write(writer); 61 | } 62 | configuration.BuildEngine4.LogMessageEvent(new BuildMessageEventArgs($"{nameof(BuildWorkspace)}.{nameof(ApplyDocumentAdded)}: {info.FilePath}", "", nameof(BuildWorkspace), MessageImportance.Low)); 63 | } 64 | 65 | protected override void ApplyDocumentTextChanged(DocumentId id, SourceText text) 66 | { 67 | base.ApplyDocumentTextChanged(id, text); 68 | var document = CurrentSolution.Projects 69 | .FirstOrDefault(p => p.Id == id.ProjectId)? 70 | .GetDocument(id); 71 | 72 | if (document != null) 73 | { 74 | using (var writer = new StreamWriter(document.FilePath, false, text.Encoding ?? Encoding.UTF8)) 75 | { 76 | text.Write(writer); 77 | } 78 | configuration.BuildEngine4.LogMessageEvent(new BuildMessageEventArgs($"{nameof(BuildWorkspace)}.{nameof(ApplyDocumentTextChanged)}: {document.FilePath}", "", nameof(BuildWorkspace), MessageImportance.Low)); 79 | } 80 | } 81 | 82 | protected override void ApplyDocumentRemoved(DocumentId id) 83 | { 84 | base.ApplyDocumentRemoved(id); 85 | var document = CurrentSolution.Projects 86 | .FirstOrDefault(p => p.Id == id.ProjectId)? 87 | .GetDocument(id); 88 | 89 | if (document != null && File.Exists(document.FilePath)) 90 | { 91 | File.Delete(document.FilePath); 92 | } 93 | } 94 | 95 | public async Task GetOrAddProjectAsync(ProjectReader reader, string projectFullPath, 96 | IEnumerable preprocessorSymbols, Action log, CancellationToken cancellation) 97 | { 98 | projectFullPath = new FileInfo(projectFullPath).FullName; 99 | // Ensure full project paths always. 100 | if (!File.Exists(projectFullPath)) 101 | throw new FileNotFoundException("Project file not found.", projectFullPath); 102 | 103 | // We load projects only once. 104 | var project = FindProjectByPath(projectFullPath); 105 | if (project != null) 106 | { 107 | log(MessageImportance.Low, $"Found existing loaded project for {Path.GetFileName(projectFullPath)}."); 108 | return project; 109 | } 110 | 111 | project = await AddProjectAsync(reader, projectFullPath, preprocessorSymbols, log, cancellation); 112 | 113 | TryApplyChanges(CurrentSolution); 114 | 115 | return project; 116 | } 117 | 118 | internal void Cleanup() => ClearSolution(); 119 | 120 | private async Task AddProjectAsync(ProjectReader reader, string projectFullPath, IEnumerable preprocessorSymbols, Action log, CancellationToken cancellation) 121 | { 122 | var watch = Stopwatch.StartNew(); 123 | 124 | var metadata = await reader.OpenProjectAsync(projectFullPath, cancellation); 125 | log(MessageImportance.Low, $"Read '{Path.GetFileName(projectFullPath)}' metadata in {watch.Elapsed.TotalSeconds} seconds."); 126 | 127 | var project = await AddProjectFromMetadataAsync(metadata, preprocessorSymbols, log, cancellation); 128 | 129 | return project; 130 | } 131 | 132 | private async Task AddProjectFromMetadataAsync(dynamic projectMetadata, IEnumerable preprocessorSymbols, Action log, CancellationToken cancellation) 133 | { 134 | var watch = Stopwatch.StartNew(); 135 | 136 | var projectFile = (string)projectMetadata.FilePath; 137 | var language = (string)projectMetadata.Language; 138 | var output = (OutputKind)Enum.Parse(typeof(OutputKind), (string)projectMetadata.CompilationOptions.OutputKind); 139 | var platform = (Platform)Enum.Parse(typeof(Platform), (string)projectMetadata.CompilationOptions.Platform); 140 | 141 | // Iterate the references of the msbuild project 142 | var referencesToAdd = new List(); 143 | foreach (var projectReference in projectMetadata.ProjectReferences) 144 | { 145 | var referencedProject = FindProjectByPath((string)projectReference.FilePath); 146 | if (referencedProject != null) 147 | { 148 | log(MessageImportance.Low, $"Found existing loaded project for {Path.GetFileName((string)projectReference.FilePath)}."); 149 | } 150 | else 151 | { 152 | referencedProject = await AddProjectFromMetadataAsync(projectReference, preprocessorSymbols, log, cancellation); 153 | } 154 | 155 | referencesToAdd.Add(new ProjectReference(referencedProject.Id)); 156 | } 157 | 158 | var compilationOptions = language == LanguageNames.CSharp 159 | ? (CompilationOptions)new CSharpCompilationOptions(output, platform: platform) 160 | : (CompilationOptions)new VisualBasicCompilationOptions(output, platform: platform); 161 | 162 | compilationOptions = compilationOptions 163 | .WithDelaySign((bool?)projectMetadata.CompilationOptions.DelaySign) 164 | .WithPublicSign((bool)projectMetadata.CompilationOptions.PublicSign); 165 | 166 | if (!string.IsNullOrEmpty((string)projectMetadata.CompilationOptions.CryptoKeyFile)) 167 | compilationOptions = compilationOptions.WithCryptoKeyFile((string)projectMetadata.CompilationOptions.CryptoKeyFile); 168 | 169 | compilationOptions = compilationOptions.WithStrongNameProvider(new DesktopStrongNameProvider()); 170 | 171 | OnProjectAdded( 172 | ProjectInfo.Create( 173 | ProjectId.CreateFromSerialized(new Guid((string)projectMetadata.Id)), 174 | VersionStamp.Default, 175 | (string)projectMetadata.Name, 176 | (string)projectMetadata.AssemblyName, 177 | language, 178 | projectFile, 179 | outputFilePath: (string)projectMetadata.OutputFilePath, 180 | metadataReferences: ((IEnumerable)projectMetadata.MetadataReferences) 181 | .Cast() 182 | .Select(x => MetadataReference.CreateFromFile(x.ToString())), 183 | // Switch compilation options depending on language. 184 | compilationOptions: compilationOptions, 185 | parseOptions: (language == LanguageNames.CSharp 186 | ? (ParseOptions)new CSharpParseOptions(documentationMode: DocumentationMode.None, preprocessorSymbols: preprocessorSymbols.ToArray()) 187 | : (ParseOptions)new VisualBasicParseOptions(documentationMode: DocumentationMode.None, 188 | preprocessorSymbols: preprocessorSymbols.Select(x => x.Split('=')).Select(x => new KeyValuePair(x[0], x.Skip(1).FirstOrDefault()?.Trim('"'))))) 189 | ) 190 | ); 191 | 192 | cancellation.ThrowIfCancellationRequested(); 193 | 194 | // Add the documents to the workspace 195 | foreach (var document in projectMetadata.Documents) 196 | { 197 | AddDocument(projectFile, document, isAdditionalDocument: false); 198 | cancellation.ThrowIfCancellationRequested(); 199 | } 200 | 201 | foreach (var document in projectMetadata.AdditionalDocuments) 202 | { 203 | AddDocument(projectFile, document, isAdditionalDocument: true); 204 | cancellation.ThrowIfCancellationRequested(); 205 | } 206 | 207 | // Fix references 208 | 209 | cancellation.ThrowIfCancellationRequested(); 210 | 211 | if (referencesToAdd.Count > 0) 212 | { 213 | var addedProject = FindProjectByPath(projectFile); 214 | 215 | TryApplyChanges(CurrentSolution.WithProjectReferences(addedProject.Id, referencesToAdd)); 216 | } 217 | 218 | log(MessageImportance.Low, $"Added '{projectMetadata.Name}' to workspace in {watch.Elapsed.TotalSeconds} seconds."); 219 | 220 | return FindProjectByPath(projectFile); 221 | } 222 | 223 | private void AddDocument(string projectPath, dynamic document, bool isAdditionalDocument) 224 | { 225 | var documentPath = (string)document.FilePath; 226 | var project = FindProjectByPath(projectPath); 227 | SourceText text; 228 | using (var reader = new StreamReader(documentPath)) 229 | { 230 | text = SourceText.From(reader.BaseStream); 231 | } 232 | 233 | var documentInfo = DocumentInfo.Create( 234 | DocumentId.CreateNewId(project.Id), 235 | Path.GetFileName(documentPath), 236 | loader: TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create(), documentPath)), 237 | folders: ((IEnumerable)document.Folders).OfType().Select(x => x.ToString()), 238 | filePath: documentPath); 239 | 240 | if (isAdditionalDocument) 241 | { 242 | OnAdditionalDocumentAdded(documentInfo); 243 | } 244 | else 245 | { 246 | OnDocumentAdded(documentInfo); 247 | } 248 | } 249 | 250 | private Project FindProjectByPath(string projectPath) 251 | => CurrentSolution.Projects 252 | .Where(x => string.Equals(x.FilePath, projectPath, StringComparison.InvariantCultureIgnoreCase)) 253 | .FirstOrDefault(); 254 | } 255 | } -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/CollectAutoCodeFixWarnings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.Build.Framework; 4 | using Microsoft.Build.Utilities; 5 | 6 | namespace AutoCodeFix 7 | { 8 | public class CollectAutoCodeFixWarnings : Task 9 | { 10 | [Required] 11 | public string ProjectFile { get; set; } 12 | 13 | [Output] 14 | public ITaskItem[] AutoCodeFixWarnings { get; set; } = new ITaskItem[0]; 15 | 16 | public override bool Execute() 17 | { 18 | var key = ProjectFile + "|" + typeof(WarningsRecorder).FullName; 19 | if (BuildEngine4.GetRegisteredTaskObject(key, RegisteredTaskObjectLifetime.Build) is WarningsRecorder recorder) 20 | { 21 | AutoCodeFixWarnings = recorder.Warnings 22 | .Select(w => new TaskItem(w.Code, new Dictionary 23 | { 24 | { nameof(BuildWarningEventArgs.File), w.File }, 25 | { nameof(BuildWarningEventArgs.LineNumber), w.LineNumber.ToString() }, 26 | { nameof(BuildWarningEventArgs.ColumnNumber), w.ColumnNumber.ToString() }, 27 | { nameof(BuildWarningEventArgs.Message), w.Message }, 28 | { nameof(BuildWarningEventArgs.SenderName), w.SenderName }, 29 | { nameof(BuildWarningEventArgs.ProjectFile), w.ProjectFile }, 30 | })).ToArray(); 31 | 32 | BuildEngine4.UnregisterTaskObject(key, RegisteredTaskObjectLifetime.Build); 33 | recorder.Dispose(); 34 | } 35 | 36 | return true; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/DisposableAction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AutoCodeFix 4 | { 5 | class DisposableAction : IDisposable 6 | { 7 | private Action action; 8 | 9 | public DisposableAction(Action action) => this.action = action; 10 | 11 | public void Dispose() => action(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/DocumentExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.Text; 5 | 6 | namespace AutoCodeFix 7 | { 8 | public static class DocumentExtensions 9 | { 10 | public static async Task RecreateDocumentAsync(this Document document, CancellationToken cancellationToken) 11 | { 12 | var newText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); 13 | newText = newText.WithChanges(new TextChange(new TextSpan(0, 0), " ")); 14 | newText = newText.WithChanges(new TextChange(new TextSpan(0, 1), string.Empty)); 15 | return document.WithText(newText); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/IBuildConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.Build.Framework; 3 | 4 | namespace AutoCodeFix 5 | { 6 | internal interface IBuildConfiguration 7 | { 8 | IBuildEngine4 BuildEngine4 { get; } 9 | 10 | bool BuildingInsideVisualStudio { get; } 11 | 12 | IDictionary GlobalProperties { get; } 13 | 14 | string MSBuildBinPath { get; } 15 | 16 | string ToolsPath { get; } 17 | 18 | bool DebugProjectReader { get; } 19 | 20 | void LogMessage(string message, MessageImportance importance); 21 | 22 | //TaskLoggingHelper Log { get; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/IWorkspace.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using Microsoft.Build.Framework; 3 | using Microsoft.CodeAnalysis; 4 | 5 | namespace AutoCodeFix 6 | { 7 | interface IWorkspace 8 | { 9 | Project GetOrAddProject(IBuildEngine buildEngine, string projectPath, CancellationToken cancellation); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/InitializeBuildEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using Microsoft.Build.Framework; 6 | using Microsoft.Build.Utilities; 7 | 8 | namespace AutoCodeFix 9 | { 10 | public class InitializeBuildEnvironment : Task, IBuildConfiguration 11 | { 12 | /// 13 | /// Gets or sets the paths to directories to search for dependencies. 14 | /// 15 | [Required] 16 | public ITaskItem[] AssemblySearchPath { get; set; } 17 | 18 | public bool BuildingInsideVisualStudio { get; set; } 19 | 20 | /// 21 | /// Whether to debug debug the task for troubleshooting purposes. 22 | /// 23 | public bool DebugAutoCodeFix { get; set; } 24 | 25 | /// 26 | /// Whether to cause the project reader console program to launch a debugger on run 27 | /// for troubleshooting purposes. 28 | /// 29 | public bool DebugProjectReader { get; set; } 30 | 31 | [Required] 32 | public string MSBuildBinPath { get; set; } 33 | 34 | [Required] 35 | public string ToolsPath { get; set; } 36 | 37 | /// 38 | /// Logging verbosity for reporting. 39 | /// 40 | public string Verbosity { get; set; } 41 | 42 | IDictionary IBuildConfiguration.GlobalProperties => BuildEngine.GetGlobalProperties(); 43 | 44 | LoggerVerbosity? verbosity = null; 45 | 46 | public void LogMessage(string message, MessageImportance importance) => Log.LogMessage(importance.ForVerbosity(verbosity), message); 47 | 48 | public override bool Execute() 49 | { 50 | if (DebugAutoCodeFix && !Debugger.IsAttached) 51 | Debugger.Launch(); 52 | 53 | if (Verbosity != null && Verbosity.Length > 0) 54 | { 55 | if (!Enum.TryParse(Verbosity, out var value)) 56 | { 57 | Log.LogError($"Invalid {nameof(Verbosity)} value '{Verbosity}'. Expected values: {string.Join(", ", Enum.GetNames(typeof(LoggerVerbosity)))}"); 58 | return false; 59 | } 60 | verbosity = value; 61 | } 62 | 63 | using (var resolver = new AssemblyResolver(AssemblySearchPath, (i, m) => Log.LogMessage(i, m))) 64 | { 65 | InitializeBuildWorkspace(); 66 | InitializeProjectReader(); 67 | } 68 | 69 | return true; 70 | } 71 | 72 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] 73 | private void InitializeBuildWorkspace() 74 | { 75 | var key = typeof(BuildWorkspace).FullName; 76 | var lifetime = BuildingInsideVisualStudio ? 77 | RegisteredTaskObjectLifetime.AppDomain : 78 | RegisteredTaskObjectLifetime.Build; 79 | 80 | if (!(BuildEngine4.GetRegisteredTaskObject(key, lifetime) is BuildWorkspace workspace)) 81 | { 82 | var watch = Stopwatch.StartNew(); 83 | workspace = new BuildWorkspace(this); 84 | BuildEngine4.RegisterTaskObject(key, workspace, lifetime, false); 85 | watch.Stop(); 86 | LogMessage($"Initialized workspace in {watch.Elapsed.Milliseconds} milliseconds", MessageImportance.Low.ForVerbosity(verbosity)); 87 | } 88 | 89 | // Register a per-build cleaner so we can cleanup the in-memory solution information. 90 | if (BuildEngine4.GetRegisteredTaskObject(key + ".Cleanup", RegisteredTaskObjectLifetime.Build) == null) 91 | { 92 | BuildEngine4.RegisterTaskObject( 93 | key + ".Cleanup", 94 | new DisposableAction(() => workspace.Cleanup()), 95 | RegisteredTaskObjectLifetime.Build, 96 | false); 97 | } 98 | } 99 | 100 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] 101 | private void InitializeProjectReader() 102 | { 103 | var key = typeof(ProjectReader).FullName; 104 | var lifetime = BuildingInsideVisualStudio ? 105 | RegisteredTaskObjectLifetime.AppDomain : 106 | RegisteredTaskObjectLifetime.Build; 107 | 108 | if (!(BuildEngine4.GetRegisteredTaskObject(key, lifetime) is ProjectReader reader)) 109 | { 110 | LogMessage($"Initializing project reader...", MessageImportance.Low.ForVerbosity(verbosity)); 111 | var properties = new Dictionary(BuildEngine.GetGlobalProperties()); 112 | // In the out of process, we're never "building" inside VS, so remove the property. 113 | properties.Remove("BuildingInsideVisualStudio"); 114 | 115 | LogMessage($@"Determined global properties to use: 116 | {string.Join(Environment.NewLine, properties.Select(p => $"\t{p.Key}={p.Value}"))}", MessageImportance.Low.ForVerbosity(verbosity)); 117 | 118 | reader = new ProjectReader(MSBuildBinPath, ToolsPath, DebugProjectReader, properties); 119 | BuildEngine4.RegisterTaskObject(key, reader, lifetime, false); 120 | } 121 | 122 | // Register a per-build cleaner so we can cleanup the in-memory solution information. 123 | if (BuildEngine4.GetRegisteredTaskObject(key + ".Cleanup", RegisteredTaskObjectLifetime.Build) == null) 124 | { 125 | BuildEngine4.RegisterTaskObject( 126 | key + ".Cleanup", 127 | new DisposableAction(async () => await reader.CloseWorkspaceAsync()), 128 | RegisteredTaskObjectLifetime.Build, 129 | false); 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/Internals.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Microsoft.CodeAnalysis.Host; 6 | 7 | namespace AutoCodeFix 8 | { 9 | public static class Internals 10 | { 11 | public static IEnumerable> GetExports(this HostServices services) 12 | { 13 | var getExports = services.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 14 | .FirstOrDefault(m => m.Name.EndsWith("GetExports") && m.IsGenericMethodDefinition && m.GetGenericArguments().Length == 2) 15 | ?.MakeGenericMethod(typeof(TExtension), typeof(TMetadata)) 16 | ?? throw new NotSupportedException("Failed to retrieve exports from host services. Plase report the issue."); 17 | 18 | var exports = getExports.Invoke(services, null); 19 | 20 | return (IEnumerable>)exports; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/ProjectReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using StreamJsonRpc; 8 | 9 | namespace AutoCodeFix 10 | { 11 | internal class ProjectReader : IDisposable 12 | { 13 | private readonly string msBuildBinPath; 14 | private readonly bool debugConsole; 15 | private readonly string readerExe; 16 | private Process process; 17 | private JsonRpc rpc; 18 | private Task initializer; 19 | 20 | public ProjectReader(string msBuildBinPath, string toolsPath, bool debugConsole, IDictionary globalProperties) 21 | { 22 | this.msBuildBinPath = msBuildBinPath; 23 | this.debugConsole = debugConsole; 24 | 25 | readerExe = new FileInfo(Path.Combine(toolsPath, "ProjectReader.exe")).FullName; 26 | 27 | if (!File.Exists(readerExe)) 28 | throw new FileNotFoundException($"Did not find project reader tool at '{readerExe}'.", readerExe); 29 | 30 | EnsureRunning(); 31 | initializer = Task.Run(async () => await CreateWorkspaceAsync(globalProperties)); 32 | } 33 | 34 | private void EnsureRunning() 35 | { 36 | if (process == null || process.HasExited) 37 | { 38 | var args = "-parent=" + Process.GetCurrentProcess().Id + 39 | // We pass down the -d flag so that the external process can also launch a debugger 40 | // for easy troubleshooting. 41 | (debugConsole ? " -d " : " ") + 42 | "-m=\"" + msBuildBinPath + "\""; 43 | 44 | process = Process.Start(new ProcessStartInfo(readerExe, args) 45 | { 46 | CreateNoWindow = true, 47 | RedirectStandardInput = true, 48 | RedirectStandardError = true, 49 | RedirectStandardOutput = true, 50 | UseShellExecute = false, 51 | }); 52 | 53 | rpc = new JsonRpc(process.StandardInput.BaseStream, process.StandardOutput.BaseStream); 54 | rpc.StartListening(); 55 | } 56 | } 57 | 58 | public void Dispose() 59 | { 60 | if (process != null && !process.HasExited) 61 | process.Kill(); 62 | 63 | process = null; 64 | } 65 | 66 | public async Task Debug() 67 | { 68 | EnsureRunning(); 69 | await rpc.InvokeAsync(nameof(Debug)); 70 | } 71 | 72 | public async Task Exit() 73 | { 74 | if (process != null && !process.HasExited) 75 | { 76 | await rpc.InvokeAsync(nameof(Exit)); 77 | process.WaitForExit(); 78 | } 79 | 80 | process = null; 81 | } 82 | 83 | public async Task Ping() 84 | { 85 | EnsureRunning(); 86 | return await rpc.InvokeAsync(nameof(Ping)); 87 | } 88 | 89 | public async Task OpenProjectAsync(string projectFullPath, CancellationToken cancellation) 90 | { 91 | EnsureRunning(); 92 | await initializer; 93 | return await rpc.InvokeWithCancellationAsync(nameof(OpenProjectAsync), new[] { projectFullPath }, cancellation); 94 | } 95 | 96 | public async Task CloseWorkspaceAsync() 97 | { 98 | if (process != null && !process.HasExited) 99 | { 100 | try 101 | { 102 | await rpc.InvokeAsync(nameof(CloseWorkspaceAsync)); 103 | } 104 | catch { } 105 | } 106 | } 107 | 108 | private async Task CreateWorkspaceAsync(IDictionary globalProperties) 109 | { 110 | EnsureRunning(); 111 | // We never do codegen in the remote workspace 112 | await rpc.InvokeAsync(nameof(CreateWorkspaceAsync), new Dictionary(globalProperties) 113 | { 114 | ["NoCodeGen"] = "true" 115 | }); 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace AutoCodeFix.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoCodeFix.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to Analyzer assembly {0} is incompatible with {1}. Must reference Microsoft.CodeAnalysis version {2} or greater.. 65 | /// 66 | internal static string ACF001 { 67 | get { 68 | return ResourceManager.GetString("ACF001", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized string similar to Failed to load analyzer assembly {0}: {1}.. 74 | /// 75 | internal static string ACF002 { 76 | get { 77 | return ResourceManager.GetString("ACF002", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// Looks up a localized string similar to Failed to get types from assembly {0}: {1}.. 83 | /// 84 | internal static string ACF003 { 85 | get { 86 | return ResourceManager.GetString("ACF003", resourceCulture); 87 | } 88 | } 89 | 90 | /// 91 | /// Looks up a localized string similar to Failed to create analyzer {0}: {1}. 92 | /// 93 | internal static string ACF004 { 94 | get { 95 | return ResourceManager.GetString("ACF004", resourceCulture); 96 | } 97 | } 98 | 99 | /// 100 | /// Looks up a localized string similar to Could not locate a code fix for the following specified fixable diagnostics: {0}.. 101 | /// 102 | internal static string ACF005 { 103 | get { 104 | return ResourceManager.GetString("ACF005", resourceCulture); 105 | } 106 | } 107 | 108 | /// 109 | /// Looks up a localized string similar to Failed to apply code action '{0}' for diagnostic {1}: {2}: {3}.. 110 | /// 111 | internal static string ACF006 { 112 | get { 113 | return ResourceManager.GetString("ACF006", resourceCulture); 114 | } 115 | } 116 | 117 | /// 118 | /// Looks up a localized string similar to No code fix action found for diagnostic {0}: {1}.. 119 | /// 120 | internal static string ACF007 { 121 | get { 122 | return ResourceManager.GetString("ACF007", resourceCulture); 123 | } 124 | } 125 | 126 | /// 127 | /// Looks up a localized string similar to No applicable changes were provided by the code action '{0}' for diagnostic {1}: {2}.. 128 | /// 129 | internal static string ACF008 { 130 | get { 131 | return ResourceManager.GetString("ACF008", resourceCulture); 132 | } 133 | } 134 | 135 | /// 136 | /// Looks up a localized string similar to Auto fix {0} was specified as NoWarn and therefore cannot be fixed.. 137 | /// 138 | internal static string ACF009 { 139 | get { 140 | return ResourceManager.GetString("ACF009", resourceCulture); 141 | } 142 | } 143 | 144 | /// 145 | /// Looks up a localized string similar to No analyzer supporting auto fix {0} was found.. 146 | /// 147 | internal static string ACF010 { 148 | get { 149 | return ResourceManager.GetString("ACF010", resourceCulture); 150 | } 151 | } 152 | 153 | /// 154 | /// Looks up a localized string similar to Expected an object of type {0} to be registered with the build engine.. 155 | /// 156 | internal static string TaskObjectNotRegistered { 157 | get { 158 | return ResourceManager.GetString("TaskObjectNotRegistered", resourceCulture); 159 | } 160 | } 161 | 162 | /// 163 | /// Looks up a localized string similar to Expected an object of type {0} to be registered with the build engine, but found object of type {1}.. 164 | /// 165 | internal static string TaskObjectWrongType { 166 | get { 167 | return ResourceManager.GetString("TaskObjectWrongType", resourceCulture); 168 | } 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Analyzer assembly {0} is incompatible with {1}. Must reference Microsoft.CodeAnalysis version {2} or greater. 122 | 123 | 124 | Failed to load analyzer assembly {0}: {1}. 125 | 126 | 127 | Failed to get types from assembly {0}: {1}. 128 | 129 | 130 | Failed to create analyzer {0}: {1} 131 | 132 | 133 | Could not locate a code fix for the following specified fixable diagnostics: {0}. 134 | 135 | 136 | Failed to apply code action '{0}' for diagnostic {1}: {2}: {3}. 137 | 138 | 139 | No code fix action found for diagnostic {0}: {1}. 140 | 141 | 142 | No applicable changes were provided by the code action '{0}' for diagnostic {1}: {2}. 143 | 144 | 145 | Auto fix {0} was specified as NoWarn and therefore cannot be fixed. 146 | 147 | 148 | No analyzer supporting auto fix {0} was found. 149 | 150 | 151 | Expected an object of type {0} to be registered with the build engine. 152 | 153 | 154 | Expected an object of type {0} to be registered with the build engine, but found object of type {1}. 155 | 156 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/RecordAutoCodeFixWarnings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Microsoft.Build.Framework; 4 | using Microsoft.Build.Utilities; 5 | 6 | namespace AutoCodeFix 7 | { 8 | public class RecordAutoCodeFixWarnings : Task 9 | { 10 | [Required] 11 | public string[] AutoCodeFixIds { get; set; } = new string[0]; 12 | 13 | [Required] 14 | public string ProjectFile { get; set; } 15 | 16 | public override bool Execute() 17 | { 18 | var lifetime = RegisteredTaskObjectLifetime.Build; 19 | var key = ProjectFile + "|" + typeof(WarningsRecorder).FullName; 20 | if (BuildEngine4.GetRegisteredTaskObject(key, lifetime) == null) 21 | { 22 | try 23 | { 24 | var recorder = new WarningsRecorder(ProjectFile, AutoCodeFixIds); 25 | var flags = BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Public; 26 | var context = BuildEngine.GetType().InvokeMember("LoggingContext", flags, null, BuildEngine, null); 27 | var logging = context.GetType().InvokeMember("LoggingService", flags, null, context, null); 28 | logging.GetType().InvokeMember("RegisterLogger", flags | BindingFlags.InvokeMethod, null, logging, new[] { recorder }); 29 | 30 | BuildEngine4.RegisterTaskObject(key, recorder, lifetime, false); 31 | } 32 | catch (Exception ex) 33 | { 34 | Log.LogErrorFromException(ex, true); 35 | return false; 36 | } 37 | } 38 | 39 | return true; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/ResolveLocalAssemblies.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Build.Utilities; 2 | 3 | namespace AutoCodeFix 4 | { 5 | /// 6 | /// Initializes the so that 7 | /// the additional assemblies can be loaded by the time the 8 | /// task runs. 9 | /// 10 | public class ResolveLocalAssemblies : Task 11 | { 12 | public override bool Execute() 13 | { 14 | AssemblyResolver.Init(); 15 | return true; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/SetAutoCodeFixEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Build.Framework; 3 | using Microsoft.Build.Utilities; 4 | 5 | namespace AutoCodeFix 6 | { 7 | public class SetAutoCodeFixEnvironment : Task 8 | { 9 | [Required] 10 | public bool DesignTimeBuild { get; set; } 11 | 12 | public override bool Execute() 13 | { 14 | if (!DesignTimeBuild) 15 | Environment.SetEnvironmentVariable("AutoCodeFix", bool.TrueString, EnvironmentVariableTarget.Process); 16 | 17 | return true; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/StyleCop/CodeFixEquivalenceGroup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | // From https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/StyleCop.Analyzers/StyleCopTester/CodeFixEquivalenceGroup.cs 4 | 5 | namespace StyleCopTester 6 | { 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Collections.Immutable; 10 | using System.Linq; 11 | using System.Threading; 12 | using System.Threading.Tasks; 13 | using Microsoft.CodeAnalysis; 14 | using Microsoft.CodeAnalysis.CodeActions; 15 | using Microsoft.CodeAnalysis.CodeFixes; 16 | 17 | internal class CodeFixEquivalenceGroup 18 | { 19 | private CodeFixEquivalenceGroup( 20 | string equivalenceKey, 21 | Solution solution, 22 | FixAllProvider fixAllProvider, 23 | CodeFixProvider codeFixProvider, 24 | ImmutableDictionary>> documentDiagnosticsToFix, 25 | ImmutableDictionary> projectDiagnosticsToFix) 26 | { 27 | this.CodeFixEquivalenceKey = equivalenceKey; 28 | this.Solution = solution; 29 | this.FixAllProvider = fixAllProvider; 30 | this.CodeFixProvider = codeFixProvider; 31 | this.DocumentDiagnosticsToFix = documentDiagnosticsToFix; 32 | this.ProjectDiagnosticsToFix = projectDiagnosticsToFix; 33 | this.NumberOfDiagnostics = documentDiagnosticsToFix.SelectMany(x => x.Value.Select(y => y.Value).SelectMany(y => y)).Count() 34 | + projectDiagnosticsToFix.SelectMany(x => x.Value).Count(); 35 | } 36 | 37 | internal string CodeFixEquivalenceKey { get; } 38 | 39 | internal Solution Solution { get; } 40 | 41 | internal FixAllProvider FixAllProvider { get; } 42 | 43 | internal CodeFixProvider CodeFixProvider { get; } 44 | 45 | internal ImmutableDictionary>> DocumentDiagnosticsToFix { get; } 46 | 47 | internal ImmutableDictionary> ProjectDiagnosticsToFix { get; } 48 | 49 | internal int NumberOfDiagnostics { get; } 50 | 51 | internal static async Task> CreateAsync(CodeFixProvider codeFixProvider, ImmutableDictionary> allDiagnostics, Solution solution, CancellationToken cancellationToken) 52 | { 53 | var fixAllProvider = codeFixProvider.GetFixAllProvider(); 54 | if (fixAllProvider == null) 55 | { 56 | return ImmutableArray.Create(); 57 | } 58 | 59 | Dictionary>> relevantDocumentDiagnostics = 60 | new Dictionary>>(); 61 | Dictionary> relevantProjectDiagnostics = 62 | new Dictionary>(); 63 | 64 | foreach (var projectDiagnostics in allDiagnostics) 65 | { 66 | foreach (var diagnostic in projectDiagnostics.Value) 67 | { 68 | if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)) 69 | { 70 | continue; 71 | } 72 | 73 | if (diagnostic.Location.IsInSource) 74 | { 75 | string sourcePath = diagnostic.Location.GetLineSpan().Path; 76 | 77 | Dictionary> projectDocumentDiagnostics; 78 | if (!relevantDocumentDiagnostics.TryGetValue(projectDiagnostics.Key, out projectDocumentDiagnostics)) 79 | { 80 | projectDocumentDiagnostics = new Dictionary>(); 81 | relevantDocumentDiagnostics.Add(projectDiagnostics.Key, projectDocumentDiagnostics); 82 | } 83 | 84 | List diagnosticsInFile; 85 | if (!projectDocumentDiagnostics.TryGetValue(sourcePath, out diagnosticsInFile)) 86 | { 87 | diagnosticsInFile = new List(); 88 | projectDocumentDiagnostics.Add(sourcePath, diagnosticsInFile); 89 | } 90 | 91 | diagnosticsInFile.Add(diagnostic); 92 | } 93 | else 94 | { 95 | List diagnosticsInProject; 96 | if (!relevantProjectDiagnostics.TryGetValue(projectDiagnostics.Key, out diagnosticsInProject)) 97 | { 98 | diagnosticsInProject = new List(); 99 | relevantProjectDiagnostics.Add(projectDiagnostics.Key, diagnosticsInProject); 100 | } 101 | 102 | diagnosticsInProject.Add(diagnostic); 103 | } 104 | } 105 | } 106 | 107 | ImmutableDictionary>> documentDiagnosticsToFix = 108 | relevantDocumentDiagnostics.ToImmutableDictionary(i => i.Key, i => i.Value.ToImmutableDictionary(j => j.Key, j => j.Value.ToImmutableArray(), StringComparer.OrdinalIgnoreCase)); 109 | ImmutableDictionary> projectDiagnosticsToFix = 110 | relevantProjectDiagnostics.ToImmutableDictionary(i => i.Key, i => i.Value.ToImmutableArray()); 111 | 112 | HashSet equivalenceKeys = new HashSet(); 113 | foreach (var diagnostic in relevantDocumentDiagnostics.Values.SelectMany(i => i.Values).SelectMany(i => i).Concat(relevantProjectDiagnostics.Values.SelectMany(i => i))) 114 | { 115 | foreach (var codeAction in await GetFixesAsync(solution, codeFixProvider, diagnostic, cancellationToken).ConfigureAwait(false)) 116 | { 117 | equivalenceKeys.Add(codeAction.EquivalenceKey); 118 | } 119 | } 120 | 121 | List groups = new List(); 122 | foreach (var equivalenceKey in equivalenceKeys) 123 | { 124 | groups.Add(new CodeFixEquivalenceGroup(equivalenceKey, solution, fixAllProvider, codeFixProvider, documentDiagnosticsToFix, projectDiagnosticsToFix)); 125 | } 126 | 127 | return groups.ToImmutableArray(); 128 | } 129 | 130 | internal async Task> GetOperationsAsync(CancellationToken cancellationToken) 131 | { 132 | Diagnostic diagnostic = this.DocumentDiagnosticsToFix.Values.SelectMany(i => i.Values).Concat(this.ProjectDiagnosticsToFix.Values).First().First(); 133 | Document document = this.Solution.GetDocument(diagnostic.Location.SourceTree); 134 | HashSet diagnosticIds = new HashSet(this.DocumentDiagnosticsToFix.Values.SelectMany(i => i.Values).Concat(this.ProjectDiagnosticsToFix.Values).SelectMany(i => i.Select(j => j.Id))); 135 | 136 | var diagnosticsProvider = new TesterDiagnosticProvider(this.DocumentDiagnosticsToFix, this.ProjectDiagnosticsToFix); 137 | 138 | var context = new FixAllContext(document, this.CodeFixProvider, FixAllScope.Project, this.CodeFixEquivalenceKey, diagnosticIds, diagnosticsProvider, cancellationToken); 139 | 140 | CodeAction action = await this.FixAllProvider.GetFixAsync(context).ConfigureAwait(false); 141 | 142 | return await action.GetOperationsAsync(cancellationToken).ConfigureAwait(false); 143 | } 144 | 145 | private static async Task> GetFixesAsync(Solution solution, CodeFixProvider codeFixProvider, Diagnostic diagnostic, CancellationToken cancellationToken) 146 | { 147 | List codeActions = new List(); 148 | 149 | await codeFixProvider.RegisterCodeFixesAsync(new CodeFixContext(solution.GetDocument(diagnostic.Location.SourceTree), diagnostic, (a, d) => codeActions.Add(a), cancellationToken)).ConfigureAwait(false); 150 | 151 | return codeActions; 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/StyleCop/TesterDiagnosticProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | namespace StyleCopTester 5 | { 6 | using System.Collections.Generic; 7 | using System.Collections.Immutable; 8 | using System.Linq; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Microsoft.CodeAnalysis; 12 | using Microsoft.CodeAnalysis.CodeFixes; 13 | 14 | internal sealed class TesterDiagnosticProvider : FixAllContext.DiagnosticProvider 15 | { 16 | private readonly ImmutableDictionary>> documentDiagnostics; 17 | private readonly ImmutableDictionary> projectDiagnostics; 18 | 19 | public TesterDiagnosticProvider(ImmutableDictionary>> documentDiagnostics, ImmutableDictionary> projectDiagnostics) 20 | { 21 | this.documentDiagnostics = documentDiagnostics; 22 | this.projectDiagnostics = projectDiagnostics; 23 | } 24 | 25 | public override Task> GetAllDiagnosticsAsync(Project project, CancellationToken cancellationToken) 26 | { 27 | ImmutableArray filteredProjectDiagnostics; 28 | if (!this.projectDiagnostics.TryGetValue(project.Id, out filteredProjectDiagnostics)) 29 | { 30 | filteredProjectDiagnostics = ImmutableArray.Empty; 31 | } 32 | 33 | ImmutableDictionary> filteredDocumentDiagnostics; 34 | if (!this.documentDiagnostics.TryGetValue(project.Id, out filteredDocumentDiagnostics)) 35 | { 36 | filteredDocumentDiagnostics = ImmutableDictionary>.Empty; 37 | } 38 | 39 | return Task.FromResult(filteredProjectDiagnostics.Concat(filteredDocumentDiagnostics.Values.SelectMany(i => i))); 40 | } 41 | 42 | public override Task> GetDocumentDiagnosticsAsync(Document document, CancellationToken cancellationToken) 43 | { 44 | ImmutableDictionary> projectDocumentDiagnostics; 45 | if (!this.documentDiagnostics.TryGetValue(document.Project.Id, out projectDocumentDiagnostics)) 46 | { 47 | return Task.FromResult(Enumerable.Empty()); 48 | } 49 | 50 | ImmutableArray diagnostics; 51 | if (!projectDocumentDiagnostics.TryGetValue(document.FilePath, out diagnostics)) 52 | { 53 | return Task.FromResult(Enumerable.Empty()); 54 | } 55 | 56 | return Task.FromResult(diagnostics.AsEnumerable()); 57 | } 58 | 59 | public override Task> GetProjectDiagnosticsAsync(Project project, CancellationToken cancellationToken) 60 | { 61 | ImmutableArray diagnostics; 62 | if (!this.projectDiagnostics.TryGetValue(project.Id, out diagnostics)) 63 | { 64 | return Task.FromResult(Enumerable.Empty()); 65 | } 66 | 67 | return Task.FromResult(diagnostics.AsEnumerable()); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tasks/WarningsRecorder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Microsoft.Build.Framework; 6 | 7 | namespace AutoCodeFix 8 | { 9 | internal class WarningsRecorder : ILogger, IDisposable 10 | { 11 | string projectFile; 12 | string[] diagnosticIds; 13 | IEventSource source; 14 | 15 | public WarningsRecorder(string projectFile, string[] diagnosticIds) 16 | { 17 | this.projectFile = projectFile; 18 | this.diagnosticIds = diagnosticIds; 19 | } 20 | 21 | public IList Warnings { get; } = new List(); 22 | 23 | public string Parameters { get; set; } 24 | 25 | public LoggerVerbosity Verbosity { get; set; } 26 | 27 | public void Initialize(IEventSource eventSource) 28 | { 29 | eventSource.WarningRaised += OnWarning; 30 | source = eventSource; 31 | } 32 | 33 | void OnWarning(object sender, BuildWarningEventArgs e) 34 | { 35 | if (projectFile.Equals(e.ProjectFile, StringComparison.InvariantCultureIgnoreCase) && 36 | diagnosticIds.Any(id => e.Code.Equals(id, StringComparison.OrdinalIgnoreCase))) 37 | Warnings.Add(e); 38 | } 39 | 40 | public void Dispose() => source.WarningRaised -= OnWarning; 41 | 42 | public void Shutdown() { } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tests/AutoCodeFix.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | $(DefaultItemExcludes);**/*.feature 6 | true 7 | false 8 | 9 | net472 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tests/AutoFix.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using Gherkinator; 3 | using System.Diagnostics; 4 | 5 | namespace AutoCodeFix.Tests 6 | { 7 | public class AutoFix 8 | { 9 | [Fact] 10 | public void can_apply_custom_analyer_and_code_fix() 11 | => new BuildScenario() 12 | .UseAutoCodeFix() 13 | .When("restoring packages", c => c.Build("Foo.csproj", "Restore").AssertSuccess()) 14 | .And("building project", c => c.Build("Foo.csproj", "Build").AssertSuccess()) 15 | .Run(); 16 | 17 | [Fact] 18 | public void can_apply_StyleCop_code_fix_automatically() 19 | => new BuildScenario() 20 | .UseAutoCodeFix() 21 | .When("restoring packages", c => c.Build("Foo.csproj", "Restore").AssertSuccess()) 22 | .And("building project", c => c.Build("Foo.csproj", "Build")) 23 | .Then("build succeeds", c => c.AssertSuccess()) 24 | .Run(); 25 | 26 | [Fact] 27 | public void Can_preserve_preprocessor_symbols() 28 | => new BuildScenario() 29 | .UseAutoCodeFix() 30 | .When("restoring packages", c => c.Build("Foo.csproj", "Restore").AssertSuccess()) 31 | .And("building project", c => c.Build("Foo.csproj", "Build")) 32 | .Then("build succeeds", c => c.AssertSuccess()) 33 | .Run(); 34 | 35 | [Fact] 36 | public void Can_preserve_preprocessor_symbols_vb() 37 | => new BuildScenario() 38 | .UseAutoCodeFix() 39 | .When("restoring packages", c => c.Build("Foo.vbproj", "Restore").AssertSuccess()) 40 | .And("building project", c => c.Build("Foo.vbproj", "Build")) 41 | .Then("build succeeds", c => c.AssertSuccess()) 42 | .Run(); 43 | 44 | [Fact] 45 | public void Can_apply_NET_analyzer_code_fix_automatically_in_VB() 46 | => new BuildScenario() 47 | .UseAutoCodeFix() 48 | .When("restoring packages", c => c.Build("Foo.vbproj", "Restore").AssertSuccess()) 49 | .And("building project", c => c.Build("Foo.vbproj", "Build")) 50 | .Then("build succeeds", c => c.AssertSuccess()) 51 | .Run(); 52 | 53 | [Fact] 54 | public void can_apply_StyleCop_batch_code_fix() 55 | => new BuildScenario() 56 | .UseAutoCodeFix() 57 | .When("restoring packages", c => c.Build("Foo.csproj", "Restore").AssertSuccess()) 58 | .And("building project", c => c.Build("Foo.csproj", "Build")) 59 | .Then("build succeeds", c => c.AssertSuccess()) 60 | .Run(); 61 | 62 | [Fact] 63 | public void can_apply_RefactoringEssentials_code_fix_automatically() 64 | => new BuildScenario() 65 | .UseAutoCodeFix() 66 | .When("restoring packages", c => c.Build("Foo.csproj", "Restore").AssertSuccess()) 67 | .And("building project", c => c.Build("Foo.csproj", "Build").AssertSuccess()) 68 | .Run(); 69 | 70 | [Fact] 71 | public void can_apply_Roslynator_code_fix_automatically() 72 | => new BuildScenario() 73 | .UseAutoCodeFix() 74 | .When("restoring packages", c => c.Build("Foo.csproj", "Restore").AssertSuccess()) 75 | .And("building project", c => c.Build("Foo.csproj", "Build").AssertSuccess()) 76 | .Run(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tests/AutoFix.feature: -------------------------------------------------------------------------------- 1 | Feature: AutoCodeFix 2 | Applies code fixes during build 3 | 4 | Background: A common import of the package targets directly 5 | Given Directory.Build.props = 6 | """ 7 | 8 | 9 | 10 | 11 | """ 12 | Given Directory.Build.targets = 13 | """ 14 | 15 | 16 | 17 | """ 18 | And NuGet.Config = 19 | """ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | """ 30 | 31 | Scenario: Can apply custom analyer and code fix 32 | Given Foo.csproj = 33 | """ 34 | 35 | 36 | netstandard2.0 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | """ 46 | And Class1.cs = 47 | """ 48 | public class Class1 49 | { 50 | public void Foo() { } 51 | } 52 | """ 53 | When restoring packages 54 | And building project 55 | Then Class1.cs = 56 | """ 57 | public class Class1 58 | { 59 | public virtual void Foo() { } 60 | } 61 | """ 62 | 63 | Scenario: Can apply StyleCop code fix automatically 64 | Given Foo.csproj = 65 | """ 66 | 67 | 68 | netstandard2.0 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | """ 82 | And Class1.cs = 83 | """ 84 | using Xunit; 85 | using System; 86 | 87 | public static class Program 88 | { 89 | public static void Main() 90 | { 91 | Console.WriteLine("Hello"); 92 | // This comment is too tight 93 | Console.ReadLine(); 94 | } 95 | } 96 | """ 97 | When restoring packages 98 | And building project 99 | Then build succeeds 100 | And Class1.cs = 101 | """ 102 | using System; 103 | using Xunit; 104 | 105 | public static class Program 106 | { 107 | public static void Main() 108 | { 109 | Console.WriteLine("Hello"); 110 | 111 | // This comment is too tight 112 | Console.ReadLine(); 113 | } 114 | } 115 | """ 116 | 117 | Scenario: Can preserve preprocessor symbols 118 | Given Foo.csproj = 119 | """ 120 | 121 | 122 | netstandard2.0 123 | $(DefineConstants);TEST 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | """ 136 | And Class1.cs = 137 | """ 138 | public class Class1 139 | { 140 | #if TEST 141 | public void Main(string[] args) 142 | { 143 | System.Console.WriteLine("Hello World!"); 144 | } 145 | #endif 146 | } 147 | """ 148 | When restoring packages 149 | And building project 150 | Then build succeeds 151 | And Class1.cs = 152 | """ 153 | public class Class1 154 | { 155 | #if TEST 156 | public static void Main() 157 | { 158 | System.Console.WriteLine("Hello World!"); 159 | } 160 | #endif 161 | } 162 | """ 163 | 164 | Scenario: Can preserve preprocessor symbols VB 165 | Given Foo.vbproj = 166 | """ 167 | 168 | 169 | netstandard2.0 170 | TEST=-1 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | """ 183 | And Class1.vb = 184 | """ 185 | Public Class Class1 186 | #If TEST Then 187 | Public Sub Main(ByVal count As Integer) 188 | Console.ReadLine() 189 | End Sub 190 | #End If 191 | End Class 192 | """ 193 | When restoring packages 194 | And building project 195 | Then build succeeds 196 | And Class1.vb = 197 | """ 198 | Public Class Class1 199 | #If TEST Then 200 | Public Shared Sub Main() 201 | Console.ReadLine() 202 | End Sub 203 | #End If 204 | End Class 205 | """ 206 | 207 | Scenario: Can apply NET analyzer code fix automatically in VB 208 | Given Foo.vbproj = 209 | """ 210 | 211 | 212 | netstandard2.0 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | """ 225 | And Class1.vb = 226 | """ 227 | Public Class Class1 228 | 229 | Public Sub Main(ByVal count As Integer) 230 | Console.ReadLine() 231 | End Sub 232 | 233 | End Class 234 | """ 235 | When restoring packages 236 | And building project 237 | Then build succeeds 238 | And Class1.vb = 239 | """ 240 | Public Class Class1 241 | 242 | Public Shared Sub Main() 243 | Console.ReadLine() 244 | End Sub 245 | 246 | End Class 247 | """ 248 | 249 | Scenario: Can apply StyleCop batch code fix 250 | Given Foo.csproj = 251 | """ 252 | 253 | 254 | netstandard2.0 255 | Diagnostic 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | """ 267 | And Class1.cs = 268 | """ 269 | using System; 270 | using Xunit; 271 | 272 | public static class Program 273 | { 274 | public static void Main() 275 | { 276 | // This comment is not too tight 277 | Console.WriteLine("Hello"); 278 | // This comment is too tight2 279 | Console.ReadLine(); 280 | // This comment is too tight3 281 | } 282 | } 283 | """ 284 | When restoring packages 285 | And building project 286 | Then build succeeds 287 | And Class1.cs = 288 | """ 289 | using System; 290 | using Xunit; 291 | 292 | public static class Program 293 | { 294 | public static void Main() 295 | { 296 | // This comment is not too tight 297 | Console.WriteLine("Hello"); 298 | 299 | // This comment is too tight2 300 | Console.ReadLine(); 301 | 302 | // This comment is too tight3 303 | } 304 | } 305 | """ 306 | 307 | Scenario: Can apply RefactoringEssentials code fix automatically 308 | Given Foo.csproj = 309 | """ 310 | 311 | 312 | netstandard2.0 313 | Rules.ruleset 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | """ 323 | And Rules.ruleset = 324 | """ 325 | 326 | 327 | 328 | 329 | 330 | """ 331 | And Class1.cs = 332 | """ 333 | using System; 334 | 335 | public static class Program 336 | { 337 | public static void Main() 338 | { 339 | int[] values = new int[] { 1, 2, 3 }; 340 | Console.WriteLine(values); 341 | } 342 | } 343 | """ 344 | When restoring packages 345 | And building project 346 | Then Class1.cs = 347 | """ 348 | using System; 349 | 350 | public static class Program 351 | { 352 | public static void Main() 353 | { 354 | int[] values = { 1, 2, 3 }; 355 | Console.WriteLine(values); 356 | } 357 | } 358 | """ 359 | 360 | Scenario: Can apply Roslynator code fix automatically 361 | Given Foo.csproj = 362 | """ 363 | 364 | 365 | netstandard2.0 366 | Rules.ruleset 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | """ 377 | And Rules.ruleset = 378 | """ 379 | 380 | 381 | 382 | 383 | 384 | """ 385 | And Class1.cs = 386 | """ 387 | using System; 388 | 389 | public static class Program 390 | { 391 | public static void Main() 392 | { 393 | if (Console.ReadLine().Length == 0) 394 | Console 395 | .WriteLine("true"); 396 | else 397 | Console 398 | .WriteLine("false"); 399 | } 400 | } 401 | """ 402 | When restoring packages 403 | And building project 404 | Then Class1.cs = 405 | """ 406 | using System; 407 | 408 | public static class Program 409 | { 410 | public static void Main() 411 | { 412 | if (Console.ReadLine().Length == 0) 413 | { 414 | Console 415 | .WriteLine("true"); 416 | } 417 | else 418 | { 419 | Console 420 | .WriteLine("false"); 421 | } 422 | } 423 | } 424 | """ 425 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tests/GherkinatorExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using Gherkinator; 8 | using Microsoft.Build.Execution; 9 | using Microsoft.Build.Framework; 10 | using Xunit; 11 | 12 | namespace AutoCodeFix 13 | { 14 | public static class GherkinatorExtensions 15 | { 16 | public static BuildScenario UseAutoCodeFix(this BuildScenario scenario) 17 | { 18 | scenario.Sdk.BeforeGiven(state => 19 | { 20 | // We expand the properties, rather than setting them as global 21 | // properties by setting the Dictionary state, 22 | // so that when files are preserved, we can open and build them 23 | // from msbuild or VS. 24 | var dir = Path.Combine(state.GetTempDir(), "AutoCodeFix.props"); 25 | Directory.CreateDirectory(state.GetTempDir()); 26 | var props = new[] 27 | { 28 | ("CurrentDirectory", Directory.GetCurrentDirectory() + "\\"), 29 | ("DebugAutoCodeFix", Debugger.IsAttached.ToString()), 30 | ("AutoCodeFixPath", Directory.GetCurrentDirectory() + "\\AutoCodeFix\\"), 31 | ("AutoCodeFixVersion", Assembly 32 | .GetExecutingAssembly() 33 | .GetCustomAttributes() 34 | .Where(x => x.Key == "PackageVersion") 35 | .Select(x => x.Value) 36 | .First()), 37 | ("RestoreIgnoreFailedSources", "true"), 38 | //("RestoreSources", Environment.ExpandEnvironmentVariables( 39 | // @"%TEMP%\packages;%USERPROFILE%\.nuget\packages;C:\Program Files\dotnet\sdk\NuGetFallbackFolder;https://api.nuget.org/v3/index.json")) 40 | }; 41 | 42 | File.WriteAllText(dir, @" 43 | 44 | "); 45 | File.AppendAllLines(dir, props.Select(x => $"\t\t<{x.Item1}>{x.Item2}")); 46 | File.AppendAllText(dir, @" 47 | 48 | "); 49 | }); 50 | 51 | return scenario; 52 | } 53 | 54 | public static void AssertSuccess(this (BuildResult result, IEnumerable) build) 55 | { 56 | var project = CallContext.GetData("Build.Project", default(string)); 57 | var target = CallContext.GetData("Build.Target", default(string)); 58 | 59 | if (build.result.OverallResult != BuildResultCode.Success) 60 | CallContext.GetData().OpenLog(project, target); 61 | 62 | Assert.Equal(BuildResultCode.Success, build.result.OverallResult); 63 | } 64 | 65 | public static void AssertSuccess(this BuildContext context, string project = null, string target = null) 66 | { 67 | project = project ?? CallContext.GetData("Build.Project", default(string)); 68 | target = target ?? CallContext.GetData("Build.Target", default(string)); 69 | 70 | var result = context.LastBuildResult; 71 | project = project ?? Path.GetFileName(result.ProjectStateAfterBuild.FullPath); 72 | target = target ?? result.ResultsByTarget.Keys.First(); 73 | 74 | if (result.OverallResult != BuildResultCode.Success) 75 | context.OpenLog(project, target); 76 | 77 | Assert.Equal(BuildResultCode.Success, result.OverallResult); 78 | } 79 | 80 | public static TDictionary Append(this TDictionary dictionary, string key, string value) 81 | where TDictionary : IDictionary 82 | { 83 | dictionary.Add(key, value); 84 | return dictionary; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/AutoCodeFix.Tests/Virtualizer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Immutable; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.CodeAnalysis.CodeActions; 8 | using Microsoft.CodeAnalysis.CodeFixes; 9 | using Microsoft.CodeAnalysis.CSharp; 10 | using Microsoft.CodeAnalysis.CSharp.Syntax; 11 | using Microsoft.CodeAnalysis.Diagnostics; 12 | 13 | namespace AutoCodeFix.Tests 14 | { 15 | // Example analyzer + code fix that makes every method virtual 16 | 17 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 18 | public class VirtualizerAnalyzer : DiagnosticAnalyzer 19 | { 20 | public const string DiagnosticId = "TEST001"; 21 | public const string Category = "Build"; 22 | 23 | readonly DiagnosticDescriptor descriptor = new DiagnosticDescriptor( 24 | DiagnosticId, 25 | "All instance methods should be virtual", 26 | nameof(VirtualizerAnalyzer), 27 | Category, 28 | DiagnosticSeverity.Warning, isEnabledByDefault: true); 29 | 30 | public VirtualizerAnalyzer() 31 | { 32 | // NOTE: the environment variable tells us we're being run with AutoCodeFix enabled and 33 | // active, meaning we should issue warnings so that AutoCodeFix can apply the code fixes. 34 | // If AutoCodeFix is not active, we can alternatively do so if we want to allow users to 35 | // also apply the code fixes themselves, by reporting either Info or Warning. 36 | // Reporting Error is not recommended since it defeats the purpose of AutoCodeFix, since 37 | // in that case the compilation phase will completely fail and therefore not give AutoCodeFix 38 | // a chance to detect that after build and apply the fixes before attempting a second build. 39 | var autoCodeFixEnabled = bool.TryParse(Environment.GetEnvironmentVariable("AutoCodeFix"), out var value) && value; 40 | 41 | descriptor = new DiagnosticDescriptor( 42 | DiagnosticId, 43 | "All methods should be virtual", 44 | "Make '{0}' virtual", 45 | Category, 46 | autoCodeFixEnabled ? DiagnosticSeverity.Warning : DiagnosticSeverity.Hidden, 47 | isEnabledByDefault: true); 48 | } 49 | 50 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(descriptor); 51 | 52 | public override void Initialize(AnalysisContext context) 53 | { 54 | context.RegisterSymbolAction(AnalyzeSymbolNode, SymbolKind.Method); 55 | } 56 | 57 | void AnalyzeSymbolNode(SymbolAnalysisContext context) 58 | { 59 | if (context.Symbol is IMethodSymbol method && 60 | !method.IsStatic && 61 | // .ctor cannot be referenced by name, yet it's reported as an IMethodSymbol 62 | method.CanBeReferencedByName && 63 | !method.IsVirtual && 64 | // We need a declaring reference where we'll add the `virtual` keyword. 65 | context.Symbol.DeclaringSyntaxReferences.FirstOrDefault() is SyntaxReference reference && 66 | reference != null) 67 | { 68 | var syntax = context.Symbol.DeclaringSyntaxReferences.First(); 69 | var diagnostic = Diagnostic.Create(descriptor, Location.Create(reference.SyntaxTree, reference.Span), method.Name); 70 | context.ReportDiagnostic(diagnostic); 71 | 72 | // Optionally, code fixes can optimize build times by flagging to AutoCodeFix that 73 | // there are code fixes to apply before compilation happens. This way, a single build 74 | // will happen, after code fixes are applied. Otherwise, two compilation passes need 75 | // to happen: first to record the fixable warnings, then to apply them and finally to 76 | // compile again so the final code contains the modified versions. 77 | var metadataFile = context.Options.AdditionalFiles.FirstOrDefault(x => x.Path.EndsWith("AutoCodeFix.ini", StringComparison.OrdinalIgnoreCase)); 78 | if (metadataFile != null) 79 | { 80 | // We can pass ourselves arbitrary settings by adding items. 81 | // If items are calculated, you can create a target and run BeforeTargets="SaveAutoCodeFixSettings". 82 | var settings = File.ReadAllLines(metadataFile.Path) 83 | .Where(line => !string.IsNullOrEmpty(line)) 84 | .Select(line => line.Split('=')) 85 | .Where(pair => pair.Length == 2) 86 | .ToDictionary(pair => pair[0].Trim(), pair => pair[1].Trim()); 87 | 88 | // The location of the flag file must be the intermediate output path. 89 | if (settings.TryGetValue("IntermediateOutputPath", out var intermediatePath)) 90 | { 91 | File.WriteAllText(Path.Combine(intermediatePath, "AutoCodeFixBeforeCompile.flag"), ""); 92 | } 93 | } 94 | 95 | } 96 | } 97 | } 98 | 99 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(VirtualizerCodeFixProvider))] 100 | public class VirtualizerCodeFixProvider : CodeFixProvider 101 | { 102 | public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(VirtualizerAnalyzer.DiagnosticId); 103 | 104 | public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; 105 | 106 | public override async Task RegisterCodeFixesAsync(CodeFixContext context) 107 | { 108 | var document = context.Document; 109 | var root = await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 110 | var span = context.Span; 111 | var node = root.FindNode(span); 112 | var token = root.FindToken(span.Start); 113 | if (!token.Span.IntersectsWith(span) || node.Kind() != SyntaxKind.MethodDeclaration) 114 | return; 115 | 116 | var syntax = (MethodDeclarationSyntax)node; 117 | 118 | foreach (var diagnostic in context.Diagnostics.Where(d => FixableDiagnosticIds.Contains(d.Id))) 119 | { 120 | context.RegisterCodeFix( 121 | CodeAction.Create("Make method virtual", 122 | (cancellation) => 123 | { 124 | var updated = syntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.VirtualKeyword)); 125 | var newRoot = syntax.SyntaxTree.GetCompilationUnitRoot().ReplaceNode(syntax, updated); 126 | 127 | return Task.FromResult(document.WithSyntaxRoot(newRoot)); 128 | }, 129 | diagnostic.Id + diagnostic.Location.GetLineSpan().ToString()), 130 | diagnostic); 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | true 6 | 7 | 8 | 9 | kzu 10 | kzu 11 | 12 | https://github.com/kzu/AutoCodeFix/ 13 | https://github.com/kzu/AutoCodeFix/blob/master/LICENSE 14 | Copyright © 2018 Daniel Cazzulino 15 | 16 | 17 | 18 | Latest 19 | Debug 20 | false 21 | false 22 | $(DefaultItemExcludes);*.binlog 23 | false 24 | 25 | 26 | 27 | false 28 | $(MSBuildThisFileDirectory)..\out 29 | 30 | 31 | 32 | 3.0.0 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/GitInfo.txt: -------------------------------------------------------------------------------- 1 | 42.42.42-dev -------------------------------------------------------------------------------- /src/Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15.9.20 5 | 3.0.0 6 | 2.4.1 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/Version.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | true 6 | 7 | 8 | 9 | SetVersions;$(GenerateNuspecDependsOn) 10 | SetVersions;$(GetPackageVersionDependsOn) 11 | 12 | 13 | 14 | $(SYSTEM_PULLREQUEST_TARGETBRANCH) 15 | $(BUILD_SOURCEBRANCHNAME) 16 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | +@(VersionMetadata -> '%(Identity)', '-') 32 | 33 | 34 | 35 | 36 | $(GitSemVerPatch).$(GitSemVerPatch).$(GitSemVerPatch) 37 | $(VersionMetadata) 38 | 39 | 40 | 41 | 42 | $(GitBaseVersionMajor).$(GitBaseVersionMinor).$(GitBaseVersionPatch) 43 | $(GitSemVerDashLabel)$(VersionMetadata) 44 | $(GitSemVerDashLabel).$(GitCommits)$(VersionMetadata) 45 | 46 | 47 | 48 | $(VersionPrefix)$(VersionSuffix) 49 | $(VersionPrefix) 50 | $(VersionPrefix) 51 | $(VersionPrefix).$(GitCommits) 52 | $(Version) 53 | 54 | 55 | 56 | 57 | <_Parameter1>Version 58 | <_Parameter2>$(Version) 59 | 60 | 61 | <_Parameter1>PackageVersion 62 | <_Parameter2>$(PackageVersion) 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | $(RoslynVersion) 72 | 73 | $(RoslynVersion)-dev 74 | $(RoslynVersion)-$(ReleaseLabel) 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "msbuild-sdks": { 3 | "Microsoft.Build.CentralPackageVersions": "2.0.1" 4 | } 5 | } --------------------------------------------------------------------------------