diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..0f17d7434 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,46 @@ +#New analyzer: + +Input your analyzer description. + +Before: + +````csharp +//your code that triggers the diagnostic +```` + +After: + +````csharp +//your code after the fix has been applied +```` + +You can add more information here, e.g. conditions under which a diagnostic should not trigger, etc. + +Diagnostic Id: `CC0000` (take a number and update the [wiki](https://github.com/code-cracker/code-cracker/wiki/DiagnosticIds)) +Category: `` (see [supported categories](https://github.com/code-cracker/code-cracker/blob/master/src/Common/CodeCracker.Common/SupportedCategories.cs) and their [descriptions](https://github.com/code-cracker/code-cracker/issues/97)) +Severity: `Hidden | Info | Warning | Error` (see the [descriptions](https://github.com/code-cracker/code-cracker/#severity-levels)) + +#Bug + +Input your bug description. Make sure you describe the steps to reproduce, +that you are working with the latest version, and the issue has not been reported yet. + +Example: (don't use your project code, use a sample that anyone could use to verify the bug, +so, for example, +don't use classes that are not part of the BCL or declared on your sample.) + +````csharp +//the code that reproduces the bug +```` + +Current output after fix applied (if it is a code fix bug): + +````csharp +//code fixed incorrectly +```` + +Expected output after fix applied (if it is a code fix bug): + +````csharp +//code fixed incorrectly +```` \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..f1fc8739d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,16 @@ +#Info (delete this section) +Please see the [contributing guide](https://github.com/code-cracker/code-cracker/blob/master/README.md#contributing), on the contributing section. + +You should notify the maintainers on the issue you are working on before you send a PR. + +[Rebase](http://gitready.com/intermediate/2009/01/31/intro-to-rebase.html) and +[squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) your commits. +Submit one commit only, on top of the branch you are working on, `master` for bug fixes to the +stable version, `vnext` to new analyzers and fixers. + +Verify if you pass the [definition of done](https://github.com/code-cracker/code-cracker#definition-of-done). + +Always reference issue you are fixing (see bellow, e.g. `Fixes #123`) + +#Start your PR description here (delete this line too) +Fixes # . \ No newline at end of file diff --git a/.gitignore b/.gitignore index 48f64d5cd..b5c6a38ac 100644 --- a/.gitignore +++ b/.gitignore @@ -190,3 +190,6 @@ _Pvt_Extensions/ ModelManifest.xml log/ +.vs +nuget.exe +*.nupkg diff --git a/.nuget/packages.config b/.nuget/packages.config index a571bd134..ac480de76 100644 --- a/.nuget/packages.config +++ b/.nuget/packages.config @@ -1,5 +1,10 @@ - + - - - \ No newline at end of file + + + + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..d44726355 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,506 @@ +# Change Log + +## [v1.1.0](https://github.com/code-cracker/code-cracker/tree/v1.1.0) (2018-05-20) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.3...v1.1.0) + +**Implemented enhancements:** + +- Create a reusable FixAllProvider based on IntroduceFieldFromConstructorCodeFixProviderAll [\#910](https://github.com/code-cracker/code-cracker/issues/910) +- Enable CodeCracker to work with .NET Core [\#871](https://github.com/code-cracker/code-cracker/issues/871) +- CC0068 \(Remove private method\): ShouldSerializeXXX\(\) and ResetXXX\(\) should not trigger the message [\#762](https://github.com/code-cracker/code-cracker/issues/762) +- Create PropertyChangedEventArgs statically [\#42](https://github.com/code-cracker/code-cracker/issues/42) + +- CC0013 Check for applicability as argument [\#522](https://github.com/code-cracker/code-cracker/issues/522) +- Make readonly \(for complex value types\) [\#808](https://github.com/code-cracker/code-cracker/issues/808) +- CC0120: Suggest default for switch statements \(C\#\) [\#780](https://github.com/code-cracker/code-cracker/issues/780) +- Remove Unnecessary ToString in String Concatenation [\#753](https://github.com/code-cracker/code-cracker/issues/753) +- Check consistency of optional parameter default value [\#575](https://github.com/code-cracker/code-cracker/issues/575) +- Make accessibility consistent \(code fix for CS0050 to CS0061\) [\#381](https://github.com/code-cracker/code-cracker/issues/381) +- Prefer "Any" to "Count\(\) \> 0" [\#490](https://github.com/code-cracker/code-cracker/issues/490) +- Prefer "Count" to "Count\(\)" [\#489](https://github.com/code-cracker/code-cracker/issues/489) +- Extract Class to a New File [\#382](https://github.com/code-cracker/code-cracker/issues/382) +- Seal member if possible [\#372](https://github.com/code-cracker/code-cracker/issues/372) +- Remove virtual modifier if possible [\#371](https://github.com/code-cracker/code-cracker/issues/371) +- Remove async and return task directly [\#151](https://github.com/code-cracker/code-cracker/issues/151) +- Change from as operator to direct cast or the opposite [\#65](https://github.com/code-cracker/code-cracker/issues/65) +- Convert loop to linq expression [\#22](https://github.com/code-cracker/code-cracker/issues/22) + +**Fixed bugs:** + +- CC0061 shouldn't pop for async Main [\#958](https://github.com/code-cracker/code-cracker/issues/958) +- Bug: CC0061: Implementing interface using async keyword should not raise a diagnostic [\#936](https://github.com/code-cracker/code-cracker/issues/936) +- Bug CC0031 UseInvokeMethodToFireEventAnalyzer false positive in constructor [\#926](https://github.com/code-cracker/code-cracker/issues/926) +- BUG: CC0014 Casting to interface or implicit casts for the ternary operator are fixed wrong [\#911](https://github.com/code-cracker/code-cracker/issues/911) +- TernaryOperatorWithReturnCodeFixProvider NullReferenceException [\#906](https://github.com/code-cracker/code-cracker/issues/906) +- BUG: CC0022 failed to show fix with null coalesce operator [\#870](https://github.com/code-cracker/code-cracker/issues/870) +- BUG: CC0118 - Unnecessary '.ToString\(\)' call in string concatenation [\#866](https://github.com/code-cracker/code-cracker/issues/866) + +**Closed issues:** + +- Support Hacktoberfest event adding hacktoberfest tag. [\#949](https://github.com/code-cracker/code-cracker/issues/949) +- Using Extension & NuGet package together causes VS2017 to crash [\#944](https://github.com/code-cracker/code-cracker/issues/944) +- False postives and NullReferenceException when using eventhandler in code behind for UWP apps. [\#916](https://github.com/code-cracker/code-cracker/issues/916) +- Replace getter only properties with backing readonly field with getter-only auto-property [\#881](https://github.com/code-cracker/code-cracker/issues/881) + +## [v1.0.3](https://github.com/code-cracker/code-cracker/tree/v1.0.3) (2017-03-20) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.2...v1.0.3) + +**Fixed bugs:** + +- BUG: CC0022 DisposableVariableNotDisposedAnalyzer: False positive for expression bodied members [\#880](https://github.com/code-cracker/code-cracker/issues/880) +- BUG: CC0022 DisposableVariableNotDisposedAnalyzer: False positive in iterator methods [\#877](https://github.com/code-cracker/code-cracker/issues/877) + +## [v1.0.2](https://github.com/code-cracker/code-cracker/tree/v1.0.2) (2017-03-12) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.1...v1.0.2) + +**Implemented enhancements:** + +- VS 2017RC Support [\#856](https://github.com/code-cracker/code-cracker/issues/856) + +**Fixed bugs:** + +- CC0057 UnusedParametersAnalyzer should not be triggered on virtual methods [\#872](https://github.com/code-cracker/code-cracker/issues/872) +- BUG: CC0060 - detected for nested struct in abstract class [\#867](https://github.com/code-cracker/code-cracker/issues/867) +- Bug on CC0120 for the fix when there is a conversion [\#859](https://github.com/code-cracker/code-cracker/issues/859) +- BUG: CC0052 \(Make readonly\) sometimes detects complex value types [\#854](https://github.com/code-cracker/code-cracker/issues/854) +- BUG: CC0052 \(Make readonly\) does not work with lambda expressions and initialized variables [\#853](https://github.com/code-cracker/code-cracker/issues/853) +- "Disposable Field Not Disposed" rule does not recognize null propagation [\#848](https://github.com/code-cracker/code-cracker/issues/848) +- CC0082: ComputeExpressionCodeFixProvider crashs [\#841](https://github.com/code-cracker/code-cracker/issues/841) +- CC0030: Bad grammar in message [\#838](https://github.com/code-cracker/code-cracker/issues/838) +- CC0008: Don't suggest for dynamic objects [\#837](https://github.com/code-cracker/code-cracker/issues/837) +- Bug: Should not use Async methods in analyzers \(CC0029\) [\#821](https://github.com/code-cracker/code-cracker/issues/821) +- BUG: CC0014 converts if y then x += 1 else x =1 to x +=\(if\(y, 1, 1\) [\#798](https://github.com/code-cracker/code-cracker/issues/798) + +## [v1.0.1](https://github.com/code-cracker/code-cracker/tree/v1.0.1) (2016-09-06) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.0...v1.0.1) + +**Implemented enhancements:** + +- developmentDependency not added when used with SonarLint [\#829](https://github.com/code-cracker/code-cracker/issues/829) +- Auto generated files detection [\#773](https://github.com/code-cracker/code-cracker/issues/773) + +**Fixed bugs:** + +- Bug: "UseStaticRegexIsMatchAnalyzer" causes an exception \(CC0081\) [\#822](https://github.com/code-cracker/code-cracker/issues/822) +- CC0006 could break code when changing to foreach [\#814](https://github.com/code-cracker/code-cracker/issues/814) +- BUG: Make readonly \(CC0052\) is incorrectly raised if constructor shows up after member that uses the field [\#812](https://github.com/code-cracker/code-cracker/issues/812) +- Bug: ArgumentNullException on CallExtensionMethodAsExtensionAnalyzer \(CC0026\) [\#810](https://github.com/code-cracker/code-cracker/issues/810) +- BUG: GC.SuppressFinalize with arrow methods \(CC0029\) [\#809](https://github.com/code-cracker/code-cracker/issues/809) +- Bug: CC0039 False positive when concatenating to loop variable propery/field \(StringBuilderInLoop\) [\#797](https://github.com/code-cracker/code-cracker/issues/797) +- Bug: CC0033 appears again after adding 'this' keyword [\#795](https://github.com/code-cracker/code-cracker/issues/795) +- CC0061: Implementing interface using async pattern [\#793](https://github.com/code-cracker/code-cracker/issues/793) +- CC0052 Make field readonly does not take ref out into consideration. [\#788](https://github.com/code-cracker/code-cracker/issues/788) +- BUG: CallExtensionMethodAsExtensionAnalyzer threw exception when project language was C\# 5.0 [\#781](https://github.com/code-cracker/code-cracker/issues/781) +- Bug: UseInvokeMethodToFireEventAnalyzer \(CC0031\) should not raise a diagnostic when already checked for null with a ternary [\#779](https://github.com/code-cracker/code-cracker/issues/779) +- BUG: GC.SuppressFinalize within any block \(CC0029\) [\#776](https://github.com/code-cracker/code-cracker/issues/776) +- BUG: CC0052 \(Make readonly\) should not be applied to complex value types [\#775](https://github.com/code-cracker/code-cracker/issues/775) +- BUG: CC0030 should not try to make a pointer const [\#774](https://github.com/code-cracker/code-cracker/issues/774) + +**Closed issues:** + +- Verify impact of upgrading to Roslyn 1.1 [\#770](https://github.com/code-cracker/code-cracker/issues/770) + +## [v1.0.0](https://github.com/code-cracker/code-cracker/tree/v1.0.0) (2016-04-03) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.0-rc6...v1.0.0) + +**Implemented enhancements:** + +- CC0016 and CC0031 are doing similar work [\#662](https://github.com/code-cracker/code-cracker/issues/662) +- Update XmlDocumentation to raise 2 different diagnostic ids [\#488](https://github.com/code-cracker/code-cracker/issues/488) + +**Fixed bugs:** + +- BUG: using null propagation foils the analysis for dispose object \(CC0022\) [\#761](https://github.com/code-cracker/code-cracker/issues/761) +- CC0011 incorrect when Where predicate uses index parameter [\#752](https://github.com/code-cracker/code-cracker/issues/752) +- BUG: Change to ternary fails with primitive numeric types \(CC0013 and CC0014\) [\#748](https://github.com/code-cracker/code-cracker/issues/748) +- CC0014 Use ternary tries to DirectCast integer to double [\#745](https://github.com/code-cracker/code-cracker/issues/745) +- BUG: CC0068 'Method is not used' should not apply to methods that have the ContractInvariantMethod attribute [\#744](https://github.com/code-cracker/code-cracker/issues/744) +- BUG: CC0057 UnusedParametersAnalyzer should not be triggered with verbatim identifier \(prefixed with @\) [\#741](https://github.com/code-cracker/code-cracker/issues/741) +- Code fix for CC0075 could break the logic of the code [\#740](https://github.com/code-cracker/code-cracker/issues/740) +- BUG: CC0016 "CopyEventToVariableBeforeFireCodeFixProvider" crashes [\#735](https://github.com/code-cracker/code-cracker/issues/735) +- CC0057 UnusedParametersAnalyzer should not be triggered by DllImport [\#733](https://github.com/code-cracker/code-cracker/issues/733) +- BUG: CC0013 \(Make Ternary\) doesn't handle comments correctly [\#725](https://github.com/code-cracker/code-cracker/issues/725) +- BUG: CC0014: You can use a ternary operator turns a+=b into a=a+b [\#724](https://github.com/code-cracker/code-cracker/issues/724) +- Bug in introduce field from constructor [\#721](https://github.com/code-cracker/code-cracker/issues/721) +- BUG: CC0090 \(xmldoc\) is raised on generated files [\#720](https://github.com/code-cracker/code-cracker/issues/720) +- BUG: CC0008 code fix removes too many lines when constructor already has initializer [\#717](https://github.com/code-cracker/code-cracker/issues/717) +- BUG: CC0067 must not fire when parameter Func\ is called in constructor. [\#712](https://github.com/code-cracker/code-cracker/issues/712) +- BUG: CC0033 \(DisposableFieldNotDisposed\) Should ignore static field [\#710](https://github.com/code-cracker/code-cracker/issues/710) +- CC0017 virtual props can create infinite loops [\#702](https://github.com/code-cracker/code-cracker/issues/702) +- CC0057: extern method parameter unused [\#701](https://github.com/code-cracker/code-cracker/issues/701) +- BUG: CC0052 \(readonly\) flags field that is used in variable initializer, which then gives compile error [\#700](https://github.com/code-cracker/code-cracker/issues/700) +- BUG: CC0017 \(create auto property\) removing modifiers \(static, virtual, etc\) [\#699](https://github.com/code-cracker/code-cracker/issues/699) +- BUG: CC0006 \(foreach\) edge case for non-writable struct field [\#698](https://github.com/code-cracker/code-cracker/issues/698) +- BUG: CC00091 Method `GetEnumerator` should never be made static [\#696](https://github.com/code-cracker/code-cracker/issues/696) +- Bug: CC0048 nested interpolations doesn't fix in one iteration [\#690](https://github.com/code-cracker/code-cracker/issues/690) +- BUG: CC0091 WPF event cannot be static [\#639](https://github.com/code-cracker/code-cracker/issues/639) +- DisposableVariableNotDisposedAnalyzer \(CC0022\) should not raise a diagnostic when returning a type that contains the disposable [\#465](https://github.com/code-cracker/code-cracker/issues/465) + +## [v1.0.0-rc6](https://github.com/code-cracker/code-cracker/tree/v1.0.0-rc6) (2016-02-01) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.0-rc5...v1.0.0-rc6) + +**Implemented enhancements:** + +- CC0031 must not fire when Null-check is outside of a try block [\#672](https://github.com/code-cracker/code-cracker/issues/672) +- Add exceptions to CC0068 depending on the method attribute [\#526](https://github.com/code-cracker/code-cracker/issues/526) + +**Fixed bugs:** + +- BUG: CC0048 \(string interpolation\) when string is split into multiple lines [\#689](https://github.com/code-cracker/code-cracker/issues/689) +- CC0017 wrong change with explicitly implemented properties, but can be fixed [\#687](https://github.com/code-cracker/code-cracker/issues/687) +- CC0006 should be checking if for-looped array is enumerable by foreach [\#684](https://github.com/code-cracker/code-cracker/issues/684) +- BUG: CC0006 wrongly tries to convert for into foreach when there would be an assignment to the iteration variable [\#683](https://github.com/code-cracker/code-cracker/issues/683) +- CC0091 MakeMethodStaticCodeFixProvider crashing [\#682](https://github.com/code-cracker/code-cracker/issues/682) +- CC0091 wrongly tries to make static methods in structs [\#681](https://github.com/code-cracker/code-cracker/issues/681) +- BUG: CC0091 \(make static\) changes references incorrectly when used as a method group and in conjunction with another method invocation [\#680](https://github.com/code-cracker/code-cracker/issues/680) +- CC0091 on method Foo does not replace occurences like this.Foo used in delegates [\#677](https://github.com/code-cracker/code-cracker/issues/677) +- ObjectInitializerCodeFixProvider crashes [\#676](https://github.com/code-cracker/code-cracker/issues/676) +- Bug CC0084 should not generate string empty in parameter list in constructor or method [\#669](https://github.com/code-cracker/code-cracker/issues/669) +- CC0022 when theer is ternary operator in using [\#665](https://github.com/code-cracker/code-cracker/issues/665) +- CC0031 should not copy the variable with readonly members [\#663](https://github.com/code-cracker/code-cracker/issues/663) +- BUG on CC0047 \(PropertyPrivateSet\) suggests to make a property set private when the property is already private [\#658](https://github.com/code-cracker/code-cracker/issues/658) +- With more than one constructor, IntroduceFieldFromConstructorCodeFixProvider can replace wrong constructor [\#650](https://github.com/code-cracker/code-cracker/issues/650) +- BUG on CC0057 \(unused parameter\) with named parameters [\#649](https://github.com/code-cracker/code-cracker/issues/649) +- CC0057: Should not try to remove parameters meant to fulfill a delegate contract [\#646](https://github.com/code-cracker/code-cracker/issues/646) +- CC0016 should not copy the variable with readonly members [\#645](https://github.com/code-cracker/code-cracker/issues/645) +- BUG on CC0091 \(MakeMethodStatic\) when a reference to method is used as a method group [\#644](https://github.com/code-cracker/code-cracker/issues/644) +- BUG on CC0043 and CC0092 \(ChangeAnyToAll\) when invocation is negated [\#642](https://github.com/code-cracker/code-cracker/issues/642) +- BUG on CC0043 and CC0092 \(ChangeAnyToAll\) when invocation is in an expression bodied member [\#641](https://github.com/code-cracker/code-cracker/issues/641) +- AD0001 Crash [\#638](https://github.com/code-cracker/code-cracker/issues/638) +- BUG on CC0022 \(disposable not disposed\) fix when there is method chaining [\#630](https://github.com/code-cracker/code-cracker/issues/630) +- New word 'Foramt' in NameOfSymbolDisplayForamt [\#627](https://github.com/code-cracker/code-cracker/issues/627) +- CC0003 is shown even for catches which end with throw [\#626](https://github.com/code-cracker/code-cracker/issues/626) +- CC0001 is shown for variables with type dynamic and object [\#625](https://github.com/code-cracker/code-cracker/issues/625) +- CC0090 when using /// \ [\#624](https://github.com/code-cracker/code-cracker/issues/624) +- Bug: CC0017 not raised when using this [\#623](https://github.com/code-cracker/code-cracker/issues/623) +- CC0052 false positives [\#621](https://github.com/code-cracker/code-cracker/issues/621) +- Bug on CC0029 \(Call GC.SuppressFinalize on dispose\) with expression based methods [\#618](https://github.com/code-cracker/code-cracker/issues/618) +- Removing Option Parameter causes crash [\#617](https://github.com/code-cracker/code-cracker/issues/617) +- BUG on CC0043 and CC0092 \(ChangeAnyToAll\) when invocation has elvis operator [\#613](https://github.com/code-cracker/code-cracker/issues/613) +- When the dispose pattern is used do not raise CC0033 [\#603](https://github.com/code-cracker/code-cracker/issues/603) +- Suggested Nameof fix incorrectly suggests wrong type [\#594](https://github.com/code-cracker/code-cracker/issues/594) + +## [v1.0.0-rc5](https://github.com/code-cracker/code-cracker/tree/v1.0.0-rc5) (2015-12-03) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.0-rc4...v1.0.0-rc5) + +**Implemented enhancements:** + +- CC0091 Know APIs [\#451](https://github.com/code-cracker/code-cracker/issues/451) +- Change CC0001 so that it does not apply to primitives [\#407](https://github.com/code-cracker/code-cracker/issues/407) +- Update all existing code fixes that are doing too much work on RegisterCodeFixesAsync \(VB\) [\#348](https://github.com/code-cracker/code-cracker/issues/348) + +**Fixed bugs:** + +- BUG: UseInvokeMethodToFireEventAnalyzer throwing when method body is null [\#611](https://github.com/code-cracker/code-cracker/issues/611) +- BUG: ReadonlyFieldAnalyzer is crashing Visual Studio 2015 Update 1 because of reporting diagnostic at unexpected locations [\#610](https://github.com/code-cracker/code-cracker/issues/610) +- NoPrivateReadonlyFieldAnalyzer \(0074\) is missing the Generated file check [\#609](https://github.com/code-cracker/code-cracker/issues/609) +- BUG on CC0049 [\#597](https://github.com/code-cracker/code-cracker/issues/597) +- BUG: DisposablesShouldCallSuppressFinalize should use full name when System is not imported [\#590](https://github.com/code-cracker/code-cracker/issues/590) +- Remove empty Catch Block too aggressive [\#587](https://github.com/code-cracker/code-cracker/issues/587) +- BUG: CC0056 \(StringFormatAnalyzer\) raised incorrectly [\#585](https://github.com/code-cracker/code-cracker/issues/585) +- BUG on CC0022 \(disposable not disposed\) when a comment is present [\#577](https://github.com/code-cracker/code-cracker/issues/577) +- BUG on CC0057 \(unused parameter\) with extension methods [\#576](https://github.com/code-cracker/code-cracker/issues/576) +- IP Parsing fails for variables [\#571](https://github.com/code-cracker/code-cracker/issues/571) +- CC0057 mistaken 'Parameter is not used' warning [\#562](https://github.com/code-cracker/code-cracker/issues/562) +- Code fix error in CC0013 TernaryOperatorAnalyzer \(return\) [\#552](https://github.com/code-cracker/code-cracker/issues/552) +- Code fix in CC0014 TernaryOperatorAnalyzer remove comments [\#551](https://github.com/code-cracker/code-cracker/issues/551) +- Code fix error in CC0014 TernaryOperatorAnalyzer \(assignment\) [\#550](https://github.com/code-cracker/code-cracker/issues/550) +- Bug with CC0009 when using the created object in the initialization [\#525](https://github.com/code-cracker/code-cracker/issues/525) +- CC0013 Should check for a common type and cast is necessary. [\#521](https://github.com/code-cracker/code-cracker/issues/521) + +## [v1.0.0-rc4](https://github.com/code-cracker/code-cracker/tree/v1.0.0-rc4) (2015-11-02) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.0-rc3...v1.0.0-rc4) + +**Implemented enhancements:** + +- NameOf Analyzer is too noisy [\#518](https://github.com/code-cracker/code-cracker/issues/518) + +**Fixed bugs:** + +- SwitchToAutoPropCodeFixProvider \(CC0017\) does not keep XML comment trivia [\#548](https://github.com/code-cracker/code-cracker/issues/548) +- CallExtensionMethodAsExtension \(CC0026\) throws NullReferenceException on expression bodied method statement [\#547](https://github.com/code-cracker/code-cracker/issues/547) +- CC0022 DI container delegate causes error [\#545](https://github.com/code-cracker/code-cracker/issues/545) +- BUG on ReadonlyFieldAnalyzer \(CC0052\) with assignment in Func\ in constructor [\#544](https://github.com/code-cracker/code-cracker/issues/544) +- UnusedParametersCodeFixProvider crashing when removing params [\#539](https://github.com/code-cracker/code-cracker/issues/539) +- CC0001 Is raised on multiple variable declarations. [\#537](https://github.com/code-cracker/code-cracker/issues/537) +- Bug: Use ?.Invoke operator and method to fire 'configuration' event, though configuration can't be null [\#536](https://github.com/code-cracker/code-cracker/issues/536) +- UnusedParametersCodeFixProvider fix all not working \(CC0057\) [\#534](https://github.com/code-cracker/code-cracker/issues/534) +- UnusedParametersCodeFixProvider will crash when it is trying to remove ParamArray [\#533](https://github.com/code-cracker/code-cracker/issues/533) +- CC0017 Change to auto property fix all not working [\#514](https://github.com/code-cracker/code-cracker/issues/514) +- BUG on CC0008 and CC0009 \(ObjectInitializer\) when used with collection [\#501](https://github.com/code-cracker/code-cracker/issues/501) +- 'TernaryOperatorWithAddignmentCodeFixProvider' encountered and error [\#496](https://github.com/code-cracker/code-cracker/issues/496) +- CC0009 eats pragmas and trivia [\#493](https://github.com/code-cracker/code-cracker/issues/493) +- CC0013 \(user ternary\) rule should be more careful with nullable types. \(VB\) [\#468](https://github.com/code-cracker/code-cracker/issues/468) + +## [v1.0.0-rc3](https://github.com/code-cracker/code-cracker/tree/v1.0.0-rc3) (2015-10-03) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.0-rc2...v1.0.0-rc3) + +**Implemented enhancements:** + +- CC0013 Should be Information instead of Warning [\#520](https://github.com/code-cracker/code-cracker/issues/520) + +**Fixed bugs:** + +- CC0017 Change to auto property codefix removes multiple variable declaration. [\#512](https://github.com/code-cracker/code-cracker/issues/512) +- CC0017 Change to auto property ignores field assignment. [\#500](https://github.com/code-cracker/code-cracker/issues/500) +- CC047 eats property trivia [\#492](https://github.com/code-cracker/code-cracker/issues/492) +- CC0091 makes static keyword come before trivia [\#486](https://github.com/code-cracker/code-cracker/issues/486) +- Useles suggestion of CC0039 rule [\#485](https://github.com/code-cracker/code-cracker/issues/485) +- CC0013 \(user ternary\) rule should be more careful with nullable types \(C\#\) [\#480](https://github.com/code-cracker/code-cracker/issues/480) +- CC0091 \(make method static\) Reported Incorrectly for explicitly implemented interface [\#479](https://github.com/code-cracker/code-cracker/issues/479) +- CC0068 \(method not used\) Reported Incorrectly for explicitly implemented interface [\#478](https://github.com/code-cracker/code-cracker/issues/478) +- Add additional checks for determining if generated code [\#476](https://github.com/code-cracker/code-cracker/issues/476) +- CC0013 & CC0014 for VB is reported in generated code [\#472](https://github.com/code-cracker/code-cracker/issues/472) +- CC0068 Reported Incorrectly for VB Event Handlers [\#469](https://github.com/code-cracker/code-cracker/issues/469) +- Bug on DisposableVariableNotDisposedAnalyzer \(CC0022\) when returning a disposable [\#466](https://github.com/code-cracker/code-cracker/issues/466) +- Bug on analyzer CC0031 \(UseInvokeMethodToFireEvent\) on parameterized functor [\#397](https://github.com/code-cracker/code-cracker/issues/397) +- BUG: Create code fix for update ReadonlyFieldCodeFixProvider so that the code does not break on public fields [\#293](https://github.com/code-cracker/code-cracker/issues/293) + +## [v1.0.0-rc2](https://github.com/code-cracker/code-cracker/tree/v1.0.0-rc2) (2015-08-19) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.0-rc1...v1.0.0-rc2) + +**Implemented enhancements:** + +- Update VB Allow Members Ordering to work with Modules [\#440](https://github.com/code-cracker/code-cracker/issues/440) +- Provide an equivalence key on all code fix providers [\#417](https://github.com/code-cracker/code-cracker/issues/417) +- Unit test methods raises CC0091 - "Make \ method static" [\#404](https://github.com/code-cracker/code-cracker/issues/404) +- Validate color from System.Drawing.ColorTranslator.FromHtml [\#1](https://github.com/code-cracker/code-cracker/issues/1) + +**Fixed bugs:** + +- Erroneous CC0039 message [\#461](https://github.com/code-cracker/code-cracker/issues/461) +- CC0029 DisposablesShouldCallSuppressFinalizeAnalyzer throws InvalidCastException [\#452](https://github.com/code-cracker/code-cracker/issues/452) +- Doc comments for parameters in wrong order \(XmlDocumentationCreateMissingParametersCodeFixProvider\) [\#437](https://github.com/code-cracker/code-cracker/issues/437) +- CC0022 disposable intantiation on constructor call [\#432](https://github.com/code-cracker/code-cracker/issues/432) +- CC0017 Change to auto property has Incorrect description [\#429](https://github.com/code-cracker/code-cracker/issues/429) +- BUG in CC0061 when the method is a override from a base class [\#424](https://github.com/code-cracker/code-cracker/issues/424) +- CC0021: nameof\(x\) suggested before x is declared \(VB\) [\#420](https://github.com/code-cracker/code-cracker/issues/420) +- Bug: RemoveUnusedVariablesCodeFixProvider is throwing on CatchDeclarations [\#419](https://github.com/code-cracker/code-cracker/issues/419) +- CC064 invalid error [\#418](https://github.com/code-cracker/code-cracker/issues/418) +- BUG: XmlDocumentationCreateMissingParametersCodeFixProvider throws when there are only remarks [\#412](https://github.com/code-cracker/code-cracker/issues/412) +- Bug: DisposableVariableNotDisposedCodeFixProvider throws when object creation is being passed as an argument \(CC0022\) [\#409](https://github.com/code-cracker/code-cracker/issues/409) +- CC0021: nameof\(x\) suggested before x is declared [\#408](https://github.com/code-cracker/code-cracker/issues/408) + +## [v1.0.0-rc1](https://github.com/code-cracker/code-cracker/tree/v1.0.0-rc1) (2015-07-23) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.0-beta1...v1.0.0-rc1) + +**Implemented enhancements:** + +- Verify if xml docs have the correct parameters [\#357](https://github.com/code-cracker/code-cracker/issues/357) + +**Fixed bugs:** + +- BUG: CopyEventToVariableBeforeFireCodeFixProvider throwing [\#411](https://github.com/code-cracker/code-cracker/issues/411) +- Incorrect spacing in string interpolation \(CC0048\) [\#410](https://github.com/code-cracker/code-cracker/issues/410) + +## [v1.0.0-beta1](https://github.com/code-cracker/code-cracker/tree/v1.0.0-beta1) (2015-07-04) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.0-alpha6...v1.0.0-beta1) + +**Implemented enhancements:** + +- Make method non async \(fix for CS1998\) [\#393](https://github.com/code-cracker/code-cracker/issues/393) +- Using the refactoring for CC0008 will result in a warning CC0015 [\#383](https://github.com/code-cracker/code-cracker/issues/383) +- Add string interpolation to Console.WriteLine [\#380](https://github.com/code-cracker/code-cracker/issues/380) +- Make method static if possible [\#364](https://github.com/code-cracker/code-cracker/issues/364) +- Update all existing code fixes that are doing too much work on RegisterCodeFixesAsync \(C\#\) [\#347](https://github.com/code-cracker/code-cracker/issues/347) +- Make accessibility consistent \(code fix for CS0051\) [\#321](https://github.com/code-cracker/code-cracker/issues/321) +- Expand CC0006 \(change for into a foreach\) [\#318](https://github.com/code-cracker/code-cracker/issues/318) +- Convert a "Not Any" query for a condition to a "All" query for the inverted condition [\#31](https://github.com/code-cracker/code-cracker/issues/31) + +**Fixed bugs:** + +- Ignore Disposables created on return statements [\#399](https://github.com/code-cracker/code-cracker/issues/399) +- Null Reference Exception on CodeCracker.CSharp.Usage.VirtualMethodOnConstructorAnalyzer [\#398](https://github.com/code-cracker/code-cracker/issues/398) +- Bug on ObjectInitializerCodeFixProvider when constructor already has initialization [\#396](https://github.com/code-cracker/code-cracker/issues/396) +- Async method can't be used with RegisterXXXXAction. [\#375](https://github.com/code-cracker/code-cracker/issues/375) +- Bug on CC0068 \(RemovePrivateMethodNeverUsed\) with Main [\#368](https://github.com/code-cracker/code-cracker/issues/368) +- Bug in CC0008 when used together with null coalescing operator ?? [\#366](https://github.com/code-cracker/code-cracker/issues/366) +- Bug: nameof analyzer \(CC0021\) being raised on self referencing initialization statement [\#359](https://github.com/code-cracker/code-cracker/issues/359) + +## [v1.0.0-alpha6](https://github.com/code-cracker/code-cracker/tree/v1.0.0-alpha6) (2015-06-01) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.0-alpha5...v1.0.0-alpha6) + +**Implemented enhancements:** + +- Remove redundant else [\#355](https://github.com/code-cracker/code-cracker/issues/355) +- Use "" instead of String.Empty [\#354](https://github.com/code-cracker/code-cracker/issues/354) +- Port changes from CC0021 \(nameof\) to VB [\#341](https://github.com/code-cracker/code-cracker/issues/341) +- Incorrect string.Format usage [\#335](https://github.com/code-cracker/code-cracker/issues/335) +- Expand NameOf Analyzer to work with any identifier in scope [\#263](https://github.com/code-cracker/code-cracker/issues/263) +- Test NameOfCodeFixProvider with keyword [\#198](https://github.com/code-cracker/code-cracker/issues/198) +- Use String.Empty instead "" [\#120](https://github.com/code-cracker/code-cracker/issues/120) +- Use auto property when possible [\#12](https://github.com/code-cracker/code-cracker/issues/12) +- Supress assignment of default value to field/property declarations [\#9](https://github.com/code-cracker/code-cracker/issues/9) + +**Fixed bugs:** + +- Bug on CallExtensionMethodAsExtensionAnalyzer \(CC0026\) throwing InvalidOperationException [\#345](https://github.com/code-cracker/code-cracker/issues/345) +- Bug on CC0068 \(RemovePrivateMethodNeverUsed\) with generic methods [\#343](https://github.com/code-cracker/code-cracker/issues/343) +- BUG: CC0056 Incorrectly classifying format strings [\#333](https://github.com/code-cracker/code-cracker/issues/333) +- CC0056 Incorrectly classifying format strings. [\#330](https://github.com/code-cracker/code-cracker/issues/330) + +## [v1.0.0-alpha5](https://github.com/code-cracker/code-cracker/tree/v1.0.0-alpha5) (2015-04-26) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.0-alpha4...v1.0.0-alpha5) + +**Implemented enhancements:** + +- Create infrastructure for testing multi culture values [\#313](https://github.com/code-cracker/code-cracker/issues/313) +- View lines covered by tests in coverwalls [\#304](https://github.com/code-cracker/code-cracker/issues/304) +- Regex performance: use static Regex.IsMatch [\#297](https://github.com/code-cracker/code-cracker/issues/297) +- Don't run CodeCracker on generated code [\#260](https://github.com/code-cracker/code-cracker/issues/260) +- Virtual method call in constructor [\#203](https://github.com/code-cracker/code-cracker/issues/203) +- Merge nested if statements [\#131](https://github.com/code-cracker/code-cracker/issues/131) +- Split 'if' with '&&' condition into nested 'if'-statements [\#130](https://github.com/code-cracker/code-cracker/issues/130) +- Convert numeric literal from decimal to hex and hex to decimal [\#119](https://github.com/code-cracker/code-cracker/issues/119) +- Compute value of an expression and replaces it whenever it's possible [\#117](https://github.com/code-cracker/code-cracker/issues/117) +- Check arguments in String.Format [\#116](https://github.com/code-cracker/code-cracker/issues/116) +- ArgumentExceptionAnalyzer ignores several code constructs [\#112](https://github.com/code-cracker/code-cracker/issues/112) +- Remove unused variables [\#23](https://github.com/code-cracker/code-cracker/issues/23) + +**Fixed bugs:** + +- Bug in CC0008 analyser for variable access. [\#324](https://github.com/code-cracker/code-cracker/issues/324) +- Bug: CC0022 being raised on using when not assigned [\#319](https://github.com/code-cracker/code-cracker/issues/319) +- Visual studio crash on initializer refactoring \(CC0009\) [\#315](https://github.com/code-cracker/code-cracker/issues/315) +- NameOf produces wrong results/ [\#309](https://github.com/code-cracker/code-cracker/issues/309) +- Bug on Unused parameter \(CC0057\) not checking for assignment [\#291](https://github.com/code-cracker/code-cracker/issues/291) +- BUG: Method not used \(CC0068\) fails to check partial classes [\#290](https://github.com/code-cracker/code-cracker/issues/290) +- Bug on CC0026 \(use extension method\) where selected overload would change [\#262](https://github.com/code-cracker/code-cracker/issues/262) +- Null Reference BUG on ConvertToExpressionBodiedMemberAnalyzer \(CC0038\) [\#192](https://github.com/code-cracker/code-cracker/issues/192) +- CC0029 should not be reported for some types with private constructors [\#110](https://github.com/code-cracker/code-cracker/issues/110) +- CC0029 should not be reported for some sealed types [\#109](https://github.com/code-cracker/code-cracker/issues/109) +- CC0029 should not be reported for structs [\#108](https://github.com/code-cracker/code-cracker/issues/108) +- CC0029 should not be reported for Dispose\(bool\) [\#107](https://github.com/code-cracker/code-cracker/issues/107) +- Fix analysis for CC0029 \(DisposablesShouldCallSuppressFinalizeAnalyzer\) [\#95](https://github.com/code-cracker/code-cracker/issues/95) + +## [v1.0.0-alpha4](https://github.com/code-cracker/code-cracker/tree/v1.0.0-alpha4) (2015-03-04) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.0-alpha3...v1.0.0-alpha4) + +**Implemented enhancements:** + +- Update to Roslyn RC1 and VS 2015 CTP 6 [\#288](https://github.com/code-cracker/code-cracker/issues/288) +- Review AllowMembersOrderingCodeFixProvider.Base to avoid running analyzis on ComputeFixesAsync method [\#273](https://github.com/code-cracker/code-cracker/issues/273) +- Add braces to switch case [\#252](https://github.com/code-cracker/code-cracker/issues/252) +- Unify DiagnosticId class on a separate assembly [\#248](https://github.com/code-cracker/code-cracker/issues/248) +- If method does not return a Task it shouldn't end with "Async" [\#245](https://github.com/code-cracker/code-cracker/issues/245) +- Use enum for diagnostic ids \(VB\) [\#244](https://github.com/code-cracker/code-cracker/issues/244) +- Introduce field from constructor [\#241](https://github.com/code-cracker/code-cracker/issues/241) +- Remove private method is never used in a class [\#204](https://github.com/code-cracker/code-cracker/issues/204) +- Detect read-only not private fields and fix adding the "readonly" modifier [\#177](https://github.com/code-cracker/code-cracker/issues/177) +- Convert Lambda to Method Group whenever it's possible [\#49](https://github.com/code-cracker/code-cracker/issues/49) +- Review Rules ID and Descriptions [\#41](https://github.com/code-cracker/code-cracker/issues/41) +- Remove unreachable code [\#21](https://github.com/code-cracker/code-cracker/issues/21) + +**Fixed bugs:** + +- RemovePrivateMethodNeverUsedAnalyzer \(CC0068\) throwing cast exception [\#276](https://github.com/code-cracker/code-cracker/issues/276) +- Bug on CC0071 \(introduce field\) for fix all [\#267](https://github.com/code-cracker/code-cracker/issues/267) +- BUG on CC0071 \(introduce field\), name clash [\#266](https://github.com/code-cracker/code-cracker/issues/266) +- BUG on CC0021 \(NameOf\) when using attribute [\#258](https://github.com/code-cracker/code-cracker/issues/258) +- CC0031 Formatting bug on comments [\#257](https://github.com/code-cracker/code-cracker/issues/257) +- Bug on CC0065, entire summary removed. [\#256](https://github.com/code-cracker/code-cracker/issues/256) +- CC0022 Batch Fixer does not work in edge cases [\#253](https://github.com/code-cracker/code-cracker/issues/253) +- BUG on CC0057 \(UnusedParameter\) on constructor that passes argument to base [\#251](https://github.com/code-cracker/code-cracker/issues/251) +- Bug in string interpolation \(CC0048\) when Insert uses a ternary operator [\#249](https://github.com/code-cracker/code-cracker/issues/249) + +## [v1.0.0-alpha3](https://github.com/code-cracker/code-cracker/tree/v1.0.0-alpha3) (2015-02-01) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.0-alpha2...v1.0.0-alpha3) + +**Implemented enhancements:** + +- Use ConfigureAwait\(false\) on awaited task [\#235](https://github.com/code-cracker/code-cracker/issues/235) +- CTP5 nuget "The assembly \<...\> does not contain any analyzers" [\#229](https://github.com/code-cracker/code-cracker/issues/229) +- Use enum for diagnostic ids [\#228](https://github.com/code-cracker/code-cracker/issues/228) +- Change verbatim string.format to use string interpolation [\#220](https://github.com/code-cracker/code-cracker/issues/220) +- Update CC to use VS 2015 CTP 5 [\#219](https://github.com/code-cracker/code-cracker/issues/219) +- Build with PSake and kill \(almost\) all scripts [\#215](https://github.com/code-cracker/code-cracker/issues/215) +- Remove trailling whitespace [\#201](https://github.com/code-cracker/code-cracker/issues/201) +- Validate IPAddress.Parse from System.Net [\#188](https://github.com/code-cracker/code-cracker/issues/188) +- Validade Uri from System.Uri [\#182](https://github.com/code-cracker/code-cracker/issues/182) +- Interfaces should start with an "I" [\#179](https://github.com/code-cracker/code-cracker/issues/179) +- If method returns a Task it should have the postfix "Async" [\#178](https://github.com/code-cracker/code-cracker/issues/178) +- Offer a fix for ordering members inside classes and structs following StyleCop patterns [\#172](https://github.com/code-cracker/code-cracker/issues/172) +- Abstract class ctors should not have public constructors [\#164](https://github.com/code-cracker/code-cracker/issues/164) +- Update string interpolation to use new version [\#146](https://github.com/code-cracker/code-cracker/issues/146) +- Allow formating on string interpolation substitutions of string.format [\#145](https://github.com/code-cracker/code-cracker/issues/145) +- Run Analysis on a large OSS project and make sure it does not throw [\#100](https://github.com/code-cracker/code-cracker/issues/100) +- Add code coverage metrics [\#99](https://github.com/code-cracker/code-cracker/issues/99) +- Update all analyzers to use the supported categories [\#97](https://github.com/code-cracker/code-cracker/issues/97) +- Detect read-only private fields and fix adding the "readonly" modifier [\#86](https://github.com/code-cracker/code-cracker/issues/86) +- Offer diagnostic to allow for ordering members inside classes and structs [\#76](https://github.com/code-cracker/code-cracker/issues/76) +- Excess parameters in methods [\#44](https://github.com/code-cracker/code-cracker/issues/44) +- Suggest use of stringbuilder when you have a while loop [\#34](https://github.com/code-cracker/code-cracker/issues/34) +- Private set by default for automatic properties [\#32](https://github.com/code-cracker/code-cracker/issues/32) +- Class that has IDisposable fields should implement IDisposable and dispose those fields [\#30](https://github.com/code-cracker/code-cracker/issues/30) +- IDisposable not assigned to a field is not being disposed [\#28](https://github.com/code-cracker/code-cracker/issues/28) +- Remove unused parameters [\#24](https://github.com/code-cracker/code-cracker/issues/24) +- Validate Json when used with Json.NET [\#2](https://github.com/code-cracker/code-cracker/issues/2) + +**Fixed bugs:** + +- BUG on string interpolation when indexes are inverted [\#246](https://github.com/code-cracker/code-cracker/issues/246) +- BUG on CC0049, simplify boolean comparison, not simmetric [\#238](https://github.com/code-cracker/code-cracker/issues/238) +- False positive on CC0049 \("You can remove this comparison"\) [\#236](https://github.com/code-cracker/code-cracker/issues/236) +- BUG on CC0032/3: DisposableFieldNotDisposedAnalyzer throwing when Dispose is abstract [\#227](https://github.com/code-cracker/code-cracker/issues/227) +- BUG on CC0012: RethrowExceptionAnalyzer throwing "Sequence contains no elements" [\#226](https://github.com/code-cracker/code-cracker/issues/226) +- BUG on CC0026: When there is a dynamic don't raise a diagnostic [\#225](https://github.com/code-cracker/code-cracker/issues/225) +- CC0029 should be reported for explicit IDisposable.Dispose implementation [\#222](https://github.com/code-cracker/code-cracker/issues/222) +- BUG on PrivateSetAnalyzer when there are no accessors [\#217](https://github.com/code-cracker/code-cracker/issues/217) +- BUG on UriAnalyzer when analyzing an expression [\#209](https://github.com/code-cracker/code-cracker/issues/209) +- BUG on ReadonlyFieldAnalyzer \(CC0052\) when analyzing partial classes [\#194](https://github.com/code-cracker/code-cracker/issues/194) +- Null Reference BUG on ForInArrayAnalyzer \(CC0006\) [\#193](https://github.com/code-cracker/code-cracker/issues/193) +- InvalidCastException in CC0016 and CC0031 [\#175](https://github.com/code-cracker/code-cracker/issues/175) +- Bug: CC0030 changes nullable type to const [\#167](https://github.com/code-cracker/code-cracker/issues/167) +- BUG: CC0009 \(from ObjectInitializerAnalyzer\) not being generated [\#165](https://github.com/code-cracker/code-cracker/issues/165) +- BUG on CC0047 \(private set\) should not report diagnostic when the property is being referenced outside the class [\#159](https://github.com/code-cracker/code-cracker/issues/159) + +## [v1.0.0-alpha2](https://github.com/code-cracker/code-cracker/tree/v1.0.0-alpha2) (2014-12-15) +[Full Changelog](https://github.com/code-cracker/code-cracker/compare/v1.0.0-alpha1...v1.0.0-alpha2) + +**Implemented enhancements:** + +- Change verbatim string from refactoring to Hidden diagnostic + code fix [\#161](https://github.com/code-cracker/code-cracker/issues/161) +- Modify CC0037 to make set accessor private when that have code [\#150](https://github.com/code-cracker/code-cracker/issues/150) +- Change from string.Format to string interpolation [\#133](https://github.com/code-cracker/code-cracker/issues/133) +- Simplify redundant Boolean comparisons [\#124](https://github.com/code-cracker/code-cracker/issues/124) +- Convert method body to expression bodied member when applicable [\#101](https://github.com/code-cracker/code-cracker/issues/101) +- Remove commented code [\#98](https://github.com/code-cracker/code-cracker/issues/98) +- Invert loop for 0..n and n..0 [\#87](https://github.com/code-cracker/code-cracker/issues/87) +- Make a variable const whenever it's possible [\#79](https://github.com/code-cracker/code-cracker/issues/79) +- Check null on event to avoid race condition when invoking it [\#61](https://github.com/code-cracker/code-cracker/issues/61) +- Struct vs. Keyword [\#59](https://github.com/code-cracker/code-cracker/issues/59) +- Detect direct event invocation [\#55](https://github.com/code-cracker/code-cracker/issues/55) +- Create a build server and build every push and pull request [\#53](https://github.com/code-cracker/code-cracker/issues/53) +- Suggest nameof when encounter a string with the same name of a parameter [\#40](https://github.com/code-cracker/code-cracker/issues/40) +- Suggest switch if you have 3 or more nested if / else statements [\#39](https://github.com/code-cracker/code-cracker/issues/39) +- Call extension method as an extension [\#27](https://github.com/code-cracker/code-cracker/issues/27) +- Empty Catch block not allowed [\#15](https://github.com/code-cracker/code-cracker/issues/15) +- Remove empty object initializers [\#14](https://github.com/code-cracker/code-cracker/issues/14) +- Use existence ?. operator when possible in expressions [\#13](https://github.com/code-cracker/code-cracker/issues/13) +- Remove unnecessary parenthesis from class initialization [\#11](https://github.com/code-cracker/code-cracker/issues/11) +- Use class initializer where it makes sense [\#10](https://github.com/code-cracker/code-cracker/issues/10) +- Replace if check followed by return true or false [\#8](https://github.com/code-cracker/code-cracker/issues/8) +- On Linq clauses move predicate from Where to First, Single, etc when applicable [\#6](https://github.com/code-cracker/code-cracker/issues/6) +- Always use var [\#4](https://github.com/code-cracker/code-cracker/issues/4) + +**Fixed bugs:** + +- BUG when using "use string interpolation" code fix with string that contains line breaks [\#162](https://github.com/code-cracker/code-cracker/issues/162) +- Bug when running codefix for CC0031 \(UseInvokeMethodToFireEvent\) on statement without block [\#160](https://github.com/code-cracker/code-cracker/issues/160) +- Bug: Private set on props being suggested when there are inheritted members referencing [\#153](https://github.com/code-cracker/code-cracker/issues/153) +- Bug when running codefix for CC0031 \(UseInvokeMethodToFireEvent\) on parameters [\#152](https://github.com/code-cracker/code-cracker/issues/152) +- BUG when make const sees an string interpolation \(CC0030\) [\#140](https://github.com/code-cracker/code-cracker/issues/140) +- NullReferenceException in ArgumentExceptionAnalyzer [\#111](https://github.com/code-cracker/code-cracker/issues/111) +- When changing ifs to switch comments are lost [\#74](https://github.com/code-cracker/code-cracker/issues/74) +- TernaryOperatorAnalyzer throwing NullReferenceException [\#70](https://github.com/code-cracker/code-cracker/issues/70) +- Regex Analyzer/CodeFix tests ignores local culture [\#69](https://github.com/code-cracker/code-cracker/issues/69) + +## [v1.0.0-alpha1](https://github.com/code-cracker/code-cracker/tree/v1.0.0-alpha1) (2014-11-12) +**Implemented enhancements:** + +- Change if to ternary operator [\#7](https://github.com/code-cracker/code-cracker/issues/7) +- Transform for into a foreach [\#3](https://github.com/code-cracker/code-cracker/issues/3) + + + +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file diff --git a/CodeCracker.CSharp.sln b/CodeCracker.CSharp.sln index 9df0d05b1..9090838a1 100644 --- a/CodeCracker.CSharp.sln +++ b/CodeCracker.CSharp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.23103.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{2F5240AD-2B4E-48A4-B9FC-7D19DACEF2CD}" ProjectSection(SolutionItems) = preProject @@ -19,6 +19,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .gitattributes = .gitattributes .gitignore = .gitignore + CHANGELOG.md = CHANGELOG.md README.md = README.md EndProjectSection EndProject @@ -35,19 +36,21 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{40545653 ProjectSection(SolutionItems) = preProject appveyor.yml = appveyor.yml build.ps1 = build.ps1 - build.targets.ps1 = build.targets.ps1 - psake.ps1 = psake.ps1 + default.ps1 = default.ps1 EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCracker.Common", "src\Common\CodeCracker.Common\CodeCracker.Common.csproj", "{753D4757-FCBA-43BA-B1BE-89201ACDA192}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCracker.Test.Common", "test\Common\CodeCracker.Test.Common\CodeCracker.Test.Common.csproj", "{1CD1A3EE-28CE-404B-A59E-AEACF762D938}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCracker.Vsix.Debug", "src\CSharp\CodeCracker.Vsix\CodeCracker.Vsix.Debug.csproj", "{E3B0C133-B97E-46B9-809D-16BB762EF74F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU DebugNoVsix|Any CPU = DebugNoVsix|Any CPU Release|Any CPU = Release|Any CPU + ReleaseNoVsix|Any CPU = ReleaseNoVsix|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {FF1097FB-A890-461B-979E-064697891B96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -56,29 +59,42 @@ Global {FF1097FB-A890-461B-979E-064697891B96}.DebugNoVsix|Any CPU.Build.0 = Debug|Any CPU {FF1097FB-A890-461B-979E-064697891B96}.Release|Any CPU.ActiveCfg = Release|Any CPU {FF1097FB-A890-461B-979E-064697891B96}.Release|Any CPU.Build.0 = Release|Any CPU + {FF1097FB-A890-461B-979E-064697891B96}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {FF1097FB-A890-461B-979E-064697891B96}.ReleaseNoVsix|Any CPU.Build.0 = Release|Any CPU {6BAC4057-7239-485E-A04B-02E687A83BAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6BAC4057-7239-485E-A04B-02E687A83BAA}.Debug|Any CPU.Build.0 = Debug|Any CPU {6BAC4057-7239-485E-A04B-02E687A83BAA}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {6BAC4057-7239-485E-A04B-02E687A83BAA}.Release|Any CPU.ActiveCfg = Release|Any CPU {6BAC4057-7239-485E-A04B-02E687A83BAA}.Release|Any CPU.Build.0 = Release|Any CPU + {6BAC4057-7239-485E-A04B-02E687A83BAA}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU {F7843158-046E-4B22-95D7-CAC7BB01283D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F7843158-046E-4B22-95D7-CAC7BB01283D}.Debug|Any CPU.Build.0 = Debug|Any CPU {F7843158-046E-4B22-95D7-CAC7BB01283D}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {F7843158-046E-4B22-95D7-CAC7BB01283D}.DebugNoVsix|Any CPU.Build.0 = Debug|Any CPU {F7843158-046E-4B22-95D7-CAC7BB01283D}.Release|Any CPU.ActiveCfg = Release|Any CPU {F7843158-046E-4B22-95D7-CAC7BB01283D}.Release|Any CPU.Build.0 = Release|Any CPU + {F7843158-046E-4B22-95D7-CAC7BB01283D}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {F7843158-046E-4B22-95D7-CAC7BB01283D}.ReleaseNoVsix|Any CPU.Build.0 = Release|Any CPU {753D4757-FCBA-43BA-B1BE-89201ACDA192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {753D4757-FCBA-43BA-B1BE-89201ACDA192}.Debug|Any CPU.Build.0 = Debug|Any CPU {753D4757-FCBA-43BA-B1BE-89201ACDA192}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {753D4757-FCBA-43BA-B1BE-89201ACDA192}.DebugNoVsix|Any CPU.Build.0 = Debug|Any CPU {753D4757-FCBA-43BA-B1BE-89201ACDA192}.Release|Any CPU.ActiveCfg = Release|Any CPU {753D4757-FCBA-43BA-B1BE-89201ACDA192}.Release|Any CPU.Build.0 = Release|Any CPU + {753D4757-FCBA-43BA-B1BE-89201ACDA192}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {753D4757-FCBA-43BA-B1BE-89201ACDA192}.ReleaseNoVsix|Any CPU.Build.0 = Release|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.Debug|Any CPU.Build.0 = Debug|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.DebugNoVsix|Any CPU.Build.0 = Debug|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.Release|Any CPU.ActiveCfg = Release|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.Release|Any CPU.Build.0 = Release|Any CPU + {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.ReleaseNoVsix|Any CPU.Build.0 = Release|Any CPU + {E3B0C133-B97E-46B9-809D-16BB762EF74F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E3B0C133-B97E-46B9-809D-16BB762EF74F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E3B0C133-B97E-46B9-809D-16BB762EF74F}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU + {E3B0C133-B97E-46B9-809D-16BB762EF74F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E3B0C133-B97E-46B9-809D-16BB762EF74F}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -87,4 +103,7 @@ Global {051F1BE2-9A44-4B84-9DF8-6537852B7BBC} = {7B4F0131-D598-4692-9E2C-111E6C42C6AD} {40545653-8444-49E0-8DAD-BBB381F8A3B2} = {7B4F0131-D598-4692-9E2C-111E6C42C6AD} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {80743E34-82D0-4E0A-9514-0C85FF873E14} + EndGlobalSection EndGlobal diff --git a/CodeCracker.VisualBasic.sln b/CodeCracker.VisualBasic.sln index 049aa9a2c..e4feebe4e 100644 --- a/CodeCracker.VisualBasic.sln +++ b/CodeCracker.VisualBasic.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.22609.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "CodeCracker", "src\VisualBasic\CodeCracker\CodeCracker.vbproj", "{41FA4971-D354-4647-A269-4A886DA2EF4C}" EndProject @@ -24,8 +24,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{D1591C8E ProjectSection(SolutionItems) = preProject appveyor.yml = appveyor.yml build.ps1 = build.ps1 - build.targets.ps1 = build.targets.ps1 - psake.ps1 = psake.ps1 + default.ps1 = default.ps1 EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{22D6C608-E7F1-4236-BB07-BE5F97A4C810}" @@ -40,11 +39,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCracker.Test.Common", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCracker.Vsix", "src\VisualBasic\CodeCracker.Vsix\CodeCracker.Vsix.csproj", "{B7B513B4-0317-4F32-B560-4BFC4FAEC239}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCracker.Vsix.Debug", "src\VisualBasic\CodeCracker.Vsix\CodeCracker.Vsix.Debug.csproj", "{7F08D429-91E1-4B47-B3B2-A98754C8DFA7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU DebugNoVsix|Any CPU = DebugNoVsix|Any CPU Release|Any CPU = Release|Any CPU + ReleaseNoVsix|Any CPU = ReleaseNoVsix|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {41FA4971-D354-4647-A269-4A886DA2EF4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -53,29 +55,42 @@ Global {41FA4971-D354-4647-A269-4A886DA2EF4C}.DebugNoVsix|Any CPU.Build.0 = Debug|Any CPU {41FA4971-D354-4647-A269-4A886DA2EF4C}.Release|Any CPU.ActiveCfg = Release|Any CPU {41FA4971-D354-4647-A269-4A886DA2EF4C}.Release|Any CPU.Build.0 = Release|Any CPU + {41FA4971-D354-4647-A269-4A886DA2EF4C}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {41FA4971-D354-4647-A269-4A886DA2EF4C}.ReleaseNoVsix|Any CPU.Build.0 = Release|Any CPU {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.Debug|Any CPU.Build.0 = Debug|Any CPU {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.DebugNoVsix|Any CPU.Build.0 = Debug|Any CPU {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.Release|Any CPU.ActiveCfg = Release|Any CPU {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.Release|Any CPU.Build.0 = Release|Any CPU + {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.ReleaseNoVsix|Any CPU.Build.0 = Release|Any CPU {753D4757-FCBA-43BA-B1BE-89201ACDA192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {753D4757-FCBA-43BA-B1BE-89201ACDA192}.Debug|Any CPU.Build.0 = Debug|Any CPU {753D4757-FCBA-43BA-B1BE-89201ACDA192}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {753D4757-FCBA-43BA-B1BE-89201ACDA192}.DebugNoVsix|Any CPU.Build.0 = Debug|Any CPU {753D4757-FCBA-43BA-B1BE-89201ACDA192}.Release|Any CPU.ActiveCfg = Release|Any CPU {753D4757-FCBA-43BA-B1BE-89201ACDA192}.Release|Any CPU.Build.0 = Release|Any CPU + {753D4757-FCBA-43BA-B1BE-89201ACDA192}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {753D4757-FCBA-43BA-B1BE-89201ACDA192}.ReleaseNoVsix|Any CPU.Build.0 = Release|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.Debug|Any CPU.Build.0 = Debug|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.DebugNoVsix|Any CPU.Build.0 = Debug|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.Release|Any CPU.ActiveCfg = Release|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.Release|Any CPU.Build.0 = Release|Any CPU + {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.ReleaseNoVsix|Any CPU.Build.0 = Release|Any CPU {B7B513B4-0317-4F32-B560-4BFC4FAEC239}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B7B513B4-0317-4F32-B560-4BFC4FAEC239}.Debug|Any CPU.Build.0 = Debug|Any CPU {B7B513B4-0317-4F32-B560-4BFC4FAEC239}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {B7B513B4-0317-4F32-B560-4BFC4FAEC239}.Release|Any CPU.ActiveCfg = Release|Any CPU {B7B513B4-0317-4F32-B560-4BFC4FAEC239}.Release|Any CPU.Build.0 = Release|Any CPU + {B7B513B4-0317-4F32-B560-4BFC4FAEC239}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {7F08D429-91E1-4B47-B3B2-A98754C8DFA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F08D429-91E1-4B47-B3B2-A98754C8DFA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F08D429-91E1-4B47-B3B2-A98754C8DFA7}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU + {7F08D429-91E1-4B47-B3B2-A98754C8DFA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F08D429-91E1-4B47-B3B2-A98754C8DFA7}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -84,4 +99,7 @@ Global {D1591C8E-982D-402F-B3CB-1D1662104425} = {3DB077DC-387D-4AAD-9ECE-96D3D24FB3A6} {22D6C608-E7F1-4236-BB07-BE5F97A4C810} = {3DB077DC-387D-4AAD-9ECE-96D3D24FB3A6} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0D75A128-4A6D-4847-9699-93EF828F5B40} + EndGlobalSection EndGlobal diff --git a/CodeCracker.sln b/CodeCracker.sln index b0eef3877..735aa087f 100644 --- a/CodeCracker.sln +++ b/CodeCracker.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.22609.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCracker.Common", "src\Common\CodeCracker.Common\CodeCracker.Common.csproj", "{753D4757-FCBA-43BA-B1BE-89201ACDA192}" EndProject @@ -34,6 +34,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Solution Items", ".Solutio ProjectSection(SolutionItems) = preProject .gitattributes = .gitattributes .gitignore = .gitignore + CHANGELOG.md = CHANGELOG.md README.md = README.md EndProjectSection EndProject @@ -41,8 +42,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{234973E7 ProjectSection(SolutionItems) = preProject appveyor.yml = appveyor.yml build.ps1 = build.ps1 - build.targets.ps1 = build.targets.ps1 - psake.ps1 = psake.ps1 + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + psakefile.ps1 = psakefile.ps1 EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C5584F20-6E93-4D2D-B6F0-141B977AFC9F}" @@ -55,11 +57,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C5584F20-6 test.ps1 = test.ps1 EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCracker.Vsix.Debug", "src\VisualBasic\CodeCracker.Vsix\CodeCracker.Vsix.Debug.csproj", "{7F08D429-91E1-4B47-B3B2-A98754C8DFA7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCracker.Vsix.Debug", "src\CSharp\CodeCracker.Vsix\CodeCracker.Vsix.Debug.csproj", "{E3B0C133-B97E-46B9-809D-16BB762EF74F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU DebugNoVsix|Any CPU = DebugNoVsix|Any CPU Release|Any CPU = Release|Any CPU + ReleaseNoVsix|Any CPU = ReleaseNoVsix|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {753D4757-FCBA-43BA-B1BE-89201ACDA192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -68,46 +75,68 @@ Global {753D4757-FCBA-43BA-B1BE-89201ACDA192}.DebugNoVsix|Any CPU.Build.0 = Debug|Any CPU {753D4757-FCBA-43BA-B1BE-89201ACDA192}.Release|Any CPU.ActiveCfg = Release|Any CPU {753D4757-FCBA-43BA-B1BE-89201ACDA192}.Release|Any CPU.Build.0 = Release|Any CPU + {753D4757-FCBA-43BA-B1BE-89201ACDA192}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {753D4757-FCBA-43BA-B1BE-89201ACDA192}.ReleaseNoVsix|Any CPU.Build.0 = Release|Any CPU {FF1097FB-A890-461B-979E-064697891B96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FF1097FB-A890-461B-979E-064697891B96}.Debug|Any CPU.Build.0 = Debug|Any CPU {FF1097FB-A890-461B-979E-064697891B96}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {FF1097FB-A890-461B-979E-064697891B96}.DebugNoVsix|Any CPU.Build.0 = Debug|Any CPU {FF1097FB-A890-461B-979E-064697891B96}.Release|Any CPU.ActiveCfg = Release|Any CPU {FF1097FB-A890-461B-979E-064697891B96}.Release|Any CPU.Build.0 = Release|Any CPU + {FF1097FB-A890-461B-979E-064697891B96}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {FF1097FB-A890-461B-979E-064697891B96}.ReleaseNoVsix|Any CPU.Build.0 = Release|Any CPU {6BAC4057-7239-485E-A04B-02E687A83BAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6BAC4057-7239-485E-A04B-02E687A83BAA}.Debug|Any CPU.Build.0 = Debug|Any CPU {6BAC4057-7239-485E-A04B-02E687A83BAA}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {6BAC4057-7239-485E-A04B-02E687A83BAA}.Release|Any CPU.ActiveCfg = Release|Any CPU {6BAC4057-7239-485E-A04B-02E687A83BAA}.Release|Any CPU.Build.0 = Release|Any CPU + {6BAC4057-7239-485E-A04B-02E687A83BAA}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU {F7843158-046E-4B22-95D7-CAC7BB01283D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F7843158-046E-4B22-95D7-CAC7BB01283D}.Debug|Any CPU.Build.0 = Debug|Any CPU {F7843158-046E-4B22-95D7-CAC7BB01283D}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {F7843158-046E-4B22-95D7-CAC7BB01283D}.DebugNoVsix|Any CPU.Build.0 = Debug|Any CPU {F7843158-046E-4B22-95D7-CAC7BB01283D}.Release|Any CPU.ActiveCfg = Release|Any CPU {F7843158-046E-4B22-95D7-CAC7BB01283D}.Release|Any CPU.Build.0 = Release|Any CPU + {F7843158-046E-4B22-95D7-CAC7BB01283D}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {F7843158-046E-4B22-95D7-CAC7BB01283D}.ReleaseNoVsix|Any CPU.Build.0 = Release|Any CPU {41FA4971-D354-4647-A269-4A886DA2EF4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {41FA4971-D354-4647-A269-4A886DA2EF4C}.Debug|Any CPU.Build.0 = Debug|Any CPU {41FA4971-D354-4647-A269-4A886DA2EF4C}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {41FA4971-D354-4647-A269-4A886DA2EF4C}.DebugNoVsix|Any CPU.Build.0 = Debug|Any CPU {41FA4971-D354-4647-A269-4A886DA2EF4C}.Release|Any CPU.ActiveCfg = Release|Any CPU {41FA4971-D354-4647-A269-4A886DA2EF4C}.Release|Any CPU.Build.0 = Release|Any CPU + {41FA4971-D354-4647-A269-4A886DA2EF4C}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {41FA4971-D354-4647-A269-4A886DA2EF4C}.ReleaseNoVsix|Any CPU.Build.0 = Release|Any CPU {B7B513B4-0317-4F32-B560-4BFC4FAEC239}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B7B513B4-0317-4F32-B560-4BFC4FAEC239}.Debug|Any CPU.Build.0 = Debug|Any CPU {B7B513B4-0317-4F32-B560-4BFC4FAEC239}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {B7B513B4-0317-4F32-B560-4BFC4FAEC239}.Release|Any CPU.ActiveCfg = Release|Any CPU {B7B513B4-0317-4F32-B560-4BFC4FAEC239}.Release|Any CPU.Build.0 = Release|Any CPU + {B7B513B4-0317-4F32-B560-4BFC4FAEC239}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.Debug|Any CPU.Build.0 = Debug|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.DebugNoVsix|Any CPU.Build.0 = Debug|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.Release|Any CPU.ActiveCfg = Release|Any CPU {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.Release|Any CPU.Build.0 = Release|Any CPU + {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {1CD1A3EE-28CE-404B-A59E-AEACF762D938}.ReleaseNoVsix|Any CPU.Build.0 = Release|Any CPU {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.Debug|Any CPU.Build.0 = Debug|Any CPU {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.DebugNoVsix|Any CPU.Build.0 = Debug|Any CPU {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.Release|Any CPU.ActiveCfg = Release|Any CPU {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.Release|Any CPU.Build.0 = Release|Any CPU + {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D}.ReleaseNoVsix|Any CPU.Build.0 = Release|Any CPU + {7F08D429-91E1-4B47-B3B2-A98754C8DFA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F08D429-91E1-4B47-B3B2-A98754C8DFA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F08D429-91E1-4B47-B3B2-A98754C8DFA7}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU + {7F08D429-91E1-4B47-B3B2-A98754C8DFA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F08D429-91E1-4B47-B3B2-A98754C8DFA7}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU + {E3B0C133-B97E-46B9-809D-16BB762EF74F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E3B0C133-B97E-46B9-809D-16BB762EF74F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E3B0C133-B97E-46B9-809D-16BB762EF74F}.DebugNoVsix|Any CPU.ActiveCfg = Debug|Any CPU + {E3B0C133-B97E-46B9-809D-16BB762EF74F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E3B0C133-B97E-46B9-809D-16BB762EF74F}.ReleaseNoVsix|Any CPU.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -121,5 +150,10 @@ Global {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D} = {11473308-7FD9-43CE-84CE-5912EEFDD1DC} {234973E7-794D-4BC5-8D5F-FB98D8BFA967} = {E1B8ADBF-3442-4EF3-8C6B-146B576340E9} {C5584F20-6E93-4D2D-B6F0-141B977AFC9F} = {E1B8ADBF-3442-4EF3-8C6B-146B576340E9} + {7F08D429-91E1-4B47-B3B2-A98754C8DFA7} = {11473308-7FD9-43CE-84CE-5912EEFDD1DC} + {E3B0C133-B97E-46B9-809D-16BB762EF74F} = {90D62FF0-A374-4C14-B827-1FFA8E384E18} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EF50C6AB-1421-41AE-8CB3-927AB24A69EA} EndGlobalSection EndGlobal diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..98ce8f2aa --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + PackageReference + + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 000000000..341027f3c --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,3 @@ + + + diff --git a/Documentation b/Documentation new file mode 100644 index 000000000..bb9c50c19 --- /dev/null +++ b/Documentation @@ -0,0 +1,40 @@ +Code Cracker + +An analyzer library for C# and VB that uses Roslyn to produce refactorings, code analysis, and other niceties. + +Check the official project site on code-cracker.github.io. There you will find information on how to contribute, our task board, definition of done, definition of ready, etc. + +Build status Nuget count License Issues open Coverage Status Source Browser + +This is a community project, free and open source. Everyone is invited to contribute, fork, share and use the code. No money shall be charged by this software, nor it will be. Ever. + +Installing + +You may use CodeCracker in two ways: as an analyzer library that you install with Nuget into your project or as a Visual Studio extension. The way you want to use it depends on the scenario you are working on. You most likely want the Nuget package. + +If you want the analyzers to work during your build, and generate warnings and errors during the build, also on build servers, then you want to use the Nuget package. The package is available on nuget (C#, VB). If you want to be able to configure which analyzers are being used in your project, and which ones you will ignore, and commit those changes to source control and share with your team, then you also want the Nuget package. + +To install from Nuget, for the C# version: + +Install-Package CodeCracker.CSharp +Or for the Visual Basic version: + +Install-Package CodeCracker.VisualBasic +Or use the Package Manager in Visual Studio. + +There is also a version for both named CodeCracker only, but it makes not sense to get it, you should search for the C# or VB version. + +If you want the alpha builds that build on each push to the repo, add https://www.myget.org/F/codecrackerbuild/ to your nuget feed. We only push complete releases to Nuget.org, and commit builds go to Myget.org. + +If you want global analyzers that will work on every project you open in Visual Studio, then you want the Extension. Grab the extension at the Visual Studio Extensions Gallery (C#, VB). + +To build from source: + +git clone https://github.com/code-cracker/code-cracker.git +cd CodeCracker +msbuild +Then add a reference to CodeCracker.dll from within the Analyzers node inside References, in Visual Studio. + +SonarQube Plugin + +CodeCracker has a SonarQube Plugin that can downloaded at Plugins HomePage. diff --git a/Hacktoberfest b/Hacktoberfest new file mode 100644 index 000000000..28c43036f --- /dev/null +++ b/Hacktoberfest @@ -0,0 +1,26 @@ +Hacktoberfest Sign In Sheet 2017! + +The goal of this repo is to help beginners who are doing their first pull requests. Feel free to join! + +Instruction + +In the index file, look for the 'ol' tag. Then insert a 'li' tag with your link to your profile. + +Git and Pull Request Resources + +Github +The Net Ninja +Awesome-Git +How to Create a Pull Request + +Click on the fork on the top to fork this repo. +Go to your repo where you forked the project. +Hit the clone button on your forked repo and copy the given link. +On your terminal / command prompt, type "git clone [put the link here]". +Change the index file in the folder. +Afterward, on your terminal / command prompt, type "git add index.html"; then 'git commit -m "[type a message]" '. +Create a remote to link the repository on github to your local workspace. use "git remote add [remote-name] [put the github link here]" +Push the commit. For example, type "git push [remote-name] master". +Go back to the original repo. +Hit "new pull request" and compare between forks. +Confirm the pull request and that's it!!! diff --git a/README.md b/README.md index 2101e79dd..d30381db7 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,68 @@ # Code Cracker -An analyzer library for C# and VB that uses [Roslyn](http://msdn.microsoft.com/en-us/vstudio/roslyn.aspx) to produce refactorings, code analysis, and other niceties. +An analyzer library for C# and VB that uses [Roslyn](https://github.com/dotnet/roslyn) to produce refactorings, code analysis, and other niceties. Check the official project site on [code-cracker.github.io](http://code-cracker.github.io). There you will find information on how to contribute, our task board, definition of done, definition of ready, etc. [![Build status](https://ci.appveyor.com/api/projects/status/h21sli3jkumuswyi?svg=true)](https://ci.appveyor.com/project/code-cracker/code-cracker) -[![Nuget count](http://img.shields.io/nuget/v/codecracker.svg)](https://www.nuget.org/packages/codecracker/) -[![Nuget downloads](http://img.shields.io/nuget/dt/codecracker.svg)](https://www.nuget.org/packages/codecracker/) -[![Issues open](http://img.shields.io/github/issues-raw/code-cracker/code-cracker.svg)](https://huboard.com/code-cracker/code-cracker/) +[![Nuget count](https://img.shields.io/nuget/v/codecracker.CSharp.svg)](https://www.nuget.org/packages/codecracker.CSharp/) +[![License](https://img.shields.io/github/license/code-cracker/code-cracker.svg)](https://github.com/code-cracker/code-cracker/blob/master/LICENSE.txt) +[![Issues open](https://img.shields.io/github/issues-raw/code-cracker/code-cracker.svg)](https://huboard.com/code-cracker/code-cracker/) [![Coverage Status](https://img.shields.io/coveralls/code-cracker/code-cracker/master.svg)](https://coveralls.io/r/code-cracker/code-cracker?branch=master) -[![Source Browser](https://img.shields.io/badge/Browse-Source-green.svg)](http://sourcebrowser.io/Browse/code-cracker/code-cracker) +[![Source Browser](https://img.shields.io/badge/Browse-Source-green.svg)](http://ccref.azurewebsites.net) -This is a community project, free and open source. Everyone is invited to contribute, fork, share and use the code. No money shall be charged by this +You can find this document in the following languages + +[![English](https://img.shields.io/badge/language-english-blue.svg)](https://github.com/code-cracker/code-cracker/blob/master/README.md) +[![Brazilian Portuguese](https://img.shields.io/badge/language-brazilan%20portuguese-brightgreen.svg)](https://github.com/code-cracker/code-cracker/blob/master/README.pt.md) + + +This is a community project, free and open source. Everyone is invited to contribute, fork, share and use the code. No money shall be charged by this software, nor it will be. Ever. +## Features + +The list of features is documented here: http://code-cracker.github.io/diagnostics.html + +#### Design +Code | Analyzer | Severity | Description +-- | -- | -- | -- +[CC0003](http://code-cracker.github.io/diagnostics/CC0003.html) | CatchEmptyAnalyzer | Warning | Catch statements with no Exception as an argument is not recommended. Consider adding an Exception class to the catch statement. +[CC0004](http://code-cracker.github.io/diagnostics/CC0004.html) | EmptyCatchBlockAnalyzer | Warning | An empty catch block suppress all errors and shouldn’t be used. If the error is expected consider logging it or changing the control flow such that it is explicit. +[CC0016](http://code-cracker.github.io/diagnostics/CC0016.html) | CopyEventToVariableBeforeFireAnalyzer | Warning | Events should always be checked for null before being invoked. As in a multi-threading context, it is possible for an event to be unsubscribed between the moment where it is checked to be non-null and the moment it is raised, the event must be copied to a temporary variable before the check. +[CC0021](http://code-cracker.github.io/diagnostics/CC0021.html) | NameOfAnalyzer | Warning | In C#6 the nameof() operator should be used to specify the name of a program element instead of a string literal as it produces code that is easier to refactor. +[CC0024](http://code-cracker.github.io/diagnostics/CC0024.html) | StaticConstructorExceptionAnalyzer | Warning | Static constructor are called before the first time a class is used but the caller doesn’t control when exactly. Exceptions thrown in this context force callers to use ‘try’ block around any usage of the class and should be avoided. +[CC0031](http://code-cracker.github.io/diagnostics/CC0031.html) | UseInvokeMethodToFireEventAnalyzer | Warning | In C#6 a delegate can be invoked using the null-propagating operator (?.) and its invoke method to avoid throwing a NullReference exception when there is no method attached to the delegate. + ## Installing -You may use CodeCracker in two ways: as an analyzer library that you install with Nuget into your project or as a Visual Studio extension. +You may use CodeCracker in two ways: as an analyzer library that you install with NuGet into your project, or as a Visual Studio extension. The way you want to use it depends on the scenario you are working on. You most likely want the Nuget package. If you want the analyzers to work during your build, and generate warnings and errors during the build, also on build servers, then you want -to use the Nuget package. The package is available on nuget ([C#](https://www.nuget.org/packages/codecracker.CSharp), +to use the Nuget package. The package is available on NuGet ([C#](https://www.nuget.org/packages/codecracker.CSharp), [VB](https://www.nuget.org/packages/codecracker.VisualBasic)). If you want to be able to configure which analyzers are being used in your project, and which ones you will ignore, and commit those changes to source control and share with your team, then you also want the Nuget package. -To install from Nuget, for the C# version: +To install from NuGet, for the C# version: ```powershell -Install-Package CodeCracker.CSharp -Pre +Install-Package CodeCracker.CSharp ``` Or for the Visual Basic version: ```powershell -Install-Package CodeCracker.VisualBasic -Pre +Install-Package CodeCracker.VisualBasic ``` Or use the Package Manager in Visual Studio. -There is also a version for both named `CodeCracker` only, but it makes not sense to get it, you should search for the C# or VB version. +There is also a version for both named `CodeCracker` only, but it makes no sense to get it, you should search for the C# or VB version. -If you want the alpha builds that build on each push to the repo, add https://www.myget.org/F/codecrackerbuild/ to your nuget feed. +If you want the alpha builds that build on each push to the repo, add [this](https://www.myget.org/F/codecrackerbuild/) to your NuGet feed. We only push complete releases to Nuget.org, and commit builds go to Myget.org. If you want global analyzers that will work on every project you open in Visual Studio, then you want the Extension. @@ -59,16 +79,26 @@ msbuild Then add a reference to CodeCracker.dll from within the Analyzers node inside References, in Visual Studio. -## Contributing +TL;DR: +If you want to use CodeCracker in all your projects, install the Visual Studio extension ([C#](https://visualstudiogallery.msdn.microsoft.com/ab588981-91a5-478c-8e65-74d0ff450862), [VB](https://visualstudiogallery.msdn.microsoft.com/1a5f9551-e831-4812-abd0-ac48603fc2c1)). If you want to use CodeCracker for just one project, install the Nuget package as described above. + +## SonarQube Plugin + +CodeCracker has a SonarQube Plugin that can be downloaded at [Plugins HomePage](http://docs.sonarqube.org/display/PLUG/Other+Plugins). + +## Contributing [![Open Source Helpers](https://www.codetriage.com/code-cracker/code-cracker/badges/users.svg)](https://www.codetriage.com/code-cracker/code-cracker) + +The main supported IDE for development is Visual Studio 2017. +We do not support VS 2015 anymore. Questions, comments, bug reports, and pull requests are all welcome. -Bug reports that include steps-to-reproduce (including code) are +Bug reports that include steps to reproduce (including code) are preferred. Even better, make them in the form of pull requests. Before you start to work on an existing issue, check if it is not assigned to anyone yet, and if it is, talk to that person. -Also check the project [board](https://huboard.com/code-cracker/code-cracker/) +Also, check the project [board](https://huboard.com/code-cracker/code-cracker/) and verify it is not being worked on (it will be tagged with the `Working` tag). -If it is not being worked on, before you start check if the item is `Ready`. +If it is not being worked on, check if the item is `Ready` before you start. If the issue has the `Working` tag (working swimlane on Huboard) and has no Assignee then it is not being worked on by somebody on the core team. Check the issue's description to find out who it is (if it is not there it has to be on the comments). @@ -78,7 +108,7 @@ be assigned to this team. The easiest way to start is looking into the issues that are [up for grabs](https://github.com/code-cracker/code-cracker/labels/up-for-grabs). You -may ask to work on any of them, read below to see how. +may ask to work on any of them, read below to see how. You can also triage issues which may include reproducing bug reports or asking for vital information such as version numbers or reproduction instructions. If you would like to start triaging issues, one easy way to get started is to [subscribe to code-cracker on CodeTriage](https://www.codetriage.com/code-cracker/code-cracker). If you are just starting with Roslyn, want to contribute, and feel you are not yet ready to start working on full analyzers or code fixes, you can start helping with areas that are @@ -86,8 +116,7 @@ less demanding. We have identified a few: * Fixing bugs - Still demands knowledge of Roslyn internals but it is easier than coming up with a full - analyzer or code fix. Look for the [bugs that are up for grabs](https://github.com/code-cracker/code-cracker/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3Abug+label%3Aup-for-grabs). + Still demands knowledge of Roslyn internals but it is easier than coming up with a full analyzer or code fix. Look for the [bugs that are up for grabs](https://github.com/code-cracker/code-cracker/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3Abug+label%3Aup-for-grabs). * Documentation @@ -96,10 +125,8 @@ less demanding. We have identified a few: * Localization/Translation - We are starting to translate the analyzers and code fixes messages to other languages. If you want CodeCracker - on your language feel free to create an issue and start working on it. If you want to help with an ongoing translation, - comment on the existing issue and say you are ready to help. We also need to update existing analyzers, which were - not created ready for localization. + We are starting to translate the analyzers and code fixes messages to other languages. If you want CodeCracker on your language feel free to create an issue and start working on it. If you want to help with an ongoing translation, + comment on the existing issue and say you are ready to help. We also need to update existing analyzers, which were not created ready for localization. ## Issues and task board @@ -113,8 +140,9 @@ defined ready as: 1. Have most of the scenarios/test cases defined on the issue on Github 2. If it has an analyzer then - 1. The warning level of the analyzer must be in the issue's description (`Hidden`, `Information`, `Warning`, or `Error`) - 2. The diagnostics it provides should already have numeric ids defined formated as `CC0000`. + + 1. The warning level of the analyzer must be in the issue's description (`Hidden`, `Information`, `Warning`, or `Error`) + 2. The diagnostics it provides should already have numeric ids defined formatted as `CC0000`. 3. If it has a code fix then the category should be in the issue's description. The supported categories are listed on the `SupportedCategories.cs` file. 4. Have some of the maintainers verify it (cannot be the same one who wrote the issue and/or test cases) @@ -129,10 +157,10 @@ and [#10](https://github.com/code-cracker/code-cracker/issues/10). These are the 4 severity levels supported on Roslyn and how they are understood on the Code Cracker project: -1. **Hidden**: Only used for refactorings. See #66 (and its comments) to understand why. -2. **Info**: An alternative way (ex: replacing for with foreach). Clearly a matter of opinion and/or current way could be correct, or maybe the new code could be correct. We cannot determine. +1. **Hidden**: Only used for refactorings. See [#66](https://github.com/code-cracker/code-cracker/issues/66) (and its comments) to understand why. +2. **Info**: An alternative way (ex: replacing for with foreach). Clearly, a matter of opinion and/or current way could be correct, or maybe the new code could be correct. We cannot determine. 3. **Warning**: Code that could/should be improved. It is a code smell and most likely is wrong, but there are situations where the pattern is acceptable or desired. -4. **Error**: Clearly a mistake (ex: throwing ArgumentException with an non-existent parameter). There is no situation where this code could be correct. There are no differences of opinion. +4. **Error**: Clearly a mistake (ex: throwing ArgumentException with a non-existent parameter). There is no situation where this code could be correct. There are no differences of opinion. You can read [directly on Microsoft's source code](http://source.roslyn.codeplex.com/#Microsoft.CodeAnalysis/Diagnostic/DiagnosticSeverity.cs,e70281df673d47f6,references) how they interpret these levels. @@ -145,23 +173,25 @@ The DoD is still evolving. At the present time the checklist is as follows: 2. Has tests for analyzers, code fixes (including fix all providers) and refactoring 3. All tests pass 4. Analyzers follow the guidelines for names - 1. Always named `Analyzer` - 2. Always add the diagnostic id to the `DiagnosticIds.cs` file. + + 1. Always named `Analyzer` + 2. Always add the diagnostic id to the `DiagnosticIds.cs` file. 5. Code fixes should follow the guidelines for names - 1. Always named `CodeFixProvider` - 2. Always use the same diagnostic id added to the `DiagnosticIds.cs` file, unless you are writing a code fix for a diagnostic id raised by the C# compiler itself (staring with `CS`). -6. Fix all scenarios (fix all in document, fix all in project and fix all in solution) work. You might need to write a `FixAllProvider`. Check the `DisposableVariableNotDisposedFixAllProvider` as an example. + + 1. Always named `CodeFixProvider` + 2. Always use the same diagnostic id added to the `DiagnosticIds.cs` file, unless you are writing a code fix for a diagnostic id raised by the C# compiler itself (starting with `CS`). +6. Fix all scenarios (fix all in a document, fix all in a project and fix all in solution) work. You might need to write a `FixAllProvider`. Check the `DisposableVariableNotDisposedFixAllProvider` as an example. 7. Follow the coding standards present on the project code files. 8. Works in Visual Studio 9. Uses localizable strings ### Start working -Once it is Ready and agreed on by any one from the core team, just state in +Once it is Ready and agreed on by anyone from the core team, just state in a comment that you intend to start working on that item and mention any/all the maintainers (use @code-cracker/owners) so they can tag it correctly and move it on the board. -If you are not familiar with the way Github works you might want to check the [Github guides](https://guides.github.com/), specially +If you are not familiar with the way Github works you might want to check the [Github guides](https://guides.github.com/), especially the [Github flow](https://guides.github.com/introduction/flow/) one. The [GitHub for the Roslyn Team video](http://channel9.msdn.com/Blogs/dotnet/github-for-the-roslyn-team) might help you as well, and it also explains some Git concepts. @@ -170,14 +200,14 @@ To start working fork the project on Github to your own account and clone it **f from the main CodeCracker repository. Before you start coding create a new branch and name it in a way that makes sense for the issue that you will be working on. Don't work on the `master` branch because that may make things harder if you have to update your pull request or your repository later, assume your `master` branch -is always equals the main repo `master` branch, and code on a different branch. +always equals the main repo `master` branch, and code on a different branch. When you commit, mention the issue number use the pound sign (#). Avoid making a lot of small commits unless they are meaningful. For most analyzers and code fixes a single commit should be enough. If you prefer to work with a lot commits at the end squash them. Make your first commit lines mean something, specially the first one. -[Here](http://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message) and +[Here](https://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message) and [here](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) are some tips on a good commit first line/message. @@ -206,9 +236,9 @@ git merge master # solve integration conflicts ```` -You can solve the conflicts in your favorite text editor, or, if you are using Visual Studio, you can use it as well. +You can solve the conflicts in your favorite text editor or Visual Studio. Visual Studio actually presents the conflict in a very nice way to solve them. -Also, on the `go back to your working branch` step you can go back to using Visual Studio to control git, if you +Also, on the `go back to your working branch` step you can go back to using Visual Studio to control git if you prefer that. If you know git well, you can rebase your changes instead of merging them. If not, it is ok to merge them. @@ -237,7 +267,7 @@ discussing and fixing they are accepted. Work with the community to get it to be * Your pull requested will be commented by the Coveralls bot. Make sure code coverage has not gone down significantly. Ideally, it should go up. If you work on something that you have not yet discussed with the maintainers -there is a chance the code might be denied because they might find the analyzer/fix is not necessary, or duplicated, or some other reason. +there is a chance the code might be denied because they might find the analyzer/fix is not necessary, duplicated, or some other reason. They are easily reachable through Twitter or on Github. Before you code discuss it with it them. Small code changes or updates outside code files will eventually be made by the core team directly on `master`, without a PR. @@ -245,13 +275,13 @@ Small code changes or updates outside code files will eventually be made by the ## Maintainers/Core team -* [Giovanni Bassi](http://blog.lambda3.com.br/L3/giovannibassi/), aka Giggio, [Lambda3](http://www.lambda3.com.br), [@giovannibassi](http://twitter.com/giovannibassi) -* [Elemar Jr.](http://elemarjr.net/), [Promob](http://promob.com/), [@elemarjr](http://twitter.com/elemarjr) -* [Carlos dos Santos](http://carloscds.net/), [CDS Informática](http://www.cds-software.com.br/), [@cdssoftware](http://twitter.com/cdssoftware) +* [Giovanni Bassi](http://blog.lambda3.com.br/L3/giovannibassi/), aka Giggio, [Lambda3](http://www.lambda3.com.br), [@giovannibassi](https://twitter.com/giovannibassi) +* [Elemar Jr.](http://elemarjr.net/), [Promob](http://promob.com/), [@elemarjr](https://twitter.com/elemarjr) +* [Carlos dos Santos](http://carloscds.net/), [CDS Informática](http://www.cds-software.com.br/), [@cdssoftware](https://twitter.com/cdssoftware) Contributors can be found at the [contributors](https://github.com/code-cracker/code-cracker/graphs/contributors) page on Github. -### What are the maintainers responsibilities? +### What are the maintainers' responsibilities? The maintainers have to: @@ -263,7 +293,7 @@ The maintainers have to: To become part of the core team one has to be invited. Invitations happen only if all the core team agrees. -If a member of the core team is not active for at least to months, they will probably be removed from the core team. +If a member of the core team is not active for at least two months, they will probably be removed from the core team. ## Contact @@ -274,5 +304,5 @@ Please see our [contact page](http://code-cracker.github.io/contact.html). This software is open source, licensed under the Apache License, Version 2.0. See [LICENSE.txt](https://github.com/code-cracker/code-cracker/blob/master/LICENSE.txt) for details. Check out the terms of the license before you contribute, fork, copy or do anything -with the code. If you decide to contribute you agree to grant copyright of all your contribution to this project, and agree to -mention clearly if do not agree to these terms. Your work will be licensed with the project at Apache V2, along the rest of the code. \ No newline at end of file +with the code. If you decide to contribute you agree to grant copyright of all your contribution to this project and agree to +mention clearly if do not agree to these terms. Your work will be licensed with the project at Apache V2, along the rest of the code. diff --git a/README.pt.md b/README.pt.md new file mode 100644 index 000000000..2065495c5 --- /dev/null +++ b/README.pt.md @@ -0,0 +1,289 @@ +# Code Cracker + +Uma biblioteca de analizadores (analyzer library) para C# e VB que usam [Roslyn](https://github.com/dotnet/roslyn) para refatorações, análises de código e outros detalhes. + +De uma olhada no site oficial [code-cracker.github.io](http://code-cracker.github.io). Lá você irá encontrar informações sobre como contribuir, +nossa lista de tarefas, definição para "done", definição para "ready" entre outras coisas. + +[![Build status](https://ci.appveyor.com/api/projects/status/h21sli3jkumuswyi?svg=true)](https://ci.appveyor.com/project/code-cracker/code-cracker) +[![Nuget count](https://img.shields.io/nuget/v/codecracker.CSharp.svg)](https://www.nuget.org/packages/codecracker.CSharp/) +[![License](https://img.shields.io/github/license/code-cracker/code-cracker.svg)](https://github.com/code-cracker/code-cracker/blob/master/LICENSE.txt) +[![Issues open](https://img.shields.io/github/issues-raw/code-cracker/code-cracker.svg)](https://huboard.com/code-cracker/code-cracker/) +[![Coverage Status](https://img.shields.io/coveralls/code-cracker/code-cracker/master.svg)](https://coveralls.io/r/code-cracker/code-cracker?branch=master) +[![Source Browser](https://img.shields.io/badge/Browse-Source-green.svg)](http://ccref.azurewebsites.net) + +Você pode encontrar este documento nas seguintes línguas + +[![English](https://img.shields.io/badge/language-english-blue.svg)](https://github.com/code-cracker/code-cracker/blob/master/README.md) +[![Brazilian Portuguese](https://img.shields.io/badge/language-brazilan%20portuguese-brightgreen.svg)](https://github.com/code-cracker/code-cracker/blob/master/README.pt.md) + + +Este é um projeto da comunidade, *free* e *open source*. Todos estão convidados para contribuir, forkar, compartilhar e usar o código. + +## Features + +A lista de *features* está documentada aqui: http://code-cracker.github.io/diagnostics.html + +#### Design +Code | Analyzer | Gravidade | Descrição +-- | -- | -- | -- +[CC0003](http://code-cracker.github.io/diagnostics/CC0003.html) | CatchEmptyAnalyzer | Warning | Declarações de catch sem Exption como um argumento não é recomendado. Considere adicionar uma classe Exception à instrução catch. +[CC0004](http://code-cracker.github.io/diagnostics/CC0004.html) | EmptyCatchBlockAnalyzer | Warning | Um bloco catch vazio suprime todos os erros e não deve ser usado. Se o erro for esperado, considere registrá-lo ou alterar o fluxo de controle de modo que seja explícito. +[CC0016](http://code-cracker.github.io/diagnostics/CC0016.html) | CopyEventToVariableBeforeFireAnalyzer | Warning | Os eventos devem sempre ser verificados para null antes de serem invocados. Como em um contexto multi-threading, é possível que um evento seja anulado entre o momento em que ele é verificado como não-nulo e, no momento em que é gerado, o evento deve ser copiado para uma variável temporária antes da verificação. +[CC0021](http://code-cracker.github.io/diagnostics/CC0021.html) | NameOfAnalyzer | Warning | Em C # 6, o operador nameof () deve ser usado para especificar o nome de um elemento de programa em vez de um literal de string, pois ele produz um código mais fácil de refatorar. +[CC0024](http://code-cracker.github.io/diagnostics/CC0024.html) | StaticConstructorExceptionAnalyzer | Warning | O construtor estático é chamado antes da primeira vez que uma classe seja usada, mas o chamador não controla exatamente quando. A exceção lançada, neste contexto, força os chamadores a usar o bloqueio ‘try’ em torno de qualquer uso da classe e deve ser evitado. +[CC0031](http://code-cracker.github.io/diagnostics/CC0031.html) | UseInvokeMethodToFireEventAnalyzer | Warning | Em C # 6, um *delegate* pode ser chamado usando o operador de propagação nulo ou null-propagating operator (?.) E seu método de invocação para evitar lançar uma exceção NullReference quando não houver nenhum método anexado ao *delegate*. + +## Instalação + +Você pode usar o CodeCracker de duas formas: como um *analyzer library* que você instala com o Nuget dentro do seu projeto ou como uma extensão do Visual Studio. +A maneira como você deseja usá-lo depende do cenário em que você está trabalhando. Você na maioria das vezes vai optar por usar como um pacote Nuget. + +Se você quiser que os analisadores funcionem durante a sua construção, e que gerem avisos e erros durante o build, também nos build servers, então você vai querer +usar o pacote Nuget. O pacote está disponível no nuget([C#](https://www.nuget.org/packages/codecracker.CSharp), +[VB](https://www.nuget.org/packages/codecracker.VisualBasic)). + +Se você quer ser capaz de configurar quais analisadores estão sendo usados em seu projeto, e quais você irá ignorar, commitar essas mudanças no controle de código e compartilhar com sua equipe, então você também quer o pacote Nuget. + +Instalação pelo Nuget, para a versão C#: + +```powershell +Install-Package CodeCracker.CSharp +``` + +ou para a versão Visual Basic: + +```powershell +Install-Package CodeCracker.VisualBasic +``` + +Também é possível instalar pelo Gerenciador de Pacotes (Package Manager) dentro do Visual Studio. + +Além das versões específicas para cada linguagem de programação, também temos uma versão para ambas, chadama somente de `CodeCracker` sem sufixo, mas +não faz sentido usarem essa versão já que provavelmente trabalhará com uma das duas linguagens. + +Se você quer os build alpha, o que *builda* a cada push, adicione https://www.myget.org/F/codecrackerbuild/ para o seu nuget feed. +Nós apenas enviamos versões completas para o Nuget.org, e commit builds para Myget.org. + +Se você quer o analyzer globalmente, ou seja, que funciona toda vez que abre um projeto no Visual Studio, então você quer a extensão. + +Procure pela extensão na **Galeria de Extensões** do Visual Studio.([C#](https://visualstudiogallery.msdn.microsoft.com/ab588981-91a5-478c-8e65-74d0ff450862), +[VB](https://visualstudiogallery.msdn.microsoft.com/1a5f9551-e831-4812-abd0-ac48603fc2c1)). + +para build a partir do fonte: + +```shell +git clone https://github.com/code-cracker/code-cracker.git +cd CodeCracker +msbuild +``` +Em seguida, adicione uma referência ao CodeCracker.dll de dentro das referências, no Visual Studio. + +Se voce quer usar o CodeCracker em todos os projetos, instale a extensão para Visual Studio ([C#](https://visualstudiogallery.msdn.microsoft.com/ab588981-91a5-478c-8e65-74d0ff450862), [VB](https://visualstudiogallery.msdn.microsoft.com/1a5f9551-e831-4812-abd0-ac48603fc2c1)). Se você quer usar o CodeCracker só para um projeto, instale o pacote Nuget como descrevemos acima. + +## SonarQube Plugin + +CodeCracker tem um plugin para o SonarQube que pode ser baixado através deste link [Plugins HomePage](http://docs.sonarqube.org/display/PLUG/Other+Plugins). + +## Contribuindo [![Open Source Helpers](https://www.codetriage.com/code-cracker/code-cracker/badges/users.svg)](https://www.codetriage.com/code-cracker/code-cracker) + +A principal IDE suportada para desenvolvimento é o Visual Studio 2017. Não temos mais suporte para VS 2015. + +Perguntas, comentários, report de bugs e pull requests são bem-vindos. +Os Reports de bugs devem incluir o passa-a-passo (incluindo o código). Melhor ainda, utilizem o formato de uma pull request. Antes de você começar a trabalhar em uma issue existente, verifique se a mesma já não foi atribuida para alguém, caso isso tenha acontecido, converse com a pessoa. + +Verifique também o board do [projeto](https://huboard.com/code-cracker/code-cracker/) e verifique se já não estão trabalhando na tarefa (estará marcada como `Working` tag). Se a tarefa estiver livre, antes de você começar, verifique se o item tem a tag `Ready`. Se a issue estiver com a tag `Working` (*working* na raia de trabalho) e não houver atribuição então a tarefa ainda não começou a ser trabalhada por alguém do **core team**. Verifique a descrição da issue para procurar pelo responsável (se não estiver lá, você encontrará nos comentários). Nos estamos adicionando pessoas que querem contribuir com o projeto ao `Contributors` team assim nos podemos sempre atribuir os *Contributors* para as issues, provavelmente na sua primeira contribuição você será adicionado neste time. + +A forma mais fácil de começar é pesquisando pelas issues com a tag [up for grabs](https://github.com/code-cracker/code-cracker/labels/up-for-grabs). Você pode pedir para trabalhar com qualquer uma delas, leia abaixo para ver **como**. Você também pode fazer a triagem das issues que podem incluir a reprodução dos reports de bug, ou perguntando sobre informações importantes como o número das versões ou instruções para reprodução. Se você quiser iniciar a triagem das issues, uma forma fácil de começar é [subscribe to code-cracker on CodeTriage](https://www.codetriage.com/code-cracker/code-cracker). + +Se você está iniciando com Roslyn e quer contribuir mas sente que ainda não está preparado para começar trabalhando com a criação de analyzers ou code fixes, você pode começar ajudando com as áreas que são menos demandadas. Nós identificamos algumas: + +* Fixing bugs + + Ainda exige conhecimento dos componentes internos do Roslyn, mas é mais fácil do que criar um novo + analyzer ou *fix bugs*. Procure por [bugs that are up for grabs](https://github.com/code-cracker/code-cracker/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3Abug+label%3Aup-for-grabs). + +* Documentação + + Estamos documentado todos os analyzers no [CodeCracker user site](http://code-cracker.github.io/diagnostics.html). + Existem muitos analyzers e correções de código para documentar. + +* Traduzindo + + Nós estamos começando a traduzir os analyzers e mensagens para outras línguas. Se você gostaria de ver o CodeCracker na sua + língua nativa venha nos ajusdar, crie uma issue e comece a tradução. Se você quer ajudar com uma tradução já em andamento, + comente na issue existente oferecendo sua ajuda. Nós também precisamos atualizar os analyzers existentes. + +## Issues e task board + +* O task board está em [Huboard](https://huboard.com/code-cracker/code-cracker/). +* Você também pode encontrar no [Github backlog](https://github.com/code-cracker/code-cracker/issues) diretamente. + +### Definição de Ready (DoR) + +Só podemos começar a trabalhar em um item depois que o *backlog item* estiver marcado como *ready*. +Nós definimos *ready* quando: + +1. Quando temos a maioria dos cenários/casos de teste definidos na issue aqui no Github. +2. Se no backlog item tiver um ou mais analyzers então + 1. O nível de alerta (warning level) do analyzer deve estar definido na descrição da issue (`Hidden`, `Information`, `Warning`, ou `Error`). + 2. O *diagnostic* informado na issue já deve ter o id definido no formato `CC0000`. +3. Se houver um *code fix* então a categoria deve estar definida na descrição da issue. As categorias suportadas estão listadas no arquivo `SupportedCategories.cs`. +4. Um dos mantenedores devem ter verificado o item (não necessariamente o mesmo que escreveu a issue e/ou os casos de teste). + +O primeiro item é importante para definirmos claramente o que iremos construir. O último +é igualmente importante para não criarmos algo que não será útil, que irá atrapalhar os usuários ou que +será um disperdício de esforço. + +Vejam exemplos nas issues [#7](https://github.com/code-cracker/code-cracker/issues/7) +e [#10](https://github.com/code-cracker/code-cracker/issues/10). + +### Níveis de Severidade + +Estes são os 4 níveis de severidade suportados no Roslyn e como eles são entendidos no projeto Code Cracker: + +1. **Hidden**: Somente utilizados para *refactorings*. Leia a issue [#66](https://github.com/code-cracker/code-cracker/issues/66) (inclusive os comentários) para entender melhor. +2. **Info**: Uma maneira alternativa (ex: substituindo *for* pelo *foreach*). Quando claramente for uma questão de opinião e/ou a quando qualquer uma das formas podem ser consideradas corretas. +3. **Warning**: Código que pode/deve ser melhorado. Estes são os *code smells* e provavelmente estão escritos de forma errada, mas existem situações que o pattern pode ser desconsiderado. +4. **Error**: Claramente um erro. (ex: *throwing* ArgumentException com um parâmetro inexistente). Não há nenhuma situação em que esse código possa estar correto. Não há diferenças de opinião. + +Também é possível saber as definições da [própria Microsoft](http://source.roslyn.codeplex.com/#Microsoft.CodeAnalysis/Diagnostic/DiagnosticSeverity.cs,e70281df673d47f6,references) de como eles interpretam estes níveis. + +### Definição de Done (DoD) + +O DoD ainda está evoluindo. Até o momento seguimos o checklist abaixo: + +1. passando pelo Build. +2. Os analyzers tem testes, com as correções dos códigos e *refactoring*. +3. Todos os testes devem passar. +4. Os Analyzers devem seguir os padrões para nomes definidos no *guidelines* + 1. Sempre seguir o padrão `Analyzer`. + 2. Sempre adicionar o id do *diagnostic* no arquivo `DiagnosticIds.cs`. +5. As correções de códigos devem seguir os *guidelines* para definição dos nomes + 1. Sempre seguir o padrão `CodeFixProvider`. + 2. Sempre usar o mesmo id do *diagnostic* no arquivo `DiagnosticIds.cs`, a menos que você esteja escrevendo uma correção de código para um id do *diagnostic* levantado pelo próprio compilador C # (com as iniciais `CS`). +6. Correção de todos os cenários (Correção para todos os cenários no documento, no projeto and na *solution*). Talvez precise escrever um `FixAllProvider`. Verifique o `DisposableVariableNotDisposedFixAllProvider` como exemplo. +7. Siga os padrões de codificação presentes nos arquivos de código do projeto. +8. Funcionando no Visual Studio. +9. Usar strings localizaveis. + +### Comece a Trabalhar + +Quando estiver pronto e acordado por qualquer um do *core team*, apenas informe em +um comentário que você pretende começar a trabalhar neste item e mencionar qualquer ou todos +os mantenedores (use @code-cracker/owners) assim eles podem *tegar* a issue corretamente e move-la no board. + +Se você não está familiarizado com o funcionamento do Github, talvez queira verificar o [Github guides](https://guides.github.com/), em +especial o [Github flow](https://guides.github.com/introduction/flow/). O +[GitHub for the Roslyn Team video](http://channel9.msdn.com/Blogs/dotnet/github-for-the-roslyn-team) pode ajudá-lo também, e +também explica alguns conceitos do Git. + +Para começar a trabalhar, *fork* o projeto no Github com sua própria conta e copie-o **de lá, sua própria conta**. +To start working fork the project on Github to your own account and clone it **from there**. Não clone +direto do repositório do CodeCracker. Antes de iniciar a codificação, crie uma nova *branch* e nomeie-a de uma forma que tenha +sentido para a issue que você estará trabalhando. Não trabalhe na *branch* `master` porque isso pode tornar +as coisas mais difíceis se você tiver que atualizar sua *pull request* ou seu repositório mais tarde, +suponha que a sua *branch* `master` é sempre igual a *branch* `master` do repositório principal, e o seu código em uma *branch* diferente. + +Na mensagem do seu *commit*, lembre-se de mencionar o número da issue usando o sinal de cerquilha (#) na frente do número. Evite fazer pequenos *commits* +a menos que sejam significativos. Para a maioria dos analyzers e correções de código (code fixes), um único *commit* deve ser o suficiente. Se preferir +trabalhar com muito commits, no final faço o processo de *squash*. + +Faça com que suas primeiras linhas de *commit* signifiquem algo, especialmente a primeira. +[Aqui](https://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message) e +[aqui](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) existem algumas dicas de uma boa primeira linha/mensagem de +*commit*. + +**Não**, em qualquer circunstância, reformate o código para adequá-lo aos seus padrões. Siga os padrões do projeto, +e se você não concordar com os padrões, discuta abertamente com a comunidade Code Cracker. Além disso, evite o fim da linha +espaços em branco a todo custo. Mantenha seu código limpo. + +Sempre escreva testes de unidade para seus analyzers, correções de código (code fixes). + +### Pull Request + +Quando terminar, baixe as últimas mudanças da *branch* `master` do repositório principal do CodeCracker e integre-as a sua *branch*. + +Você pode fazer isso na linha de comando: +````bash +# adicione o repositório principal com o nome de `codecracker` +git remote add code-cracker https://github.com/code-cracker/code-cracker.git +# vá para a branch master +git checkout master +# faça o download das últimas alterações feitas na branch master do repositório principal +git pull code-cracker master +# volte para a sua branch de trabalho +git checkout +# integre as mudanças +git merge master +# resolva os conflitos de integração +```` + +Você pode resolver os conflitos no seu editor de textos favorito, ou, se você estiver usando o Visual Studio, também poderá usa-lo para está tarefa. +O Visual Studio na verdade apresenta os conflitos de uma muito simples e boa para resolve-los. +Além disso, no passo `volte para a sua branch de trabalho` você pode voltar a usar o Visual Studio para controlar o git, se você preferir. + +Se você conhecer um pouco mais de git, você pode usar o comando `rebase` ao invés do `merge`. Caso contrário, tudo bem se fizer um `merge`. +Quando as suas alterações estiverem atualizadas com a +branch `master` então você precisará envia-lo para o seu repositório remoto (no GitHub) e então você estará pronto para criar +um [pull request](https://help.github.com/articles/using-pull-requests/). Não esqueça de mencionar a issue que original essa PR e +capriche na sua mensagem da PR mantendo ela clara e objetiva. Se quando você criar a `pull request` no GitHub receber um retorno informando +alguma divergência, isso significa que a sua PR não pode ser mesclada porque ainda existem conflitos com a branch `master`. Corrija os conflitos, +envie para o seu repositório pessoal (aquele forkado no início). Isso irá automaticamente atualizar a sua PR. +O mantenedores do projeto não deverão resolver conflitos de `merge`, você quem precisa fazer isso. + +Depois que a sua `pull request` for aceita você pode deletar a sua *branch* local, claro, se quiser. Lembre-se de atualizar a *branch* `master` assim +poderá continuar contribuindo no futuro. e obrigado! :) + +Se a sua *pull request* não for autorizada tente entender o motivo. Não é incomum que PRs sejam rejeitadas e depois de algumas +discussões e correções elas são aceitas. Trabalhe com a comunidade para obter o melhor código possível. and Obrigado! + +### Regras para contribuição + +* Todo *pull request* deve ter testes de unidade. PRs sem testes serão negadas (denied) sem verificar qualquer outro item; +* Tem que *buildar* e todos os testes devem passar; +* Deve mencionar a issue correspondente a PR no GitHub; +* Não altere nenhum código além do alinhado na issue correspondente ao PR; +* Siga os padrões de codificação (coding standars) já em vigor no projeto; +* Uma *code issue* de cada vez por pessoa (issues bloqueadas não contam); +* Seu *pull request* será comentado pelo bot Coveralls. Certifique-se de que a cobertura do código não diminuiu significativamente. Idealmente, deveria subir. + +Se você trabalhou em algo que você ainda não discutiu com os mantenedores +existe uma chance de o código ser negado porque eles podem achar que o *analyzer* / *fix* não é necessário, duplicado ou algum outro motivo. +Os mantenedores são facilmente acessados ​​através do Twitter ou GitHub. Antes de codificar alinhe com os mantenedores. + +Mudanças de código pequenas ou atualizações fora dos arquivos de código serão eventualmente feitas pela equipe principal, diretamente no `mestre`, sem um PR. + + +## Maintainers/Core team + +* [Giovanni Bassi](http://blog.lambda3.com.br/L3/giovannibassi/), aka Giggio, [Lambda3](http://www.lambda3.com.br), [@giovannibassi](https://twitter.com/giovannibassi) +* [Elemar Jr.](http://elemarjr.net/), [Promob](http://promob.com/), [@elemarjr](https://twitter.com/elemarjr) +* [Carlos dos Santos](http://carloscds.net/), [CDS Informática](http://www.cds-software.com.br/), [@cdssoftware](https://twitter.com/cdssoftware) + +Os contribuidores podem ser encontrados aqui: [contributors](https://github.com/code-cracker/code-cracker/graphs/contributors) página no Github. + +### Quais são as responsabilidades dos mantenedores ? + +Os mantenedores devem: + +* Commitar regularmente; +* Trabalhar regularmente em tarefas de manutenção do projetos, como (mas não limitado a) + * participart dos encontros, + * revisar pull requests, + * criar e discutir nas *issues* do projeto; + +Para fazer parte da equipe principal, é preciso ser convidado. Os convites só acontecem se toda a equipe principal concordar. + +Se um membro da equipe principal não estiver ativo por pelo menos dois meses, ele provavelmente será removido da equipe principal. + +## Contato + +Por favor veja a nossa [página de contatos](http://code-cracker.github.io/contact.html). + +## Licença + +Este software é open source, licenciado sob a licença Apache, versão 2.0. +Veja [LICENSE.txt](https://github.com/code-cracker/code-cracker/blob/master/LICENSE.txt) para os detalhes. +Confira os termos da licença antes de contribuir, *forcar*, copiar ou fazer qualquer coisa com o código. +with the code. Se você decidir contribuir, você concorda em conceder direitos autorais de toda a sua contribuição para este projeto, e concorda em mencionar claramente se não concordar com estes termos. Seu trabalho será licenciado com o projeto no Apache V2, ao longo do restante do código. diff --git a/appveyor.yml b/appveyor.yml index 0eae120fe..362ed41d3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,8 @@ version: 1.0.0.{build} -skip_tags: true +image: Visual Studio 2017 -configuration: DebugNoVsix +configuration: ReleaseNoVsix init: - git config --global core.autocrlf true @@ -19,8 +19,6 @@ environment: before_build: - ps: >- - $env:path="C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow;C:\Program Files (x86)\Microsoft SDKs\F#\4.0\Framework\v4.0\;C:\Program Files (x86)\Microsoft SDKs\TypeScript\1.1;C:\Program Files (x86)\MSBuild\14.0\bin;C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\;C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN;C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools;C:\Windows\Microsoft.NET\Framework\v4.0.30319;C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\VCPackages;C:\Program Files (x86)\HTML Help Workshop;C:\Program Files (x86)\Microsoft Visual Studio 14.0\Team Tools\Performance Tools;C:\Program Files (x86)\Windows Kits\8.1\bin\x86;C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Windows Kits\8.1\Windows Performance Toolkit\;C:\Program Files\Microsoft SQL Server\120\Tools\Binn\;C:\Program Files (x86)\Microsoft SDKs\TypeScript\1.1\;$env:path" - . .\build.ps1 Prepare-Build @@ -53,10 +51,10 @@ artifacts: deploy: - provider: NuGet api_key: - secure: s1aIT1sGbIeG5Ccgree7K+k/h7LOSzPfJOrsWcCuzgFGrcuexPZUwX/CfYnU9w4v + secure: CosNPh0GfqQtJs2da/qdtykCGRhaJ1keXXxAcEFR0jNGAmveTtj01kPfqIlLPjFv skip_symbols: true on: - branch: release + appveyor_repo_tag: true - provider: NuGet server: https://www.myget.org/F/codecrackerbuild/api/v2/package api_key: @@ -70,7 +68,7 @@ deploy: secure: 42eslsnaZIIcMVVaeC9Qu5NI9yjzLzHWYUGl0HLhl0YurivQezpMyJOwgSVjiGmj skip_symbols: true on: - branch: vnext + branch: /v\d\.\d\.x/ notifications: - provider: Email diff --git a/build.ps1 b/build.ps1 index 028d74306..455eb5c88 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,11 +1,82 @@ -if ((Test-Path $PSScriptRoot\packages\psake.4.4.2\tools\psake.psm1) -ne $true) { - nuget restore $PSScriptRoot\.nuget\packages.config -SolutionDirectory $PSScriptRoot +$ErrorActionPreference = "Stop" +$tempDir = Join-Path "$([System.IO.Path]::GetTempPath())" "CodeCracker" +if (!(Test-Path $tempDir)) { mkdir $tempDir | Out-Null } +# functions: + +function IsNugetVersion3OrAbove($theNugetExe) { + try { + $nugetText = . $theNugetExe | Out-String + } catch { + return false + } + [regex]$regex = '^NuGet Version: (\d)\.(\d).*\n' + $match = $regex.Match($nugetText) + $version = $match.Groups[1].Value + Write-Host "Nuget major version is $version" + return [System.Convert]::ToInt32($version) -ge 3 } -Import-Module $PSScriptRoot\packages\psake.4.4.2\tools\psake.psm1 -force + +function Get-Nuget { + if (gcm nuget -ErrorAction SilentlyContinue) { + if (IsNugetVersion3OrAbove 'nuget') { + $script:nugetExe = 'nuget' + } else { + Download-Nuget + $script:nugetExe = $localNuget + } + } else { + Download-Nuget + $script:nugetExe = $localNuget + } +} + +function Download-Nuget { + $tempNuget = "$env:TEMP\codecracker\nuget.exe" + if (!(Test-Path "$env:TEMP\codecracker\")) { + md "$env:TEMP\codecracker\" | Out-Null + } + if (Test-Path $localNuget) { + if (IsNugetVersion3OrAbove($localNuget)) { return } + } + if (Test-Path $tempNuget) { + if (IsNugetVersion3OrAbove($tempNuget)) { + cp $tempNuget $localNuget + return + } + } + wget "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -OutFile $tempNuget + cp $tempNuget $localNuget +} + +function Import-Psake { + $psakeModule = "$PSScriptRoot\packages\psake.4.7.4\tools\psake\psake.psm1" + if ((Test-Path $psakeModule) -ne $true) { + Write-Host "Restoring $PSScriptRoot\.nuget with $script:nugetExe" + . "$script:nugetExe" restore $PSScriptRoot\.nuget\packages.config -SolutionDirectory $PSScriptRoot + } + Import-Module $psakeModule -force +} + +function Import-ILMerge { + $ilmergeExe = "$PSScriptRoot\packages\ilmerge.2.14.1208\tools\ILMerge.exe" + if ((Test-Path $ilmergeExe) -ne $true) { + Write-Host "Restoring $PSScriptRoot\.nuget with $script:nugetExe" + . "$script:nugetExe" restore $PSScriptRoot\.nuget\packages.config -SolutionDirectory $PSScriptRoot + } +} + +# statements: + +$localNuget = "$PSScriptRoot\.nuget\nuget.exe" +$nugetExe = "" +Get-Nuget +Import-Psake +Import-ILMerge if ($MyInvocation.UnboundArguments.Count -ne 0) { - . $PSScriptRoot\psake.ps1 -taskList ($MyInvocation.UnboundArguments -join " ") + Invoke-Expression("Invoke-psake -framework '4.6' $PSScriptRoot\psakefile.ps1 -taskList " + $MyInvocation.UnboundArguments -join " ") } else { . $PSScriptRoot\build.ps1 Build } + exit !($psake.build_success) \ No newline at end of file diff --git a/build.targets.ps1 b/build.targets.ps1 deleted file mode 100644 index 1bea77cb2..000000000 --- a/build.targets.ps1 +++ /dev/null @@ -1,223 +0,0 @@ -Properties { - $rootDir = Split-Path $psake.build_script_file - $solutionFileCS = "$rootDir\CodeCracker.CSharp.sln" - $solutionFileVB = "$rootDir\CodeCracker.VisualBasic.sln" - $srcDir = "$rootDir\src" - $testDir = "$rootDir\test" - $isAppVeyor = $env:APPVEYOR -eq $true - $slns = ls "$rootDir\*.sln" - $packagesDir = "$rootDir\packages" - $buildNumber = [Convert]::ToInt32($env:APPVEYOR_BUILD_NUMBER).ToString("0000") - $nuspecPathCS = "$rootDir\src\CSharp\CodeCracker\CodeCracker.nuspec" - $nuspecPathVB = "$rootDir\src\VisualBasic\CodeCracker\CodeCracker.nuspec" - $nuspecPathJoint = "$rootDir\src\CodeCracker.nuspec" - $nugetExe = "$packagesDir\NuGet.CommandLine.2.8.5\tools\NuGet.exe" - $nupkgPathCS = "$rootDir\src\CSharp\CodeCracker.CSharp.{0}.nupkg" - $nupkgPathVB = "$rootDir\src\VisualBasic\CodeCracker.VisualBasic.{0}.nupkg" - $nupkgPathJoint = "$rootDir\CodeCracker.{0}.nupkg" - $xunitConsoleExe = "$packagesDir\xunit.runner.console.2.0.0\tools\xunit.console.x86.exe" - $openCoverExe = "$packagesDir\OpenCover.4.6.166\tools\OpenCover.Console.exe" - $testDllCS = "CodeCracker.Test.CSharp.dll" - $testDllVB = "CodeCracker.Test.VisualBasic.dll" - $testDirCS = "$testDir\CSharp\CodeCracker.Test\bin\Debug" - $testDirVB = "$testDir\VisualBasic\CodeCracker.Test\bin\Debug" - $logDir = "$rootDir\log" - $outputXml = "$logDir\CodeCoverageResults.xml" - $reportGeneratorExe = "$packagesDir\ReportGenerator.2.1.8.0\tools\ReportGenerator.exe" - $coverageReportDir = "$logDir\codecoverage\" - $converallsNetExe = "$packagesDir\coveralls.io.1.3.4\tools\coveralls.net.exe" - $isRelease = $isAppVeyor -and ($env:APPVEYOR_REPO_BRANCH -eq "release") - $isPullRequest = $env:APPVEYOR_PULL_REQUEST_NUMBER -ne $null -} - -FormatTaskName (("-"*25) + "[{0}]" + ("-"*25)) - -Task Default -Depends Build, Test - -Task Rebuild -Depends Clean, Build - -Task Restore { - Foreach($sln in $slns) { - RestorePkgs $sln - } -} - -Task Prepare-Build -depends Restore, Update-Nuspec - -Task Build -depends Prepare-Build, Build-Only -Task Build-CS -depends Prepare-Build, Build-Only-CS -Task Build-VB -depends Prepare-Build, Build-Only-VB - -Task Build-Only -depends Build-Only-CS, Build-Only-VB -Task Build-Only-CS { - if ($isAppVeyor) { - Exec { msbuild $solutionFileCS /m /verbosity:minimal /p:Configuration=DebugNoVsix /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" } - } else { - Exec { msbuild $solutionFileCS /m /verbosity:minimal /p:Configuration=DebugNoVsix } - } -} -Task Build-Only-VB { - if ($isAppVeyor) { - Exec { msbuild $solutionFileVB /m /verbosity:minimal /p:Configuration=DebugNoVsix /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" } - } else { - Exec { msbuild $solutionFileVB /m /verbosity:minimal /p:Configuration=DebugNoVsix } - } -} - -Task Clean { - Exec { msbuild $solutionFileCS /t:Clean /v:quiet } - Exec { msbuild $solutionFileVB /t:Clean /v:quiet } -} - -Task Set-Log { - if ((Test-Path $logDir) -eq $false) - { - Write-Host -ForegroundColor DarkBlue "Creating log directory $logDir" - mkdir $logDir | Out-Null - } -} - -Task Test-Acceptance -depends Test { - . "$rootDir\test\CSharp\AnalyzeCoreFx.ps1" -} - -Task Test -depends Set-Log { - RunTestWithCoverage "$testDirCS\$testDllCS", "$testDirVB\$testDllVB" -} -Task Test-VB -depends Set-Log { - RunTestWithCoverage "$testDirVB\$testDllVB" -} -Task Test-CSharp -depends Set-Log { - RunTestWithCoverage "$testDirCS\$testDllCS" -} - -Task Test-No-Coverage -depends Test-No-Coverage-CSharp, Test-No-Coverage-VB -Task Test-No-Coverage-VB { - RunTest "$testDirVB\$testDllVB" -} -Task Test-No-Coverage-CSharp { - RunTest "$testDirCS\$testDllCS" -} - -Task Update-Nuspec -precondition { return $isAppVeyor -and ($isRelease -ne $true) } -depends Update-Nuspec-Joint -Task Update-Nuspec-Joint -precondition { return $isAppVeyor -and ($isRelease -ne $true) } -depends Update-Nuspec-CSharp, Update-Nuspec-VB { - UpdateNuspec $nuspecPathJoint "joint package" -} -Task Update-Nuspec-CSharp -precondition { return $isAppVeyor -and ($isRelease -ne $true) } { - UpdateNuspec $nuspecPathCS "C#" -} -Task Update-Nuspec-VB -precondition { return $isAppVeyor -and ($isRelease -ne $true) } { - UpdateNuspec $nuspecPathVB "VB" -} - -Task Pack-Nuget -precondition { return $isAppVeyor } -depends Pack-Nuget-Joint -Task Pack-Nuget-Joint -precondition { return $isAppVeyor } -depends Pack-Nuget-Csharp, Pack-Nuget-VB { - PackNuget "Joint package" "$rootDir" $nuspecPathJoint $nupkgPathJoint -} -Task Pack-Nuget-CSharp -precondition { return $isAppVeyor } { - PackNuget "C#" "$rootDir\src\CSharp" $nuspecPathCS $nupkgPathCS -} -Task Pack-Nuget-VB -precondition { return $isAppVeyor } { - PackNuget "VB" "$rootDir\src\VisualBasic" $nuspecPathVB $nupkgPathVB -} - -function PackNuget($language, $dir, $nuspecFile, $nupkgFile) { - Write-Host "Packing nuget for $language..." - [xml]$xml = cat $nuspecFile - $nupkgFile = $nupkgFile -f $xml.package.metadata.version - Write-Host "Nupkg path is $nupkgFile" - . $nugetExe pack $nuspecFile -Properties "Configuration=Debug;Platform=AnyCPU" -OutputDirectory $dir - ls $nupkgFile - Write-Host "Nuget packed for $language!" - Write-Host "Pushing nuget artifact for $language..." - appveyor PushArtifact $nupkgFile - Write-Host "Nupkg pushed for $language!" -} - -function UpdateNuspec($nuspecPath, $language) { - write-host "Updating version in nuspec file for $language to $buildNumber" - [xml]$xml = cat $nuspecPath - $xml.package.metadata.version+="-$buildNumber" - write-host "Nuspec version will be $($xml.package.metadata.version)" - $xml.Save($nuspecPath) - write-host "Nuspec saved for $language!" -} - -function RestorePkgs($sln) { - Write-Host "Restoring $sln..." -ForegroundColor Green - Retry { - . $nugetExe restore $sln - if ($LASTEXITCODE) { throw "Nuget restore for $sln failed." } - } -} - -function Retry { - Param ( - [parameter(Position=0,Mandatory=1)] - [ScriptBlock]$cmd, - [parameter(Position=1,Mandatory=0)] - [int]$times = 3 - ) - $retrycount = 0 - while ($retrycount -lt $times){ - try { - & $cmd - if (!$?) { - throw "Command failed." - } - return - } - catch { - Write-Host -ForegroundColor Red "Failed: ($($_.Exception.Message)), retrying." - } - $retrycount++ - } - throw "Command '$($cmd.ToString())' failed." -} - -function TestPath($paths) { - $notFound = @() - foreach($path in $paths) { - if ((Test-Path $path) -eq $false) - { - $notFound += $path - } - } - $notFound -} - -function RunTest($fullTestDllPath) { - if ($isAppVeyor) { - . $xunitConsoleExe $fullTestDllPath -appveyor -nologo -quiet - } else { - . $xunitConsoleExe $fullTestDllPath -nologo -quiet - } -} - -function RunTestWithCoverage($fullTestDllPaths) { - $notFoundPaths = TestPath $openCoverExe, $xunitConsoleExe, $reportGeneratorExe - if ($notFoundPaths.length -ne 0) { - Write-Host -ForegroundColor DarkRed "Paths not found: " - foreach($path in $notFoundPaths) { - Write-Host -ForegroundColor DarkRed " $path" - } - throw "Paths for test executables not found" - } - $targetArgs = "" - Foreach($fullTestDllPath in $fullTestDllPaths) { - $targetArgs += $fullTestDllPath + " " - } - $targetArgs = $targetArgs.Substring(0, $targetArgs.Length - 1) - $appVeyor = "" - if ($isAppVeyor) { - $appVeyor = " -appveyor" - } - $arguments = '-register:user', "`"-target:$xunitConsoleExe`"", "`"-targetargs:$targetArgs $appVeyor -noshadow -nologo -quiet`"", "`"-filter:+[CodeCracker*]* -[CodeCracker.Test*]*`"", "`"-output:$outputXml`"", '-coverbytest:*.Test.*.dll', '-log:All', '-returntargetcode' - Exec { . $openCoverExe $arguments } - Write-Host -ForegroundColor DarkBlue "Exporting code coverage report" - Exec { . $reportGeneratorExe -verbosity:Info -reports:$outputXml -targetdir:$coverageReportDir } - if ($env:COVERALLS_REPO_TOKEN -ne $null) { - Write-Host -ForegroundColor DarkBlue "Uploading coverage report to Coveralls.io" - Exec { . $converallsNetExe --opencover $outputXml --full-sources } - } -} \ No newline at end of file diff --git a/hello.cpp b/hello.cpp new file mode 100644 index 000000000..378706961 --- /dev/null +++ b/hello.cpp @@ -0,0 +1,7 @@ +#include + +int main() +{ + std::cout << "Hello, World!" << endl; + return 0; +} diff --git a/nuget.config b/nuget.config index bbd4db734..f42466e87 100644 --- a/nuget.config +++ b/nuget.config @@ -1,13 +1,15 @@  - - + + + - - + + + \ No newline at end of file diff --git a/psake.ps1 b/psake.ps1 deleted file mode 100644 index ac4218572..000000000 --- a/psake.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -Import-Module $PSScriptRoot\packages\psake.4.4.2\tools\psake.psm1 -force -Invoke-Expression("Invoke-psake -framework '4.5.1' build.targets.ps1 " + $MyInvocation.UnboundArguments -join " ") -exit !($psake.build_success) \ No newline at end of file diff --git a/psakefile.ps1 b/psakefile.ps1 new file mode 100644 index 000000000..146b33f54 --- /dev/null +++ b/psakefile.ps1 @@ -0,0 +1,310 @@ +Properties { + $rootDir = Split-Path $psake.build_script_file + $solutionFileCS = "$rootDir\CodeCracker.CSharp.sln" + $solutionFileVB = "$rootDir\CodeCracker.VisualBasic.sln" + $srcDir = "$rootDir\src" + $testDir = "$rootDir\test" + $isAppVeyor = $env:APPVEYOR -eq $true + $slns = ls "$rootDir\*.sln" + $packagesDir = "$rootDir\packages" + $buildNumber = [Convert]::ToInt32($env:APPVEYOR_BUILD_NUMBER).ToString("0000") + $nuspecPathCS = "$rootDir\src\CSharp\CodeCracker\CodeCracker.nuspec" + $nuspecPathVB = "$rootDir\src\VisualBasic\CodeCracker\CodeCracker.nuspec" + $nugetPackagesExe = "$packagesDir\NuGet.CommandLine.4.6.2\tools\NuGet.exe" + $nugetExe = if (Test-Path $nugetPackagesExe) { $nugetPackagesExe } else { 'nuget' } + $nupkgPathCS = "$rootDir\src\CSharp\CodeCracker.CSharp.{0}.nupkg" + $nupkgPathVB = "$rootDir\src\VisualBasic\CodeCracker.VisualBasic.{0}.nupkg" + $xunitConsoleExe = "$packagesDir\xunit.runner.console.2.3.1\tools\net452\xunit.console.x86.exe" + $openCoverExe = "$packagesDir\OpenCover.4.6.519\tools\OpenCover.Console.exe" + $dllCS = "CodeCracker.CSharp.dll" + $dllVB = "CodeCracker.VisualBasic.dll" + $dllCommon = "CodeCracker.Common.dll" + $testDllCS = "CodeCracker.Test.CSharp.dll" + $testDllVB = "CodeCracker.Test.VisualBasic.dll" + $testDirCS = "$testDir\CSharp\CodeCracker.Test\bin\Release" + $testDirVB = "$testDir\VisualBasic\CodeCracker.Test\bin\Release" + $projectDirVB = "$srcDir\VisualBasic\CodeCracker" + $projectFileVB = "$projectDirVB\CodeCracker.vbproj" + $releaseDirVB = "$projectDirVB\bin\Release" + $projectDirCS = "$srcDir\CSharp\CodeCracker" + $projectFileCS = "$projectDirCS\CodeCracker.csproj" + $releaseDirCS = "$projectDirCS\bin\Release" + $logDir = "$rootDir\log" + $outputXml = "$logDir\CodeCoverageResults.xml" + $reportGeneratorExe = "$packagesDir\ReportGenerator.3.1.2\tools\ReportGenerator.exe" + $coverageReportDir = "$logDir\codecoverage\" + $coverallsNetExe = "$packagesDir\coveralls.io.1.4.2\tools\coveralls.net.exe" + $ilmergeExe = "$packagesDir\ilmerge.2.14.1208\tools\ILMerge.exe" + $isRelease = $isAppVeyor -and (($env:APPVEYOR_REPO_BRANCH -eq "release") -or ($env:APPVEYOR_REPO_TAG -eq "true")) + $isPullRequest = $env:APPVEYOR_PULL_REQUEST_NUMBER -ne $null + $tempDir = Join-Path "$([System.IO.Path]::GetTempPath())" "CodeCracker" + # msbuild hack necessary until https://github.com/psake/psake/issues/201 is fixed: + $msbuild64 = Resolve-Path "$(if (${env:ProgramFiles(x86)}) { ${env:ProgramFiles(x86)} } else { $env:ProgramFiles } )\Microsoft Visual Studio\2017\*\MSBuild\15.0\Bin\msbuild.exe" + $msbuild32 = Resolve-Path "$(if (${env:ProgramFiles(x86)}) { ${env:ProgramFiles(x86)} } else { $env:ProgramFiles } )\Microsoft Visual Studio\2017\*\MSBuild\15.0\Bin\msbuild.exe" + $msbuild = if ($msbuild64) { $msbuild64 } else { $msbuild32 } +} + +FormatTaskName (("-"*25) + "[{0}]" + ("-"*25)) + +Task Default -Depends Build, Test + +Task Rebuild -Depends Clean, Build + +Task Restore { + Foreach($sln in $slns) { + RestorePkgs $sln + } +} + +Task Prepare-Build -depends Restore, Update-Nuspec + +Task Build -depends Prepare-Build, Build-Only +Task Build-CS -depends Prepare-Build, Build-Only-CS +Task Build-VB -depends Prepare-Build, Build-Only-VB + +Task Build-Only -depends Build-Only-CS, Build-Only-VB +Task Build-Only-CS -depends Build-MSBuild-CS, ILMerge-CS +Task Build-MSBuild-CS { + if ($isAppVeyor) { + Exec { . $msbuild $solutionFileCS /m /verbosity:minimal /p:Configuration=ReleaseNoVsix /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" } + } else { + Exec { . $msbuild $solutionFileCS /m /verbosity:minimal /p:Configuration=ReleaseNoVsix } + } +} +Task Build-Only-VB -depends Build-MSBuild-VB, ILMerge-VB +Task Build-MSBuild-VB { + if ($isAppVeyor) { + Exec { . $msbuild $solutionFileVB /m /verbosity:minimal /p:Configuration=ReleaseNoVsix /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" } + } else { + Exec { . $msbuild $solutionFileVB /m /verbosity:minimal /p:Configuration=ReleaseNoVsix } + } +} + +Task ILMerge-VB { ILMerge $releaseDirVB $dllVB $projectFileVB $projectDirVB } +Task ILMerge-CS { ILMerge $releaseDirCS $dllCS $projectFileCS $projectDirCS } + +function ILMerge($releaseDir, $dll, $projectFile, $projectDir) { + Write-Host "IL Merge:" + $mergedDir = $tempDir + if (!(Test-Path $mergedDir)) { mkdir "$mergedDir" } + $inputDll = "$releaseDir\$dll" + $inputDllCommon = "$releaseDir\$dllCommon" + $pdbCommon = Change-Extension $inputDllCommon "pdb" + if (Test-Path $inputDllCommon) { + if ((ls $inputDllCommon).LastWriteTime -gt (ls $inputDll).LastWriteTime) { + # common is newer, but no changes on main dll + Write-Host "Common dll is newer than $inputDll, stopping IL merge." + return + } + } else { + # no common dll, can't merge + Write-Host "Can't find common dll, stopping IL merge." + return + } + $mergedDll = "$mergedDir\$dll" + [xml]$proj = cat $projectFile + $libs = @() + foreach ($ref in $proj.Project.ItemGroup.Reference.HintPath) { + $dir += [System.IO.Path]::GetDirectoryName("$projectDir\$ref") + $libs += "/lib:`"$([System.IO.Path]::GetDirectoryName("$projectDir\$ref"))`" " + } + Exec { . $ilmergeExe $libs /out:"$mergedDll" "$inputDll" "$inputDllCommon" } + $releaseMergedDir = "$releaseDir\merged" + if (!(Test-Path $releaseMergedDir)) { mkdir $releaseMergedDir | Out-Null } + cp $mergedDll "$releaseMergedDir\" -Force + Write-Host " $dll -> $releaseMergedDir\$dll" + $mergedPdb = Change-Extension $mergedDll "pdb" + cp $mergedPdb "$releaseMergedDir\" -Force + $pdb = (ls $mergedPdb).Name + Write-Host " $pdb -> $releaseMergedDir\$pdb" +} + +function Change-Extension ($filename, $extension) { + Join-Path "$([System.IO.Path]::GetDirectoryName($filename))" "$([System.IO.Path]::GetFileNameWithoutExtension($filename)).$extension" +} + +Task Clean { + Exec { . $msbuild $solutionFileCS /t:Clean /v:quiet } + Exec { . $msbuild $solutionFileVB /t:Clean /v:quiet } +} + +Task Set-Log { + if ((Test-Path $logDir) -eq $false) + { + Write-Host -ForegroundColor DarkBlue "Creating log directory $logDir" + mkdir $logDir | Out-Null + } +} + +Task Test-Acceptance -depends Test { + . "$rootDir\test\CSharp\AnalyzeCoreFx.ps1" +} + +Task Test -depends Set-Log { + RunTestWithCoverage "$testDirCS\$testDllCS", "$testDirVB\$testDllVB" +} +Task Test-VB -depends Set-Log { + RunTestWithCoverage "$testDirVB\$testDllVB" +} +Task Test-CSharp -depends Set-Log { + RunTestWithCoverage "$testDirCS\$testDllCS" +} + +Task Test-No-Coverage -depends Test-No-Coverage-CSharp, Test-No-Coverage-VB +Task Test-No-Coverage-VB { + RunTest "$testDirVB\$testDllVB" +} +Task Test-No-Coverage-CSharp { + RunTest "$testDirCS\$testDllCS" +} + +Task Update-Nuspec -precondition { return $isAppVeyor -and ($isRelease -ne $true) } -depends Update-Nuspec-CSharp, Update-Nuspec-VB +Task Update-Nuspec-CSharp -precondition { return $isAppVeyor -and ($isRelease -ne $true) } { + UpdateNuspec $nuspecPathCS "C#" +} +Task Update-Nuspec-VB -precondition { return $isAppVeyor -and ($isRelease -ne $true) } { + UpdateNuspec $nuspecPathVB "VB" +} + +Task Pack-Nuget -precondition { return $isAppVeyor } -depends Pack-Nuget-Csharp, Pack-Nuget-VB +Task Pack-Nuget-CSharp -precondition { return $isAppVeyor } { + PackNuget "C#" "$rootDir\src\CSharp" $nuspecPathCS $nupkgPathCS +} +Task Pack-Nuget-VB -precondition { return $isAppVeyor } { + PackNuget "VB" "$rootDir\src\VisualBasic" $nuspecPathVB $nupkgPathVB +} +Task Pack-Nuget-Force -depends Pack-Nuget-Csharp-Force, Pack-Nuget-VB-Force +Task Pack-Nuget-Csharp-Force { + PackNuget "C#" "$rootDir\src\CSharp" $nuspecPathCS $nupkgPathCS +} +Task Pack-Nuget-VB-Force { + PackNuget "VB" "$rootDir\src\VisualBasic" $nuspecPathVB $nupkgPathVB +} + +Task Count-Analyzers { + $count = $(ls $rootDir\src\*.cs -Recurse | ? { $_.Name.contains('Analyzer') } | ? { !((cat $_) -match 'abstract class') }).count + Write-Host "Found $count C# Analyzers" + $count = $(ls $rootDir\src\*.cs -Recurse | ? { $_.Name.contains('CodeFix') } | ? { !((cat $_) -match 'abstract class') }).count + Write-Host "Found $count C# Code Fixes" + $count = $(ls $rootDir\src\*.cs -Recurse | ? { $_.Name.contains('FixAll') } | ? { !((cat $_) -match 'abstract class') }).count + Write-Host "Found $count C# Code Fixes All" + $count = $(ls $rootDir\src\*.vb -Recurse | ? { $_.Name.contains('Analyzer') } | ? { !((cat $_) -match 'mustinherit class') }).count + Write-Host "Found $count VB Analyzers" + $count = $(ls $rootDir\src\*.vb -Recurse | ? { $_.Name.contains('CodeFix') } | ? { !((cat $_) -match 'mustinherit class') }).count + Write-Host "Found $count VB Code Fixes" + $count = $(ls $rootDir\src\*.vb -Recurse | ? { $_.Name.contains('FixAll') } | ? { !((cat $_) -match 'mustinherit class') }).count + Write-Host "Found $count VB Code Fixes All" +} + +Task Update-ChangeLog { + # invoke-psake default.ps1 -tasklist update-changelog -parameters @{"token"=""} + echo $token + return + Exec { + github_changelog_generator code-cracker/code-cracker --no-pull-requests --no-issues-wo-labels --exclude-labels "Can't repro","update readme",decision,docs,duplicate,question,invalid,wontfix,Duplicate,Question,Invalid,Wontfix -t $token + } +} + +Task Echo { echo echo } + +function PackNuget($language, $dir, $nuspecFile, $nupkgFile) { + Write-Host "Packing nuget for $language..." + [xml]$xml = cat "$nuspecFile" + $nupkgFile = $nupkgFile -f $xml.package.metadata.version + . $nugetExe pack "$nuspecFile" -OutputDirectory "$dir" + $nuspecFileName = (ls $nuspecFile).Name + Write-Host " $nuspecFileName ($language/$($xml.package.metadata.version)) -> $nupkgFile" + if ($isAppVeyor) { + Write-Host "Pushing nuget artifact for $language..." + appveyor PushArtifact $nupkgFile + Write-Host "Nupkg pushed for $language!" + } +} + +function UpdateNuspec($nuspecPath, $language) { + write-host "Updating version in nuspec file for $language to $buildNumber" + [xml]$xml = cat $nuspecPath + $xml.package.metadata.version+="-z$buildNumber" + write-host "Nuspec version will be $($xml.package.metadata.version)" + $xml.Save($nuspecPath) + write-host "Nuspec saved for $language!" +} + +function RestorePkgs($sln) { + Write-Host "Restoring $sln..." -ForegroundColor Green + Retry { + . $nugetExe restore "$sln" -NonInteractive -ConfigFile "$rootDir\nuget.config" + if ($LASTEXITCODE) { throw "Nuget restore for $sln failed." } + } +} + +function Retry { + Param ( + [parameter(Position=0,Mandatory=1)] + [ScriptBlock]$cmd, + [parameter(Position=1,Mandatory=0)] + [int]$times = 3 + ) + $retrycount = 0 + while ($retrycount -lt $times){ + try { + & $cmd + if (!$?) { + throw "Command failed." + } + return + } + catch { + Write-Host -ForegroundColor Red "Failed: ($($_.Exception.Message)), retrying." + } + $retrycount++ + } + throw "Command '$($cmd.ToString())' failed." +} + +function TestPath($paths) { + $notFound = @() + foreach($path in $paths) { + if ((Test-Path $path) -eq $false) + { + $notFound += $path + } + } + $notFound +} + +function RunTest($fullTestDllPath) { + if ($isAppVeyor) { + . $xunitConsoleExe $fullTestDllPath -appveyor -nologo -quiet + } else { + . $xunitConsoleExe $fullTestDllPath -nologo -quiet + } +} + +function RunTestWithCoverage($fullTestDllPaths) { + $notFoundPaths = TestPath $openCoverExe, $xunitConsoleExe, $reportGeneratorExe + if ($notFoundPaths.length -ne 0) { + Write-Host -ForegroundColor DarkRed "Paths not found: " + foreach($path in $notFoundPaths) { + Write-Host -ForegroundColor DarkRed " $path" + } + throw "Paths for test executables not found" + } + $targetArgs = "" + Foreach($fullTestDllPath in $fullTestDllPaths) { + $targetArgs += $fullTestDllPath + " " + } + $targetArgs = $targetArgs.Substring(0, $targetArgs.Length - 1) + $appVeyor = "" + if ($isAppVeyor) { + $appVeyor = " -appveyor" + } + $arguments = '-register:user', "`"-target:$xunitConsoleExe`"", "`"-targetargs:$targetArgs $appVeyor -noshadow -parallel none -nologo`"", "`"-filter:+[CodeCracker*]* -[CodeCracker.Test*]*`"", "`"-output:$outputXml`"", '-coverbytest:*.Test.*.dll', '-log:All', '-returntargetcode' + Exec { . $openCoverExe $arguments } + Write-Host -ForegroundColor DarkBlue "Exporting code coverage report" + Exec { . $reportGeneratorExe -verbosity:Info -reports:$outputXml -targetdir:$coverageReportDir } + if ($env:COVERALLS_REPO_TOKEN -ne $null) { + Write-Host -ForegroundColor DarkBlue "Uploading coverage report to Coveralls.io" + Exec { . $coverallsNetExe --opencover $outputXml --full-sources } + } +} \ No newline at end of file diff --git a/runTestsCS.ps1 b/runTestsCS.ps1 index 324d6ed85..930bdccb4 100644 --- a/runTestsCS.ps1 +++ b/runTestsCS.ps1 @@ -1,33 +1,20 @@ param([String]$testClass) -$Global:lastRun = $lastRun = [System.DateTime]::Now $testDllDirPath = "$PSScriptRoot\test\CSharp\CodeCracker.Test\bin\Debug\" $testDllFileName = "CodeCracker.Test.CSharp.dll" -$Global:testDllFullFileName = "$testDllDirPath$testDllFileName" -$Global:xunitConsole = "$PSScriptRoot\packages\xunit.runner.console.2.0.0\tools\xunit.console.x86.exe" +$testDllFullFileName = "$testDllDirPath$testDllFileName" +$xunitConsole = "$PSScriptRoot\packages\xunit.runner.console.2.2.0\tools\xunit.console.x86.exe" -if ($testClass -eq "now"){ - . $Global:xunitConsole "$Global:testDllFullFileName" +if (!(gcm nodemon -ErrorAction Ignore)) { + Write-Host -ForegroundColor DarkRed 'Nodemon not found, install it with npm: `npm i -g nodemon`' return } -function global:DebounceXunit { - try { - if (([System.DateTime]::Now - $script:lastRun).TotalMilliseconds -lt 2000) { - return - } - $Global:lastRun = [System.DateTime]::Now - If ($Global:testClass) { - Start-Process $Global:xunitConsole -ArgumentList "`"$Global:testDllFullFileName`" -class $Global:testClass" -NoNewWindow - } Else { - Start-Process $Global:xunitConsole -ArgumentList "`"$Global:testDllFullFileName`"" -NoNewWindow - } - } - catch - { - Write-Host $_.Exception.Message - } +if ($testClass -eq "now"){ + . $xunitConsole "$testDllFullFileName" + return } + Write-Host "Watching $testDllDirPath" If ($testClass) { If ($testClass.StartsWith("CodeCracker.Test") -eq $false) { @@ -35,21 +22,9 @@ If ($testClass) { } Write-Host "Only for $testClass" } -$Global:testClass = $testClass -try { - $watcher = New-Object System.IO.FileSystemWatcher - $watcher.Path = $testDllDirPath - $watcher.Filter = $testDllFileName - $watcher.IncludeSubdirectories = $false - $watcher.EnableRaisingEvents = $true - $watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite - $changed = Register-ObjectEvent $watcher "Changed" -Action { DebounceXunit } -} -catch { - Write-Host $_.Exception.Message -} -#if we do that, then we don't get any console output: -#Write-Host "Press any key to continue ..." -#[System.Console]::ReadKey() -#Unregister-Event $changed.Id +If ($testClass) { + nodemon --watch $testDllFullFileName --exec "`"$xunitConsole`" `"$testDllFullFileName`" -class $testClass || exit 1" +} Else { + nodemon --watch $testDllFullFileName --exec "`"$xunitConsole`" `"$testDllFullFileName`" || exit 1" +} \ No newline at end of file diff --git a/runTestsVB.ps1 b/runTestsVB.ps1 index 0e21f458a..b2d53d619 100644 --- a/runTestsVB.ps1 +++ b/runTestsVB.ps1 @@ -1,33 +1,20 @@ param([String]$testClass) -$Global:lastRun = $lastRun = [System.DateTime]::Now $testDllDirPath = "$PSScriptRoot\test\VisualBasic\CodeCracker.Test\bin\Debug\" $testDllFileName = "CodeCracker.Test.VisualBasic.dll" -$Global:testDllFullFileName = "$testDllDirPath$testDllFileName" -$Global:xunitConsole = "$PSScriptRoot\packages\xunit.runner.console.2.0.0\tools\xunit.console.x86.exe" +$testDllFullFileName = "$testDllDirPath$testDllFileName" +$xunitConsole = "$PSScriptRoot\packages\xunit.runner.console.2.2.0\tools\xunit.console.x86.exe" -if ($testClass -eq "now"){ - . $Global:xunitConsole "$Global:testDllFullFileName" +if (!(gcm nodemon -ErrorAction Ignore)) { + Write-Host -ForegroundColor DarkRed 'Nodemon not found, install it with npm: `npm i -g nodemon`' return } -function global:DebounceXunit { - try { - if (([System.DateTime]::Now - $script:lastRun).TotalMilliseconds -lt 2000) { - return - } - $Global:lastRun = [System.DateTime]::Now - If ($Global:testClass) { - Start-Process $Global:xunitConsole -ArgumentList "`"$Global:testDllFullFileName`" -class $Global:testClass" -NoNewWindow - } Else { - Start-Process $Global:xunitConsole -ArgumentList "`"$Global:testDllFullFileName`"" -NoNewWindow - } - } - catch - { - Write-Host $_.Exception.Message - } +if ($testClass -eq "now"){ + . $xunitConsole "$testDllFullFileName" + return } + Write-Host "Watching $testDllDirPath" If ($testClass) { If ($testClass.StartsWith("CodeCracker.Test") -eq $false) { @@ -35,21 +22,9 @@ If ($testClass) { } Write-Host "Only for $testClass" } -$Global:testClass = $testClass -try { - $watcher = New-Object System.IO.FileSystemWatcher - $watcher.Path = $testDllDirPath - $watcher.Filter = $testDllFileName - $watcher.IncludeSubdirectories = $false - $watcher.EnableRaisingEvents = $true - $watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite - $changed = Register-ObjectEvent $watcher "Changed" -Action { DebounceXunit } -} -catch { - Write-Host $_.Exception.Message +If ($testClass) { + nodemon --watch $testDllFullFileName --exec "`"$xunitConsole`" `"$testDllFullFileName`" -class $testClass || exit 1" +} Else { + nodemon --watch $testDllFullFileName --exec "`"$xunitConsole`" `"$testDllFullFileName`" || exit 1" } -#if we do that, then we don't get any console output: -#Write-Host "Press any key to continue ..." -#[System.Console]::ReadKey() -#Unregister-Event $changed.Id diff --git a/src/CSharp/CodeCracker.Vsix/CodeCracker.Vsix.Debug.csproj b/src/CSharp/CodeCracker.Vsix/CodeCracker.Vsix.Debug.csproj new file mode 100644 index 000000000..a16c94bc4 --- /dev/null +++ b/src/CSharp/CodeCracker.Vsix/CodeCracker.Vsix.Debug.csproj @@ -0,0 +1,88 @@ + + + + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + Debug + AnyCPU + 2.0 + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {E3B0C133-B97E-46B9-809D-16BB762EF74F} + Library + Properties + CodeCracker + CodeCracker.CSharp + v4.5.2 + false + false + false + false + false + false + Roslyn + + + true + full + false + debug\bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + debug\bin\Release\ + TRACE + prompt + 4 + + + Program + $(DevEnvDir)devenv.exe + /rootsuffix Roslyn + + + + Designer + + + + + {753d4757-fcba-43ba-b1be-89201acda192} + CodeCracker.Common + + + {FF1097FB-A890-461B-979E-064697891B96} + CodeCracker + + + + + Always + true + + + Always + true + + + Always + true + + + + + + \ No newline at end of file diff --git a/src/CSharp/CodeCracker.Vsix/CodeCracker.Vsix.csproj b/src/CSharp/CodeCracker.Vsix/CodeCracker.Vsix.csproj index 05f70110b..94bd31424 100644 --- a/src/CSharp/CodeCracker.Vsix/CodeCracker.Vsix.csproj +++ b/src/CSharp/CodeCracker.Vsix/CodeCracker.Vsix.csproj @@ -1,7 +1,7 @@  - + - 14.0 + 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/src/CSharp/CodeCracker.Vsix/LICENSE.txt b/src/CSharp/CodeCracker.Vsix/LICENSE.txt index 9d626bb93..2717e6ad3 100644 --- a/src/CSharp/CodeCracker.Vsix/LICENSE.txt +++ b/src/CSharp/CodeCracker.Vsix/LICENSE.txt @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2014-2015 Giovanni Bassi and Elemar Jr. + Copyright 2014-2018 Giovanni Bassi and Elemar Jr. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/CSharp/CodeCracker.Vsix/debug/source.extension.vsixmanifest b/src/CSharp/CodeCracker.Vsix/debug/source.extension.vsixmanifest new file mode 100644 index 000000000..e0b59f52d --- /dev/null +++ b/src/CSharp/CodeCracker.Vsix/debug/source.extension.vsixmanifest @@ -0,0 +1,34 @@ + + + + + Code Cracker for C# + An analyzer library for C# that uses Roslyn to produce refactorings, code analysis, and other niceties. +Check the official project site on code-cracker.github.io. +Build status Nuget count Nuget downloads Issues open +This is a community project, free and open source. Everyone is invited to contribute, fork, share and use the code. No money shall be charged by this software, nor it will be. Ever. + http://code-cracker.github.io/ + LICENSE.txt + http://code-cracker.github.io/changelog.html + codecrackerlogo.png + ccexample.png + ReSharper, Refactoring, code analysis, analyzer, CodeRush, roslyn, roslyn c#, Refactoring c# Roslyn + + + + + + + + + + + + + + + + + + + diff --git a/src/CSharp/CodeCracker.Vsix/source.extension.vsixmanifest b/src/CSharp/CodeCracker.Vsix/source.extension.vsixmanifest index f82634412..a495c36ac 100644 --- a/src/CSharp/CodeCracker.Vsix/source.extension.vsixmanifest +++ b/src/CSharp/CodeCracker.Vsix/source.extension.vsixmanifest @@ -1,14 +1,15 @@  - - Code Cracker for C# + + Code Cracker for C# (2019) An analyzer library for C# that uses Roslyn to produce refactorings, code analysis, and other niceties. Check the official project site on code-cracker.github.io. Build status Nuget count Nuget downloads Issues open This is a community project, free and open source. Everyone is invited to contribute, fork, share and use the code. No money shall be charged by this software, nor it will be. Ever. http://code-cracker.github.io/ LICENSE.txt + http://code-cracker.github.io/changelog.html codecrackerlogo.png ccexample.png ReSharper, Refactoring, code analysis, analyzer, CodeRush, roslyn, roslyn c#, Refactoring c# Roslyn @@ -25,4 +26,8 @@ This is a community project, free and open source. Everyone is invited to contri + + + + diff --git a/src/CSharp/CodeCracker/CodeCracker.csproj b/src/CSharp/CodeCracker/CodeCracker.csproj index 815308e38..d81e8eee9 100644 --- a/src/CSharp/CodeCracker/CodeCracker.csproj +++ b/src/CSharp/CodeCracker/CodeCracker.csproj @@ -1,5 +1,5 @@  - + 11.0 @@ -10,9 +10,11 @@ Properties CodeCracker.CSharp CodeCracker.CSharp + CodeCracker.CSharp.NewIdRequiredDueToNuGetBug {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Profile7 v4.5 + AD0001,RS1010,RS1016,RS1017,RS1022 true @@ -33,6 +35,15 @@ prompt 4 + + win + true + + + + + + @@ -45,8 +56,11 @@ + + + @@ -63,22 +77,29 @@ + + + + - - - + + + + + + @@ -111,8 +132,6 @@ - - @@ -133,9 +152,11 @@ + + @@ -195,7 +216,6 @@ Designer PreserveNewest - PreserveNewest @@ -217,69 +237,5 @@ CodeCracker.Common - - - - - - - - - ..\..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll - False - - - ..\..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dll - False - - - ..\..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.Workspaces.dll - False - - - ..\..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.Workspaces.dll - False - - - ..\..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll - False - - - ..\..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll - False - - - - - - OnOutputUpdated - - \ No newline at end of file diff --git a/src/CSharp/CodeCracker/CodeCracker.nuspec b/src/CSharp/CodeCracker/CodeCracker.nuspec index 1638208e5..91e3f5c70 100644 --- a/src/CSharp/CodeCracker/CodeCracker.nuspec +++ b/src/CSharp/CodeCracker/CodeCracker.nuspec @@ -2,8 +2,8 @@ codecracker.CSharp - 1.0.0-rc3 - CodeCracker + 1.1.0 + CodeCracker for C# giggio,elemarjr,carloscds giggio,elemarjr,carloscds https://github.com/code-cracker/code-cracker/blob/master/LICENSE.txt @@ -13,15 +13,16 @@ A analyzer library for C# that uses Roslyn to produce refactorings, code analysis, and other niceties. This is a community project, free and open source. Everyone is invited to contribute, fork, share and use the code. No money shall be charged by this software, nor it will be. Ever. - Third alpha release - Copyright CodeCracker 2014-2015 + See https://github.com/code-cracker/code-cracker/blob/master/CHANGELOG.md + Copyright CodeCracker 2014-2016 roslyn, analyzers + true - + diff --git a/src/CSharp/CodeCracker/Design/CatchEmptyAnalyzer.cs b/src/CSharp/CodeCracker/Design/CatchEmptyAnalyzer.cs index 8022e180a..82e455d7f 100644 --- a/src/CSharp/CodeCracker/Design/CatchEmptyAnalyzer.cs +++ b/src/CSharp/CodeCracker/Design/CatchEmptyAnalyzer.cs @@ -3,16 +3,17 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; +using System.Linq; namespace CodeCracker.CSharp.Design { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class CatchEmptyAnalyzer : DiagnosticAnalyzer { - internal const string Title = "Your catch maybe include some Exception"; + internal const string Title = "Your catch should include an Exception"; internal const string MessageFormat = "{0}"; internal const string Category = SupportedCategories.Design; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.CatchEmpty.ToDiagnosticId(), Title, MessageFormat, @@ -32,9 +33,16 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) var catchStatement = (CatchClauseSyntax)context.Node; if (catchStatement == null || catchStatement.Declaration != null) return; - if (catchStatement.Block?.Statements.Count == 0) return; // there is another analizer for this: EmptyCatchBlock + if (catchStatement.Block?.Statements.Count == 0) return; // there is another analyzer for this: EmptyCatchBlock - var diagnostic = Diagnostic.Create(Rule, catchStatement.GetLocation(), "Consider put an Exception Class in catch."); + if (catchStatement.Block != null) + { + // Allow empty catch when the block ends with a throw. + var controlFlow = context.SemanticModel.AnalyzeControlFlow(catchStatement.Block); + if (!controlFlow.EndPointIsReachable && + controlFlow.ExitPoints.All(i => i.IsKind(SyntaxKind.ThrowStatement))) return; + } + var diagnostic = Diagnostic.Create(Rule, catchStatement.GetLocation(), "Consider adding an Exception to the catch."); context.ReportDiagnostic(diagnostic); } } diff --git a/src/CSharp/CodeCracker/Design/CopyEventToVariableBeforeFireAnalyzer.cs b/src/CSharp/CodeCracker/Design/CopyEventToVariableBeforeFireAnalyzer.cs deleted file mode 100644 index aaa6b1fee..000000000 --- a/src/CSharp/CodeCracker/Design/CopyEventToVariableBeforeFireAnalyzer.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using System; -using System.Collections.Immutable; - -namespace CodeCracker.CSharp.Design -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class CopyEventToVariableBeforeFireAnalyzer : DiagnosticAnalyzer - { - internal const string Title = "Copy Event To Variable Before Fire"; - internal const string MessageFormat = "Copy the '{0}' event to a variable before fire it."; - internal const string Category = SupportedCategories.Design; - const string Description = "Events should always be checked for null before being invoked.\r\n" - + "As in a multi-threading context it is possible for an event to be unsuscribed between " - + "the moment where it is checked to be non-null and the moment it is raised the event must " - + "be copied to a temporary variable before the check."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( - DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId(), - Title, - MessageFormat, - Category, - DiagnosticSeverity.Warning, - true, - description: Description, - helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.CopyEventToVariableBeforeFire)); - - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - - public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(Analyzer, SyntaxKind.InvocationExpression); - - private static void Analyzer(SyntaxNodeAnalysisContext context) - { - if (context.IsGenerated()) return; - var invocation = (InvocationExpressionSyntax)context.Node; - var identifier = invocation.Expression as IdentifierNameSyntax; - if (identifier == null) return; - if (context.Node.Parent.GetType().Name == nameof(ArrowExpressionClauseSyntax)) return; - - var typeInfo = context.SemanticModel.GetTypeInfo(identifier, context.CancellationToken); - - if (typeInfo.ConvertedType?.BaseType == null) return; - - var symbol = context.SemanticModel.GetSymbolInfo(identifier).Symbol; - - if (typeInfo.ConvertedType.BaseType.Name != typeof(MulticastDelegate).Name || symbol is ILocalSymbol || symbol is IParameterSymbol) return; - - context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation(), identifier.Identifier.Text)); - } - } -} \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Design/CopyEventToVariableBeforeFireCodeFixProvider.cs b/src/CSharp/CodeCracker/Design/CopyEventToVariableBeforeFireCodeFixProvider.cs deleted file mode 100644 index 88380df5b..000000000 --- a/src/CSharp/CodeCracker/Design/CopyEventToVariableBeforeFireCodeFixProvider.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Formatting; -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace CodeCracker.CSharp.Design -{ - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CopyEventToVariableBeforeFireCodeFixProvider)), Shared] - public class CopyEventToVariableBeforeFireCodeFixProvider : CodeFixProvider - { - private const string SyntaxAnnotatinKind = "CC-CopyEvent"; - - public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId()); - - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) - { - var diagnostic = context.Diagnostics.First(); - context.RegisterCodeFix( - CodeAction.Create("Copy event reference to a variable", ct => CreateVariableAsync(context.Document, diagnostic, ct), nameof(CopyEventToVariableBeforeFireCodeFixProvider)), diagnostic); - return Task.FromResult(0); - } - - private async static Task CreateVariableAsync(Document document, Diagnostic diagnostic, CancellationToken ct) - { - var root = await document.GetSyntaxRootAsync(ct).ConfigureAwait(false); - var sourceSpan = diagnostic.Location.SourceSpan; - var invocation = root.FindToken(sourceSpan.Start).Parent.AncestorsAndSelf().OfType().First(); - const string handlerName = "handler"; - var variable = - SyntaxFactory.LocalDeclarationStatement( - SyntaxFactory.VariableDeclaration( - SyntaxFactory.ParseTypeName("var"), - SyntaxFactory.SeparatedList( - new[] - { - SyntaxFactory.VariableDeclarator( - SyntaxFactory.Identifier(handlerName), - null, - SyntaxFactory.EqualsValueClause(invocation.Expression.WithoutLeadingTrivia().WithoutTrailingTrivia())) - }))) - .WithLeadingTrivia(invocation.Parent.GetLeadingTrivia()); - var newInvocation = - SyntaxFactory.IfStatement( - SyntaxFactory.BinaryExpression(SyntaxKind.NotEqualsExpression, - SyntaxFactory.IdentifierName(handlerName), - SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)), - SyntaxFactory.ExpressionStatement( - SyntaxFactory.InvocationExpression( - SyntaxFactory.IdentifierName(handlerName), - invocation.ArgumentList))) - .WithTrailingTrivia(invocation.Parent.GetTrailingTrivia()); - var oldNode = invocation.Parent; - var newNode = invocation.Parent.WithAdditionalAnnotations(new SyntaxAnnotation(SyntaxAnnotatinKind)); - if (oldNode.Parent.IsEmbeddedStatementOwner()) - newNode = SyntaxFactory.Block((StatementSyntax)newNode); - var newRoot = root.ReplaceNode(oldNode, newNode); - newRoot = newRoot.InsertNodesAfter(GetMark(newRoot), new SyntaxNode[] { variable, newInvocation }); - newRoot = newRoot.RemoveNode(GetMark(newRoot), SyntaxRemoveOptions.KeepNoTrivia); - return document.WithSyntaxRoot(newRoot.WithAdditionalAnnotations(Formatter.Annotation)); - } - - private static SyntaxNode GetMark(SyntaxNode node) => - node.DescendantNodes().First(n => n.GetAnnotations(SyntaxAnnotatinKind).Any()); - } -} \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Design/EmptyCatchBlockAnalyzer.cs b/src/CSharp/CodeCracker/Design/EmptyCatchBlockAnalyzer.cs index 43e1d02b1..f77e42f85 100644 --- a/src/CSharp/CodeCracker/Design/EmptyCatchBlockAnalyzer.cs +++ b/src/CSharp/CodeCracker/Design/EmptyCatchBlockAnalyzer.cs @@ -3,19 +3,20 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; +using CodeCracker.Properties; namespace CodeCracker.CSharp.Design { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class EmptyCatchBlockAnalyzer : DiagnosticAnalyzer { - internal const string Title = "Catch block cannot be empty"; + internal static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.EmptyCatchBlockAnalyzer_Title), Resources.ResourceManager, typeof(Resources)); internal const string MessageFormat = "{0}"; internal const string Category = SupportedCategories.Design; - const string Description = "An empty catch block suppress all errors and shouldn't be used.\r\n" - +"If the error is expected consider logging it or changing the control flow such that it is explicit."; + internal static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.EmptyCatchBlockAnalyzer_Description), Resources.ResourceManager, typeof(Resources)); + internal static readonly LocalizableString Message = new LocalizableResourceString(nameof(Resources.EmptyCatchBlockAnalyzer_Message), Resources.ResourceManager, typeof(Resources)); - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId.EmptyCatchBlock.ToDiagnosticId(), + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId.EmptyCatchBlock.ToDiagnosticId(), Title, MessageFormat, Category, @@ -34,7 +35,7 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) if (context.IsGenerated()) return; var catchStatement = (CatchClauseSyntax)context.Node; if (catchStatement?.Block?.Statements.Count != 0) return; - var diagnostic = Diagnostic.Create(Rule, catchStatement.GetLocation(), "Empty Catch Block."); + var diagnostic = Diagnostic.Create(Rule, catchStatement.GetLocation(), Message); context.ReportDiagnostic(diagnostic); } } diff --git a/src/CSharp/CodeCracker/Design/EmptyCatchBlockCodeFixProvider.cs b/src/CSharp/CodeCracker/Design/EmptyCatchBlockCodeFixProvider.cs index 5ca79d356..36eeba3d3 100644 --- a/src/CSharp/CodeCracker/Design/EmptyCatchBlockCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Design/EmptyCatchBlockCodeFixProvider.cs @@ -9,32 +9,61 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using CodeCracker.Properties; namespace CodeCracker.CSharp.Design { [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(EmptyCatchBlockCodeFixProvider)), Shared] public class EmptyCatchBlockCodeFixProvider : CodeFixProvider { + internal static readonly LocalizableString FixRemoveEmptyCatchBlock = new LocalizableResourceString(nameof(Resources.EmptyCatchBlockCodeFixProvider_Remove), Resources.ResourceManager, typeof(Resources)); + internal static readonly LocalizableString FixRemoveEmptyCatchBlockAndPutDocumentationLink = new LocalizableResourceString(nameof(Resources.EmptyCatchBlockCodeFixProvider_RemoveAndDocumentation), Resources.ResourceManager, typeof(Resources)); + internal static readonly LocalizableString FixInsertExceptionClass = new LocalizableResourceString(nameof(Resources.EmptyCatchBlockCodeFixProvider_InsertException), Resources.ResourceManager, typeof(Resources)); + internal static readonly LocalizableString FixRemoveTry = new LocalizableResourceString(nameof(Resources.EmptyCatchBlockCodeFixProvider_RemoveTry), Resources.ResourceManager, typeof(Resources)); + public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.EmptyCatchBlock.ToDiagnosticId()); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var diagnostic = context.Diagnostics.First(); - context.RegisterCodeFix(CodeAction.Create("Remove Empty Catch Block", c => RemoveEmptyCatchBlockAsync(context.Document, diagnostic, c), nameof(EmptyCatchBlockCodeFixProvider) + nameof(RemoveEmptyCatchBlockAsync)), diagnostic); - context.RegisterCodeFix(CodeAction.Create("Remove Empty Catch Block and Put a Documentation Link about Try...Catch use", c => RemoveEmptyCatchBlockPutCommentAsync(context.Document, diagnostic, c), nameof(EmptyCatchBlockCodeFixProvider ) + nameof(RemoveEmptyCatchBlockPutCommentAsync)), diagnostic); - context.RegisterCodeFix(CodeAction.Create("Insert Exception class to Catch", c => InsertExceptionClassCommentAsync(context.Document, diagnostic, c), nameof(EmptyCatchBlockCodeFixProvider) + nameof(InsertExceptionClassCommentAsync)), diagnostic); - return Task.FromResult(0); + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var diagnosticSpan = diagnostic.Location.SourceSpan; + var catchStatement = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); + var tryStatement = (TryStatementSyntax)catchStatement.Parent; + + if (tryStatement.Catches.Count > 1) + { + context.RegisterCodeFix(CodeAction.Create(FixRemoveEmptyCatchBlock.ToString(), c => RemoveEmptyCatchBlockAsync(context.Document, diagnostic, c), nameof(EmptyCatchBlockCodeFixProvider) + nameof(RemoveEmptyCatchBlockAsync)), diagnostic); + } + else + { + context.RegisterCodeFix(CodeAction.Create(FixRemoveTry.ToString(), c => RemoveEmptyTryBlockAsync(context.Document, diagnostic, c), nameof(EmptyCatchBlockCodeFixProvider) + nameof(RemoveEmptyCatchBlockAsync)), diagnostic); + context.RegisterCodeFix(CodeAction.Create(FixRemoveEmptyCatchBlockAndPutDocumentationLink.ToString(), c => RemoveEmptyCatchBlockPutCommentAsync(context.Document, diagnostic, c), nameof(EmptyCatchBlockCodeFixProvider) + nameof(RemoveEmptyCatchBlockPutCommentAsync)), diagnostic); + } + context.RegisterCodeFix(CodeAction.Create(FixInsertExceptionClass.ToString(), c => InsertExceptionClassCommentAsync(context.Document, diagnostic, c), nameof(EmptyCatchBlockCodeFixProvider) + nameof(InsertExceptionClassCommentAsync)), diagnostic); } - private async static Task RemoveEmptyCatchBlockAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) => + private async static Task RemoveEmptyTryBlockAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) => await RemoveTryAsync(document, diagnostic, cancellationToken, insertComment: false); private async static Task RemoveEmptyCatchBlockPutCommentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) => await RemoveTryAsync(document, diagnostic, cancellationToken, insertComment: true); + private async static Task RemoveEmptyCatchBlockAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var diagnosticSpan = diagnostic.Location.SourceSpan; + var catchStatement = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); + + var newRoot = root.RemoveNode(catchStatement, SyntaxRemoveOptions.KeepNoTrivia); + var newDocument = document.WithSyntaxRoot(newRoot); + return newDocument; + + } + private async static Task RemoveTryAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken, bool insertComment) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/CSharp/CodeCracker/Design/InconsistentAccessibility/InconsistentAccessibilityCodeFixProvider.cs b/src/CSharp/CodeCracker/Design/InconsistentAccessibility/InconsistentAccessibilityCodeFixProvider.cs index d8937aca7..f99e9e991 100644 --- a/src/CSharp/CodeCracker/Design/InconsistentAccessibility/InconsistentAccessibilityCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Design/InconsistentAccessibility/InconsistentAccessibilityCodeFixProvider.cs @@ -72,6 +72,8 @@ private static async Task GetInconsistentAccessib case InconsistentAccessibilityInIndexerParameterCompilerErrorNumber: inconsistentAccessibilityProvider = new InconsistentAccessibilityInIndexerParameter(); break; + default: + break; } return await inconsistentAccessibilityProvider.GetInconsistentAccessibilityInfoAsync(document, diagnostic, cancellationToken).ConfigureAwait(false); diff --git a/src/CSharp/CodeCracker/Design/MakeMethodStaticAnalyzer.cs b/src/CSharp/CodeCracker/Design/MakeMethodStaticAnalyzer.cs index bf982204f..a5fecd666 100644 --- a/src/CSharp/CodeCracker/Design/MakeMethodStaticAnalyzer.cs +++ b/src/CSharp/CodeCracker/Design/MakeMethodStaticAnalyzer.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using System.Linq; using System.Collections.Immutable; +using System; namespace CodeCracker.CSharp.Design { @@ -17,7 +18,7 @@ public class MakeMethodStaticAnalyzer : DiagnosticAnalyzer "not creating a virtual, abstract, new or partial method, and if it is not a method override, " + "your instance method may be changed to a static method."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.MakeMethodStatic.ToDiagnosticId(), Title, MessageFormat, @@ -51,16 +52,8 @@ private static void AnalyzeMethod(SyntaxNodeAnalysisContext context) var semanticModel = context.SemanticModel; var methodSymbol = semanticModel.GetDeclaredSymbol(method); - var theClass = methodSymbol.ContainingType; - if (theClass.TypeKind == TypeKind.Interface) return; - - var interfaceMembersWithSameName = theClass.AllInterfaces.SelectMany(i => i.GetMembers(methodSymbol.Name)); - foreach (var memberSymbol in interfaceMembersWithSameName) - { - if (memberSymbol.Kind != SymbolKind.Method) continue; - var implementation = theClass.FindImplementationForInterfaceMember(memberSymbol); - if (implementation != null && implementation.Equals(methodSymbol)) return; - } + if (methodSymbol == null) return; + if (methodSymbol.IsImplementingInterface()) return; if (method.Body == null) { @@ -73,15 +66,41 @@ private static void AnalyzeMethod(SyntaxNodeAnalysisContext context) { var dataFlowAnalysis = semanticModel.AnalyzeDataFlow(method.Body); if (!dataFlowAnalysis.Succeeded) return; - if (dataFlowAnalysis.DataFlowsIn.Any(inSymbol => inSymbol.Name == "this")) return; + if (dataFlowAnalysis.DataFlowsIn.Any(inSymbol => inSymbol.Name == "this") + || dataFlowAnalysis.WrittenInside.Any(inSymbol => inSymbol.Name == "this")) return; } if (IsTestMethod(method, methodSymbol)) return; + if (IsWebFormsMethod(methodSymbol)) return; + if (IsGetEnumerator(methodSymbol)) return; + if (HasRoutedEventArgs(methodSymbol)) return; var diagnostic = Diagnostic.Create(Rule, method.Identifier.GetLocation(), method.Identifier.ValueText); context.ReportDiagnostic(diagnostic); } + private static bool HasRoutedEventArgs(IMethodSymbol methodSymbol) + { + var routedEventArgsParameters = methodSymbol.Parameters.Where(p => p.Type.ToString() == "System.Windows.RoutedEventArgs"); + return routedEventArgsParameters.Any(); + } + + private static readonly string[] webFormsMethods = { + "Application_AuthenticateRequest", "Application_BeginRequest", + "Application_End", "Application_EndRequest", + "Application_Error", "Application_Start", + "Session_End", "Session_Start" }; + private static bool IsWebFormsMethod(IMethodSymbol methodSymbol) + { + if (!webFormsMethods.Contains(methodSymbol.Name)) return false; + return (methodSymbol.ContainingType.AllBaseTypes().Any(t => t.ToString() == "System.Web.HttpApplication")); + } + + private static bool IsGetEnumerator(IMethodSymbol methodSymbol) + { + return methodSymbol.Name == "GetEnumerator" && methodSymbol.ReturnType.Name == "IEnumerator"; + } + private static bool IsTestMethod(MethodDeclarationSyntax method, IMethodSymbol methodSymbol) { var result = false; diff --git a/src/CSharp/CodeCracker/Design/MakeMethodStaticCodeFixProvider.cs b/src/CSharp/CodeCracker/Design/MakeMethodStaticCodeFixProvider.cs index bb62197d7..50f2aecf1 100644 --- a/src/CSharp/CodeCracker/Design/MakeMethodStaticCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Design/MakeMethodStaticCodeFixProvider.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; @@ -37,15 +38,15 @@ private static async Task MakeMethodStaticAsync(Document document, Dia var method = (MethodDeclarationSyntax)root.FindNode(diagnosticSpan); var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var methodSymbol = semanticModel.GetDeclaredSymbol(method); - var methodClassName = methodSymbol.ContainingType.Name; var references = await SymbolFinder.FindReferencesAsync(methodSymbol, document.Project.Solution, cancellationToken).ConfigureAwait(false); var documentGroups = references.SelectMany(r => r.Locations).GroupBy(loc => loc.Document); - var newSolution = UpdateMainDocument(document, root, method, documentGroups); - newSolution = await UpdateReferencingDocumentsAsync(document, methodClassName, documentGroups, newSolution, cancellationToken); + var fullMethodName = methodSymbol.GetFullName(); + var newSolution = await UpdateMainDocumentAsync(document, fullMethodName, root, method, documentGroups, cancellationToken); + newSolution = await UpdateReferencingDocumentsAsync(document, fullMethodName, documentGroups, newSolution, cancellationToken).ConfigureAwait(false); return newSolution; } - private static Solution UpdateMainDocument(Document document, SyntaxNode root, MethodDeclarationSyntax method, IEnumerable> documentGroups) + private static async Task UpdateMainDocumentAsync(Document document, string fullMethodName, SyntaxNode root, MethodDeclarationSyntax method, IEnumerable> documentGroups, CancellationToken cancellationToken) { var mainDocGroup = documentGroups.FirstOrDefault(dg => dg.Key.Equals(document)); SyntaxNode newRoot; @@ -55,20 +56,41 @@ private static Solution UpdateMainDocument(Document document, SyntaxNode root, M } else { + var newMemberAccess = (MemberAccessExpressionSyntax)SyntaxFactory.ParseExpression(fullMethodName); + newMemberAccess = newMemberAccess.WithExpression( + newMemberAccess.Expression.WithAdditionalAnnotations(Simplifier.Annotation)); var diagnosticNodes = mainDocGroup.Select(referenceLocation => root.FindNode(referenceLocation.Location.SourceSpan)).ToList(); newRoot = root.TrackNodes(diagnosticNodes.Union(new[] { method })); - newRoot = newRoot.ReplaceNode(newRoot.GetCurrentNode(method), method.WithoutTrivia().AddModifiers(staticToken).WithTriviaFrom(method)); + var trackedMethod = newRoot.GetCurrentNode(method); + var staticMethod = method.WithoutTrivia().AddModifiers(staticToken).WithTriviaFrom(method); + newRoot = newRoot.ReplaceNode(trackedMethod, staticMethod); foreach (var diagnosticNode in diagnosticNodes) { - var token = newRoot.FindToken(diagnosticNode.GetLocation().SourceSpan.Start); - var tokenParent = token.Parent; - if (token.Parent.IsKind(SyntaxKind.IdentifierName)) continue; - var invocationExpression = newRoot.GetCurrentNode(diagnosticNode).FirstAncestorOrSelfOfType()?.Expression; - if (invocationExpression == null || invocationExpression.IsKind(SyntaxKind.IdentifierName)) continue; - var memberAccess = invocationExpression as MemberAccessExpressionSyntax; - if (memberAccess == null) continue; - var newMemberAccessParent = memberAccess.Parent.ReplaceNode(memberAccess, memberAccess.Name) - .WithAdditionalAnnotations(Formatter.Annotation); + var tempDoc = document.WithSyntaxRoot(newRoot); + newRoot = await tempDoc.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await tempDoc.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var syntaxNode = newRoot.GetCurrentNode(diagnosticNode); + var memberAccess = syntaxNode.FirstAncestorOrSelfOfType(); + if (memberAccess?.Expression == null) continue; + if (!syntaxNode.Equals(memberAccess.Name)) continue; + var memberAccessExpressionSymbol = semanticModel.GetSymbolInfo(memberAccess.Expression).Symbol; + var containingMember = memberAccess.FirstAncestorOrSelfThatIsAMember(); + var memberSymbol = semanticModel.GetDeclaredSymbol(containingMember); + var allContainingTypes = memberSymbol.GetAllContainingTypes().ToList(); + var methodTypeSymbol = GetMethodTypeSymbol(memberAccessExpressionSymbol); + var expressionToReplaceMemberAccess = allContainingTypes.Any(t => t.Equals(methodTypeSymbol)) + // ideally we would check the symbols + // but there is a bug on Roslyn 1.0, fixed on 1.1: + // https://github.com/dotnet/roslyn/issues/3096 + // so if we try to check the method symbol, it fails and always returns null + // so if we find a name clash, whatever one, we fall back to the full name + ? allContainingTypes.Count(t => t.MemberNames.Any(n => n == memberSymbol.Name)) > 1 + ? (SyntaxNode)newMemberAccess + : memberAccess.Name + : newMemberAccess; + var newMemberAccessParent = memberAccess.Parent.ReplaceNode(memberAccess, expressionToReplaceMemberAccess) + .WithAdditionalAnnotations(Formatter.Annotation) + .WithAdditionalAnnotations(Simplifier.Annotation); newRoot = newRoot.ReplaceNode(memberAccess.Parent, newMemberAccessParent); } } @@ -76,24 +98,42 @@ private static Solution UpdateMainDocument(Document document, SyntaxNode root, M return newSolution; } - private static async Task UpdateReferencingDocumentsAsync(Document document, string methodClassName, IEnumerable> documentGroups, Solution newSolution, CancellationToken cancellationToken) + private static ISymbol GetMethodTypeSymbol(ISymbol memberAccessExpressionSymbol) + { + ISymbol methodTypeSymbol = null; + var methodSymbol = memberAccessExpressionSymbol as IMethodSymbol; + if (methodSymbol != null) + methodTypeSymbol = methodSymbol.MethodKind == MethodKind.Constructor ? methodSymbol.ReceiverType : methodSymbol.ReturnType; + var parameterSymbol = memberAccessExpressionSymbol as IParameterSymbol; + if (parameterSymbol != null) + methodTypeSymbol = parameterSymbol.Type; + return methodTypeSymbol; + } + + private static async Task UpdateReferencingDocumentsAsync(Document document, string fullMethodName, IEnumerable> documentGroups, Solution newSolution, CancellationToken cancellationToken) { - var methodIdentifier = SyntaxFactory.IdentifierName(methodClassName); + var newMemberAccess = (MemberAccessExpressionSyntax)SyntaxFactory.ParseExpression(fullMethodName); + newMemberAccess = newMemberAccess.WithExpression( + newMemberAccess.Expression.WithAdditionalAnnotations(Simplifier.Annotation)); foreach (var documentGroup in documentGroups) { var referencingDocument = documentGroup.Key; - if (referencingDocument.Equals(document)) continue; - var newReferencingRoot = await referencingDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var diagnosticNodes = documentGroup.Select(referenceLocation => newReferencingRoot.FindNode(referenceLocation.Location.SourceSpan)).ToList(); - newReferencingRoot = newReferencingRoot.TrackNodes(diagnosticNodes); + if (referencingDocument.Id.Equals(document.Id)) continue; + referencingDocument = newSolution.GetDocument(referencingDocument.Id); + var root = await referencingDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var diagnosticNodes = documentGroup.Select(referenceLocation => root.FindNode(referenceLocation.Location.SourceSpan)).ToList(); + root = root.TrackNodes(diagnosticNodes); foreach (var diagnosticNode in diagnosticNodes) { - var memberAccess = (MemberAccessExpressionSyntax)newReferencingRoot.GetCurrentNode(diagnosticNode).FirstAncestorOrSelfOfType().Expression; - var newMemberAccess = memberAccess.ReplaceNode(memberAccess.Expression, methodIdentifier) + var trackedNode = root.GetCurrentNode(diagnosticNode); + var memberAccess = trackedNode.FirstAncestorOrSelfOfType(); + if (memberAccess?.Expression == null) continue; + if (!trackedNode.Equals(memberAccess.Name)) continue; + var newMemberAccessParent = memberAccess.Parent.ReplaceNode(memberAccess, newMemberAccess) .WithAdditionalAnnotations(Formatter.Annotation); - newReferencingRoot = newReferencingRoot.ReplaceNode(memberAccess, newMemberAccess); + root = root.ReplaceNode(memberAccess.Parent, newMemberAccessParent); } - newSolution = newSolution.WithDocumentSyntaxRoot(referencingDocument.Id, newReferencingRoot); + newSolution = newSolution.WithDocumentSyntaxRoot(referencingDocument.Id, root); } return newSolution; } diff --git a/src/CSharp/CodeCracker/Design/NameOfAnalyzer.cs b/src/CSharp/CodeCracker/Design/NameOfAnalyzer.cs index b5692202c..1631dc9f9 100644 --- a/src/CSharp/CodeCracker/Design/NameOfAnalyzer.cs +++ b/src/CSharp/CodeCracker/Design/NameOfAnalyzer.cs @@ -16,7 +16,7 @@ public class NameOfAnalyzer : DiagnosticAnalyzer internal static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.NameOfAnalyzer_MessageFormat), Resources.ResourceManager, typeof(Resources)); internal static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.NameOfAnalyzer_Description), Resources.ResourceManager, typeof(Resources)); internal const string Category = SupportedCategories.Design; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.NameOf.ToDiagnosticId(), Title, MessageFormat, @@ -25,8 +25,17 @@ public class NameOfAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: Description, helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.NameOf)); + internal static readonly DiagnosticDescriptor RuleExternal = new DiagnosticDescriptor( + DiagnosticId.NameOf_External.ToDiagnosticId(), + Title, + MessageFormat, + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: false, + description: Description, + helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.NameOf_External)); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule, RuleExternal); public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(LanguageVersion.CSharp6, Analyze, SyntaxKind.StringLiteralExpression); @@ -37,11 +46,12 @@ private static void Analyze(SyntaxNodeAnalysisContext context) var stringLiteral = context.Node as LiteralExpressionSyntax; if (string.IsNullOrWhiteSpace(stringLiteral?.Token.ValueText)) return; - var programElementName = GetProgramElementNameThatMatchStringLiteral(stringLiteral, context.SemanticModel); + bool externalSymbol; + var programElementName = GetProgramElementNameThatMatchStringLiteral(stringLiteral, context.SemanticModel, out externalSymbol); if (Found(programElementName) && OutSideOfDeclarationSideWithSameName(stringLiteral)) { - var diagnostic = Diagnostic.Create(Rule, stringLiteral.GetLocation(), programElementName); + var diagnostic = Diagnostic.Create(externalSymbol ? RuleExternal : Rule, stringLiteral.GetLocation(), programElementName); context.ReportDiagnostic(diagnostic); } } @@ -60,23 +70,25 @@ private static bool OutSideOfDeclarationSideWithSameName(LiteralExpressionSyntax return !string.Equals(propertyDeclaration?.Identifier.ValueText, stringLiteral.Token.ValueText, StringComparison.Ordinal); } - private static string GetProgramElementNameThatMatchStringLiteral(LiteralExpressionSyntax stringLiteral, SemanticModel semanticModel) + private static string GetProgramElementNameThatMatchStringLiteral(LiteralExpressionSyntax stringLiteral, SemanticModel semanticModel, out bool externalSymbol) { + externalSymbol = false; var programElementName = GetParameterNameThatMatchStringLiteral(stringLiteral); - if (!Found(programElementName)) { var literalValueText = stringLiteral.Token.ValueText; var symbol = semanticModel.LookupSymbols(stringLiteral.Token.SpanStart, null, literalValueText).FirstOrDefault(); + if (symbol == null) return null; + externalSymbol = !symbol.Locations.Any(l => l.IsInSource); - if (symbol?.Kind == SymbolKind.Local) + if (symbol.Kind == SymbolKind.Local) { var symbolSpan = symbol.Locations.Min(i => i.SourceSpan); if (symbolSpan.CompareTo(stringLiteral.Token.Span) > 0) - return string.Empty; + return null; } - programElementName = symbol?.ToDisplayParts().LastOrDefault(AnalyzerExtensions.IsName).ToString(); + programElementName = symbol.ToDisplayParts(NameOfSymbolDisplayFormat).LastOrDefault(AnalyzerExtensions.IsName).ToString(); } return programElementName; @@ -85,7 +97,7 @@ private static string GetProgramElementNameThatMatchStringLiteral(LiteralExpress private static string GetParameterNameThatMatchStringLiteral(LiteralExpressionSyntax stringLiteral) { var ancestorThatMightHaveParameters = stringLiteral.FirstAncestorOfType(typeof(AttributeListSyntax), typeof(MethodDeclarationSyntax), typeof(ConstructorDeclarationSyntax), typeof(IndexerDeclarationSyntax)); - var parameterName = string.Empty; + string parameterName = null; if (ancestorThatMightHaveParameters != null) { var parameters = new SeparatedSyntaxList(); @@ -102,6 +114,8 @@ private static string GetParameterNameThatMatchStringLiteral(LiteralExpressionSy break; case SyntaxKind.AttributeList: break; + default: + break; } parameterName = GetParameterWithIdentifierEqualToStringLiteral(stringLiteral, parameters)?.Identifier.Text; } @@ -112,5 +126,7 @@ private static string GetParameterNameThatMatchStringLiteral(LiteralExpressionSy private static ParameterSyntax GetParameterWithIdentifierEqualToStringLiteral(LiteralExpressionSyntax stringLiteral, SeparatedSyntaxList parameters) => parameters.FirstOrDefault(m => string.Equals(m.Identifier.ValueText, stringLiteral.Token.ValueText, StringComparison.Ordinal)); + + private static readonly SymbolDisplayFormat NameOfSymbolDisplayFormat = new SymbolDisplayFormat(memberOptions: SymbolDisplayMemberOptions.None, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes | SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers); } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Design/NameOfCodeFixProvider.cs b/src/CSharp/CodeCracker/Design/NameOfCodeFixProvider.cs index f43803f5e..ca074c96b 100644 --- a/src/CSharp/CodeCracker/Design/NameOfCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Design/NameOfCodeFixProvider.cs @@ -16,7 +16,7 @@ namespace CodeCracker.CSharp.Design [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(NameOfCodeFixProvider)), Shared] public class NameOfCodeFixProvider : CodeFixProvider { - public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.NameOf.ToDiagnosticId()); + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.NameOf.ToDiagnosticId(), DiagnosticId.NameOf_External.ToDiagnosticId()); public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; @@ -49,7 +49,7 @@ private static async Task GetIdentifierAsync(Document document, LiteralE return parameter.Identifier.Text; var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var symbol = semanticModel.LookupSymbols(stringLiteral.Token.SpanStart, null, stringLiteral.Token.ValueText).First(); - return symbol.ToDisplayParts().Last(AnalyzerExtensions.IsName).ToString(); + return symbol.ToDisplayParts(NameOfSymbolDisplayFormat).Last(AnalyzerExtensions.IsName).ToString(); } private static ParameterSyntax FindParameterThatStringLiteralRefers(SyntaxNode root, int diagnosticSpanStart, LiteralExpressionSyntax stringLiteral) @@ -57,5 +57,6 @@ private static ParameterSyntax FindParameterThatStringLiteralRefers(SyntaxNode r var method = root.FindToken(diagnosticSpanStart).Parent.FirstAncestorOfType(typeof(ConstructorDeclarationSyntax), typeof(MethodDeclarationSyntax)) as BaseMethodDeclarationSyntax; return method?.ParameterList.Parameters.FirstOrDefault(m => m.Identifier.Value.ToString() == stringLiteral.Token.Value.ToString()); } + private static readonly SymbolDisplayFormat NameOfSymbolDisplayFormat = new SymbolDisplayFormat(memberOptions: SymbolDisplayMemberOptions.None, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes | SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers); } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Design/StaticConstructorExceptionAnalyzer.cs b/src/CSharp/CodeCracker/Design/StaticConstructorExceptionAnalyzer.cs index 2bede249b..c8a9f2f23 100644 --- a/src/CSharp/CodeCracker/Design/StaticConstructorExceptionAnalyzer.cs +++ b/src/CSharp/CodeCracker/Design/StaticConstructorExceptionAnalyzer.cs @@ -10,15 +10,13 @@ namespace CodeCracker.CSharp.Design [DiagnosticAnalyzer(LanguageNames.CSharp)] public class StaticConstructorExceptionAnalyzer : DiagnosticAnalyzer { - internal const string Title = "Don't throw exception inside static constructors."; - internal const string MessageFormat = "Don't throw exception inside static constructors."; + internal const string Title = "Don't throw exceptions inside static constructors."; + internal const string MessageFormat = "Don't throw exceptions inside static constructors."; internal const string Category = SupportedCategories.Design; - const string Description = "Static constructor are called before the first time a class is used but the " - + "caller doesn't control when exactly.\r\n" - + "Exception thrown in this context force callers to use 'try' block around any useage of the class " - + "and should be avoided."; + const string Description = "Static constructors are called before a class is used for the first time. Exceptions thrown " + + "in static constructors force the use of a try block and should be avoided."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.StaticConstructorException.ToDiagnosticId(), Title, MessageFormat, @@ -49,4 +47,4 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) context.ReportDiagnostic(Diagnostic.Create(Rule, @throw.GetLocation(), ctor.Identifier.Text)); } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Design/StaticConstructorExceptionCodeFixProvider.cs b/src/CSharp/CodeCracker/Design/StaticConstructorExceptionCodeFixProvider.cs index 38d620ba6..44ff21625 100644 --- a/src/CSharp/CodeCracker/Design/StaticConstructorExceptionCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Design/StaticConstructorExceptionCodeFixProvider.cs @@ -10,7 +10,7 @@ namespace CodeCracker.CSharp.Design { - [ExportCodeFixProvider(LanguageNames.CSharp, nameof(StaticConstructorExceptionCodeFixProvider)), Shared] + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(StaticConstructorExceptionCodeFixProvider)), Shared] public class StaticConstructorExceptionCodeFixProvider : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => diff --git a/src/CSharp/CodeCracker/Design/SwitchWithoutDefaultAnalyzer.cs b/src/CSharp/CodeCracker/Design/SwitchWithoutDefaultAnalyzer.cs new file mode 100644 index 000000000..82a31165c --- /dev/null +++ b/src/CSharp/CodeCracker/Design/SwitchWithoutDefaultAnalyzer.cs @@ -0,0 +1,64 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + + +namespace CodeCracker.CSharp.Design +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class SwitchWithoutDefaultAnalyzer : DiagnosticAnalyzer + { + internal const string Title = "Your Switch maybe include default clause"; + internal const string MessageFormat = "{0}"; + internal const string Category = SupportedCategories.Design; + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( + DiagnosticId.SwitchCaseWithoutDefault.ToDiagnosticId(), + Title, + MessageFormat, + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.SwitchCaseWithoutDefault)); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(Analyzer, SyntaxKind.SwitchStatement); + + private static void Analyzer(SyntaxNodeAnalysisContext context) + { + if (context.IsGenerated()) return; + // checks if the compile error CS0151 it has fired.If Yes, don't report the diagnostic + var diagnostics = from dia in context.SemanticModel.GetDiagnostics() + where dia.Id == "CS0151" + select dia; + if (diagnostics.Any()) return; + if (context.Node.IsKind(SyntaxKind.SwitchStatement)) + { + var switchStatementToAnalyse = (SwitchStatementSyntax)context.Node; + if (switchStatementToAnalyse.DescendantNodes().Where(n => n.Kind() == SyntaxKind.DefaultSwitchLabel).ToList().Count == 0) + { + var hasInitializer = from nodes in switchStatementToAnalyse.DescendantNodes() + where nodes.Kind() == SyntaxKind.IdentifierName + select nodes; + if (!hasInitializer.Any()) return; + var hasTrueExpression = from nodes in switchStatementToAnalyse.DescendantNodes() + where nodes.IsKind(SyntaxKind.FalseLiteralExpression) + select nodes; + var hasfalseExpression = from nodes in switchStatementToAnalyse.DescendantNodes() + where nodes.IsKind(SyntaxKind.TrueLiteralExpression) + select nodes; + if (hasfalseExpression.Any() && hasTrueExpression.Any()) return; + var diagnostic = Diagnostic.Create(Rule, switchStatementToAnalyse.GetLocation(), "Consider put an default clause in Switch."); + context.ReportDiagnostic(diagnostic); + } + } + } + } +} diff --git a/src/CSharp/CodeCracker/Design/SwitchWithoutDefaultCodeFixProvider.cs b/src/CSharp/CodeCracker/Design/SwitchWithoutDefaultCodeFixProvider.cs new file mode 100644 index 000000000..086dd9055 --- /dev/null +++ b/src/CSharp/CodeCracker/Design/SwitchWithoutDefaultCodeFixProvider.cs @@ -0,0 +1,82 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +namespace CodeCracker.CSharp.Design +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SwitchWithoutDefaultCodeFixProvider)), Shared] + public class SwitchWithoutDefaultCodeFixProvider : CodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds => + ImmutableArray.Create(DiagnosticId.SwitchCaseWithoutDefault.ToDiagnosticId()); + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) + { + var diagnostic = context.Diagnostics.First(); + context.RegisterCodeFix(CodeAction.Create("Add a default clause", c => SwitchWithoutDefaultAsync(context.Document, diagnostic, c), + nameof(SwitchWithoutDefaultCodeFixProvider)), diagnostic); + return Task.FromResult(0); + } + + private async static Task SwitchWithoutDefaultAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newSections = new List(); + var switchCaseStatement = root.FindToken(diagnostic.Location.SourceSpan.Start).Parent.AncestorsAndSelf().OfType().First(); + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var expressionSymbol = semanticModel.GetSymbolInfo(switchCaseStatement.Expression).Symbol; + if (semanticModel.GetTypeInfo(switchCaseStatement.Expression).Type.SpecialType == SpecialType.System_Boolean) + { + var type = ((CaseSwitchLabelSyntax)switchCaseStatement.Sections.Last().ChildNodes().First()).Value.GetFirstToken().Text; + var againstType = type == "true" ? "false" : "true"; + newSections.Add(SyntaxFactory.SwitchSection().WithLabels( + new SyntaxList().Add(SyntaxFactory.CaseSwitchLabel(SyntaxFactory.ParseExpression(againstType)))) + .WithStatements(new SyntaxList().Add(SyntaxFactory.ParseStatement("break;")))); + } + else + { + newSections.Add(CreateSection(SyntaxFactory.DefaultSwitchLabel(), + SyntaxFactory.ThrowStatement(SyntaxFactory.ObjectCreationExpression(SyntaxFactory.ParseTypeName("System.Exception").WithAdditionalAnnotations(Simplifier.Annotation), + SyntaxFactory.ArgumentList(new SeparatedSyntaxList().Add(SyntaxFactory.Argument(SyntaxFactory.ParseExpression("\"Unexpected Case\"")))), null)))); + } + var newsSwitchCaseStatement = switchCaseStatement + .AddSections(newSections.ToArray()) + .WithAdditionalAnnotations(Formatter.Annotation); + var newRoot = root.ReplaceNode(switchCaseStatement, newsSwitchCaseStatement); + var newDocument = document.WithSyntaxRoot(newRoot); + return newDocument; + } + + static SwitchSectionSyntax CreateSection(SwitchLabelSyntax label, StatementSyntax statement) + { + var labels = new SyntaxList(); + labels = labels.Add(label); + return SyntaxFactory.SwitchSection(labels, CreateSectionStatements(statement)); + } + + static SyntaxList CreateSectionStatements(StatementSyntax source) + { + var result = new SyntaxList(); + if (source is BlockSyntax) + { + var block = source as BlockSyntax; + result = result.AddRange(block.Statements); + } + else + { + result = result.Add(source); + } + return result; + } + } +} diff --git a/src/CSharp/CodeCracker/Design/UseInvokeMethodToFireEventAnalyzer.cs b/src/CSharp/CodeCracker/Design/UseInvokeMethodToFireEventAnalyzer.cs index dc6b973db..6a30e2533 100644 --- a/src/CSharp/CodeCracker/Design/UseInvokeMethodToFireEventAnalyzer.cs +++ b/src/CSharp/CodeCracker/Design/UseInvokeMethodToFireEventAnalyzer.cs @@ -4,19 +4,21 @@ using Microsoft.CodeAnalysis.Diagnostics; using System; using System.Collections.Immutable; +using System.Linq; namespace CodeCracker.CSharp.Design { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class UseInvokeMethodToFireEventAnalyzer : DiagnosticAnalyzer { - internal const string Title = "Use Invoke Method To Fire Event Analyzer"; - internal const string MessageFormat = "Use ?.Invoke operator and method to fire '{0}' event."; + internal const string Title = "Check for null before calling a delegate"; + internal const string MessageFormat = "Verify if delegate '{0}' is null before invoking it."; internal const string Category = SupportedCategories.Design; - const string Description = "In C#6 an event can be invoked using the null-propagating operator (?.) and it's" - + "invoke method to avoid throwing a NullReference exception when there is no event handler attached."; + const string Description = "In C#6 a delegate can be invoked using the null-propagating operator (?.) and it's" + + " invoke method to avoid throwing a NullReference exception when there is no method attached to the delegate. " + + "Or you can check for null before calling the delegate."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), Title, MessageFormat, @@ -29,7 +31,7 @@ public class UseInvokeMethodToFireEventAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) => - context.RegisterSyntaxNodeAction(LanguageVersion.CSharp6, Analyzer, SyntaxKind.InvocationExpression); + context.RegisterSyntaxNodeAction(Analyzer, SyntaxKind.InvocationExpression); private static void Analyzer(SyntaxNodeAnalysisContext context) { @@ -47,9 +49,83 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) var invokedMethodSymbol = (typeInfo.ConvertedType as INamedTypeSymbol)?.DelegateInvokeMethod; if (invokedMethodSymbol == null) return; - if (!invokedMethodSymbol.ReturnsVoid && !invokedMethodSymbol.ReturnType.IsReferenceType) return; + + if (HasCheckForNullThatReturns(invocation, context.SemanticModel, symbol)) return; + if (IsInsideANullCheck(invocation, context.SemanticModel, symbol)) return; + if (IsPartOfATernaryThatChecksForNull(invocation, context.SemanticModel, symbol)) return; + if (IsPartOfALogicalOrThatChecksForNull(invocation, context.SemanticModel, symbol)) return; + if (IsPartOfALogicalAndThatChecksForNotNull(invocation, context.SemanticModel, symbol)) return; + if (symbol.IsReadOnlyAndInitializedForCertain(context)) return; context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation(), identifier.Identifier.Text)); } + + private static bool HasCheckForNullThatReturns(InvocationExpressionSyntax invocation, SemanticModel semanticModel, ISymbol symbol) + { + var method = invocation.FirstAncestorOfKind(SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration) as BaseMethodDeclarationSyntax; + if (method != null && method.Body != null) + { + var ifs = method.Body.Statements.OfKind(SyntaxKind.IfStatement); + foreach (IfStatementSyntax @if in ifs) + { + if (!@if.Condition?.IsKind(SyntaxKind.EqualsExpression) ?? true) continue; + var equals = (BinaryExpressionSyntax)@if.Condition; + if (equals.Left == null || equals.Right == null) continue; + if (@if.GetLocation().SourceSpan.Start > invocation.GetLocation().SourceSpan.Start) return false; + ISymbol identifierSymbol; + if (equals.Right.IsKind(SyntaxKind.NullLiteralExpression) && equals.Left.IsKind(SyntaxKind.IdentifierName)) + identifierSymbol = semanticModel.GetSymbolInfo(equals.Left).Symbol; + else if (equals.Left.IsKind(SyntaxKind.NullLiteralExpression) && equals.Right.IsKind(SyntaxKind.IdentifierName)) + identifierSymbol = semanticModel.GetSymbolInfo(equals.Right).Symbol; + else continue; + if (!symbol.Equals(identifierSymbol)) continue; + if (@if.Statement == null) continue; + if (@if.Statement.IsKind(SyntaxKind.Block)) + { + var ifBlock = (BlockSyntax)@if.Statement; + if (ifBlock.Statements.OfKind(SyntaxKind.ThrowStatement, SyntaxKind.ReturnStatement).Any()) return true; + } + else + { + if (@if.Statement.IsAnyKind(SyntaxKind.ThrowStatement, SyntaxKind.ReturnStatement)) return true; + } + } + } + return false; + } + + private static bool IsPartOfATernaryThatChecksForNull(InvocationExpressionSyntax invocation, SemanticModel semanticModel, ISymbol symbol) => + IsConditionThatChecksForNotEqualsNull(invocation.FirstAncestorOfType()?.Condition, semanticModel, symbol); + + private static bool IsPartOfALogicalOrThatChecksForNull(InvocationExpressionSyntax invocation, SemanticModel semanticModel, ISymbol symbol) => + IsConditionThatChecksForEqualsNull(invocation.FirstAncestorOfKind(SyntaxKind.LogicalOrExpression)?.Left, semanticModel, symbol); + + private static bool IsPartOfALogicalAndThatChecksForNotNull(InvocationExpressionSyntax invocation, SemanticModel semanticModel, ISymbol symbol) => + IsConditionThatChecksForNotEqualsNull(invocation.FirstAncestorOfKind(SyntaxKind.LogicalAndExpression)?.Left, semanticModel, symbol); + + private static bool IsInsideANullCheck(InvocationExpressionSyntax invocation, SemanticModel semanticModel, ISymbol symbol) => + invocation.Ancestors().OfType().Any(@if => IsConditionThatChecksForNotEqualsNull(@if.Condition, semanticModel, symbol)); + + private static bool IsConditionThatChecksForNotEqualsNull(ExpressionSyntax condition, SemanticModel semanticModel, ISymbol symbol) => + IsConditionThatChecksForNull(condition, semanticModel, symbol, SyntaxKind.NotEqualsExpression); + + private static bool IsConditionThatChecksForEqualsNull(ExpressionSyntax condition, SemanticModel semanticModel, ISymbol symbol) => + IsConditionThatChecksForNull(condition, semanticModel, symbol, SyntaxKind.EqualsExpression); + + private static bool IsConditionThatChecksForNull(ExpressionSyntax condition, SemanticModel semanticModel, ISymbol symbol, SyntaxKind binarySyntaxKind) + { + if (condition == null) return false; + if (!condition.IsKind(binarySyntaxKind)) return false; + var equals = (BinaryExpressionSyntax)condition; + if (equals.Left == null || equals.Right == null) return false; + ISymbol identifierSymbol; + if (equals.Right.IsKind(SyntaxKind.NullLiteralExpression) && equals.Left.IsKind(SyntaxKind.IdentifierName)) + identifierSymbol = semanticModel.GetSymbolInfo(equals.Left).Symbol; + else if (equals.Left.IsKind(SyntaxKind.NullLiteralExpression) && equals.Right.IsKind(SyntaxKind.IdentifierName)) + identifierSymbol = semanticModel.GetSymbolInfo(equals.Right).Symbol; + else return false; + if (symbol.Equals(identifierSymbol)) return true; + return false; + } } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Design/UseInvokeMethodToFireEventCodeFixProvider.cs b/src/CSharp/CodeCracker/Design/UseInvokeMethodToFireEventCodeFixProvider.cs index 40c226049..b3fbbfbee 100644 --- a/src/CSharp/CodeCracker/Design/UseInvokeMethodToFireEventCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Design/UseInvokeMethodToFireEventCodeFixProvider.cs @@ -4,6 +4,8 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; +using System; using System.Collections.Immutable; using System.Composition; using System.Linq; @@ -15,17 +17,26 @@ namespace CodeCracker.CSharp.Design [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseInvokeMethodToFireEventCodeFixProvider)), Shared] public class UseInvokeMethodToFireEventCodeFixProvider : CodeFixProvider { + private const string SyntaxAnnotatinKind = "CC-CopyEvent"; + public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId()); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) + public async sealed override Task RegisterCodeFixesAsync(CodeFixContext context) { var diagnostic = context.Diagnostics.First(); + var compilation = (CSharpCompilation)await context.Document.Project.GetCompilationAsync(); + if (compilation.LanguageVersion >= LanguageVersion.CSharp6) + context.RegisterCodeFix( + CodeAction.Create("Change to ?.Invoke to call a delegate", + ct => UseInvokeAsync(context.Document, diagnostic, ct), + nameof(UseInvokeMethodToFireEventCodeFixProvider) + "_Elvis"), diagnostic); context.RegisterCodeFix( - CodeAction.Create("Use ?.Invoke operator and method to fire an event.", ct => UseInvokeAsync(context.Document, diagnostic, ct), nameof(UseInvokeMethodToFireEventCodeFixProvider)), diagnostic); - return Task.FromResult(0); + CodeAction.Create("Copy delegate reference to a variable", + ct => CreateVariableAsync(context.Document, diagnostic, ct), + nameof(UseInvokeMethodToFireEventCodeFixProvider) + "_CopyToVariable"), diagnostic); } private async static Task UseInvokeAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) @@ -42,8 +53,78 @@ private async static Task UseInvokeAsync(Document document, Diagnostic SyntaxFactory.Token(SyntaxKind.DotToken), SyntaxFactory.IdentifierName("Invoke")), invocation.ArgumentList)) + .WithAdditionalAnnotations(Formatter.Annotation) + .WithTrailingTrivia(invocation.GetTrailingTrivia()); + var identifier = (IdentifierNameSyntax)invocation.Expression; + var semanticModel = await document.GetSemanticModelAsync(cancellationToken); + var typeInfo = semanticModel.GetTypeInfo(identifier, cancellationToken); + var symbol = semanticModel.GetSymbolInfo(identifier).Symbol; + var invokedMethodSymbol = ((INamedTypeSymbol)typeInfo.ConvertedType).DelegateInvokeMethod; + ExpressionSyntax newNode = newInvocation; + if (!invokedMethodSymbol.ReturnsVoid && !invokedMethodSymbol.ReturnType.IsReferenceType && !(invocation.Parent is ExpressionStatementSyntax)) + { + var typeName = invokedMethodSymbol.ReturnType.GetFullName(false); + var defaultValue = SyntaxFactory.DefaultExpression(SyntaxFactory.ParseName(typeName) + .WithAdditionalAnnotations(Simplifier.Annotation)); + newNode = SyntaxFactory.BinaryExpression(SyntaxKind.CoalesceExpression, newInvocation, defaultValue) .WithAdditionalAnnotations(Formatter.Annotation); - return document.WithSyntaxRoot(root.ReplaceNode(invocation, newInvocation).WithTrailingTrivia(invocation.GetTrailingTrivia())); + if (invocation.Parent is BinaryExpressionSyntax) + { + var binary = (BinaryExpressionSyntax)invocation.Parent; + if (binary.Right.Equals(invocation.Parent) + && (binary.IsKind(SyntaxKind.BitwiseAndExpression) + || binary.IsKind(SyntaxKind.ExclusiveOrExpression) + || binary.IsKind(SyntaxKind.BitwiseOrExpression) + || binary.IsKind(SyntaxKind.LogicalAndExpression)) + || binary.IsKind(SyntaxKind.LogicalOrExpression) + || binary.IsKind(SyntaxKind.CoalesceExpression)) + newNode = SyntaxFactory.ParenthesizedExpression(newNode); + } + } + var newRoot = root.ReplaceNode(invocation, newNode); + return document.WithSyntaxRoot(newRoot); } + + private async static Task CreateVariableAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var sourceSpan = diagnostic.Location.SourceSpan; + var invocation = root.FindToken(sourceSpan.Start).Parent.AncestorsAndSelf().OfType().First(); + var semanticModel = await document.GetSemanticModelAsync(cancellationToken); + var handlerName = semanticModel.FindAvailableIdentifierName(sourceSpan.Start, "handler"); + var variable = + SyntaxFactory.LocalDeclarationStatement( + SyntaxFactory.VariableDeclaration( + SyntaxFactory.ParseTypeName("var"), + SyntaxFactory.SeparatedList( + new[] + { + SyntaxFactory.VariableDeclarator( + SyntaxFactory.Identifier(handlerName), + null, + SyntaxFactory.EqualsValueClause(invocation.Expression.WithoutLeadingTrivia().WithoutTrailingTrivia())) + }))) + .WithLeadingTrivia(invocation.Parent.GetLeadingTrivia()); + var statement = invocation.Expression.FirstAncestorOrSelfThatIsAStatement(); + var newStatement = statement.ReplaceNode(invocation.Expression, SyntaxFactory.IdentifierName(handlerName)); + var newInvocation = + SyntaxFactory.IfStatement( + SyntaxFactory.BinaryExpression(SyntaxKind.NotEqualsExpression, + SyntaxFactory.IdentifierName(handlerName), + SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)), + newStatement) + .WithTrailingTrivia(invocation.Parent.GetTrailingTrivia()); + var oldNode = statement; + var newNode = newStatement.WithAdditionalAnnotations(new SyntaxAnnotation(SyntaxAnnotatinKind)); + if (oldNode.Parent.IsEmbeddedStatementOwner()) + newNode = SyntaxFactory.Block((StatementSyntax)newNode); + var newRoot = root.ReplaceNode(oldNode, newNode); + newRoot = newRoot.InsertNodesAfter(GetMark(newRoot), new SyntaxNode[] { variable, newInvocation }); + newRoot = newRoot.RemoveNode(GetMark(newRoot), SyntaxRemoveOptions.KeepNoTrivia); + return document.WithSyntaxRoot(newRoot.WithAdditionalAnnotations(Formatter.Annotation)); + } + + private static SyntaxNode GetMark(SyntaxNode node) => + node.DescendantNodes().First(n => n.GetAnnotations(SyntaxAnnotatinKind).Any()); } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Extensions/CSharpAnalyzerExtensions.cs b/src/CSharp/CodeCracker/Extensions/CSharpAnalyzerExtensions.cs index d30d9a067..6b79daa2b 100644 --- a/src/CSharp/CodeCracker/Extensions/CSharpAnalyzerExtensions.cs +++ b/src/CSharp/CodeCracker/Extensions/CSharpAnalyzerExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Formatting; using System; using System.Collections.Generic; using System.Linq; @@ -16,12 +17,17 @@ public static void RegisterSyntaxNodeAction(this AnalysisCont public static void RegisterCompilationStartAction(this AnalysisContext context, LanguageVersion greaterOrEqualThanLanguageVersion, Action registrationAction) => context.RegisterCompilationStartAction(compilationContext => compilationContext.RunIfCSharpVersionOrGreater(greaterOrEqualThanLanguageVersion, () => registrationAction?.Invoke(compilationContext))); + + public static void RegisterSymbolAction(this AnalysisContext context, LanguageVersion greaterOrEqualThanLanguageVersion, Action registrationAction, params SymbolKind[] symbolKinds) => + context.RegisterSymbolAction(compilationContext => compilationContext.RunIfCSharpVersionOrGreater(greaterOrEqualThanLanguageVersion, () => registrationAction?.Invoke(compilationContext)), symbolKinds); #pragma warning disable RS1012 private static void RunIfCSharpVersionOrGreater(this CompilationStartAnalysisContext context, LanguageVersion greaterOrEqualThanLanguageVersion, Action action) => context.Compilation.RunIfCSharpVersionOrGreater(action, greaterOrEqualThanLanguageVersion); #pragma warning restore RS1012 private static void RunIfCSharpVersionOrGreater(this Compilation compilation, Action action, LanguageVersion greaterOrEqualThanLanguageVersion) => (compilation as CSharpCompilation)?.LanguageVersion.RunIfCSharpVersionGreater(action, greaterOrEqualThanLanguageVersion); + private static void RunIfCSharpVersionOrGreater(this SymbolAnalysisContext context, LanguageVersion greaterOrEqualThanLanguageVersion, Action action) => + context.Compilation.RunIfCSharpVersionOrGreater(action, greaterOrEqualThanLanguageVersion); private static void RunIfCSharpVersionGreater(this LanguageVersion languageVersion, Action action, LanguageVersion greaterOrEqualThanLanguageVersion) { @@ -146,6 +152,28 @@ public static bool IsAnyKind(this SyntaxNode node, params SyntaxKind[] kinds) return false; } + public static MemberDeclarationSyntax FirstAncestorOrSelfThatIsAMember(this SyntaxNode node) + { + var currentNode = node; + while (true) + { + if (currentNode == null) break; + if (currentNode.IsAnyKind( + SyntaxKind.EnumDeclaration, SyntaxKind.ClassDeclaration, + SyntaxKind.InterfaceDeclaration, SyntaxKind.StructDeclaration, + SyntaxKind.ConstructorDeclaration, SyntaxKind.DestructorDeclaration, + SyntaxKind.MethodDeclaration, SyntaxKind.PropertyDeclaration, + SyntaxKind.EventDeclaration, SyntaxKind.DelegateDeclaration, + SyntaxKind.EventFieldDeclaration, SyntaxKind.FieldDeclaration, + SyntaxKind.ConversionOperatorDeclaration, SyntaxKind.OperatorDeclaration, + SyntaxKind.IndexerDeclaration, SyntaxKind.NamespaceDeclaration)) + return (MemberDeclarationSyntax)currentNode; + currentNode = currentNode.Parent; + } + return null; + + } + public static StatementSyntax FirstAncestorOrSelfThatIsAStatement(this SyntaxNode node) { var currentNode = node; @@ -289,6 +317,8 @@ private static string GetLastIdentifierValueText(CSharpSyntaxNode node) case SyntaxKind.AliasQualifiedName: result = ((AliasQualifiedNameSyntax)node).Name.Identifier.ValueText; break; + default: + break; } return result; } @@ -308,8 +338,9 @@ public static SyntaxToken GetIdentifier(this BaseMethodDeclarationSyntax method) case SyntaxKind.DestructorDeclaration: result = ((DestructorDeclarationSyntax)method).Identifier; break; + default: + return result; } - return result; } @@ -364,6 +395,8 @@ public static MemberDeclarationSyntax WithModifiers(this MemberDeclarationSyntax case SyntaxKind.EventDeclaration: result = ((EventDeclarationSyntax)declaration).WithModifiers(newModifiers); break; + default: + break; } return result; @@ -400,6 +433,8 @@ public static SyntaxTokenList GetModifiers(this MemberDeclarationSyntax memberDe case SyntaxKind.EventDeclaration: result = ((BasePropertyDeclarationSyntax)memberDeclaration).Modifiers; break; + default: + break; } return result; @@ -422,5 +457,477 @@ public static SyntaxTokenList CloneAccessibilityModifiers(this SyntaxTokenList m return SyntaxFactory.TokenList(accessibilityModifiers.EnsureProtectedBeforeInternal()); } + + public static SyntaxNode FirstAncestorOfKind(this SyntaxNode node, params SyntaxKind[] kinds) + { + var currentNode = node; + while (true) + { + var parent = currentNode.Parent; + if (parent == null) break; + if (parent.IsAnyKind(kinds)) return parent; + currentNode = parent; + } + return null; + } + + public static TNode FirstAncestorOfKind(this SyntaxNode node, params SyntaxKind[] kinds) where TNode : SyntaxNode => + (TNode)FirstAncestorOfKind(node, kinds); + + public static IEnumerable OfKind(this IEnumerable nodes, SyntaxKind kind) where TNode : SyntaxNode + { + foreach (var node in nodes) + if (node.IsKind(kind)) + yield return (TNode)node; + } + + public static IEnumerable OfKind(this IEnumerable nodes, params SyntaxKind[] kinds) where TNode : SyntaxNode + { + foreach (var node in nodes) + if (node.IsAnyKind(kinds)) + yield return (TNode)node; + } + + public static IEnumerable OfKind(this IEnumerable nodes, SyntaxKind kind) where TNode : SyntaxNode + { + foreach (var node in nodes) + if (node.IsKind(kind)) + yield return node; + } + + public static IEnumerable OfKind(this IEnumerable nodes, params SyntaxKind[] kinds) where TNode : SyntaxNode + { + foreach (var node in nodes) + if (node.IsAnyKind(kinds)) + yield return node; + } + + public static StatementSyntax GetPreviousStatement(this StatementSyntax statement) + { + var parent = statement.Parent; + SyntaxList statements; + if (parent.IsKind(SyntaxKind.Block)) + { + var block = (BlockSyntax)parent; + statements = block.Statements; + } + else if (parent.IsKind(SyntaxKind.SwitchSection)) + { + var section = (SwitchSectionSyntax)parent; + statements = section.Statements; + } + else return null; + if (statement.Equals(statements[0])) return null; + for (int i = 1; i < statements.Count; i++) + { + var someStatement = statements[i]; + if (statement.Equals(someStatement)) + return statements[i - 1]; + } + return null; + } + + + /// + /// Determines whether the specified symbol is a read only field and initialized in the specified context. + /// + /// The context. + /// The symbol. + /// + /// True if the symbol is a read only field that is initialized either on declaration or in all constructors of the containing type; otherwise false. + /// + /// + /// If the symbol is initialized in a block of code of the constructor that might not always be called, the symbol is considered to + /// not be initialized for certain. For more information + /// + public static bool IsReadOnlyAndInitializedForCertain(this ISymbol symbol, SyntaxNodeAnalysisContext context) + { + if (symbol.Kind != SymbolKind.Field) return false; + + var field = (IFieldSymbol)symbol; + foreach (var declaringSyntaxReference in symbol.DeclaringSyntaxReferences) + { + var variableDeclarator = declaringSyntaxReference.GetSyntax(context.CancellationToken) as VariableDeclaratorSyntax; + + if (variableDeclarator != null && variableDeclarator.Initializer != null && field.IsReadOnly && + !variableDeclarator.Initializer.Value.IsKind(SyntaxKind.NullLiteralExpression)) return true; + } + + foreach (var constructor in symbol.ContainingType.Constructors) + { + foreach (var declaringSyntaxReference in constructor.DeclaringSyntaxReferences) + { + var constructorSyntax = declaringSyntaxReference.GetSyntax(context.CancellationToken) as ConstructorDeclarationSyntax; + if (constructorSyntax != null) + if (field.IsReadOnly && constructorSyntax.Body.Statements.DoesBlockContainCertainInitializer(context, symbol) == InitializerState.Initializer) + return true; + } + } + + return false; + } + + private static InitializerState DoesBlockContainCertainInitializer(this StatementSyntax statement, SyntaxNodeAnalysisContext context, ISymbol symbol) + { + return new[] { statement }.DoesBlockContainCertainInitializer(context, symbol); + } + + /// + /// This method can be used to determine if the specified block of + /// statements contains an initializer for the specified symbol. + /// + /// The context. + /// The symbol. + /// The statements. + /// + /// The initializer state found + /// + /// + /// Code blocks that might not always be called are: + /// - An if or else statement. + /// - The body of a for, while or for-each loop. + /// - Switch statements + /// + /// The following exceptions are taken into account: + /// - If both if and else statements contain a certain initialization. + /// - If all cases in a switch contain a certain initialization (this means a default case must exist as well). + /// + /// Please note that this is a recursive function so we can check a block of code in an if statement for example. + /// + private static InitializerState DoesBlockContainCertainInitializer(this IEnumerable statements, SyntaxNodeAnalysisContext context, ISymbol symbol) + { + // Keep track of the current initializer state. This can only be None + // or Initializer, WayToSkipInitializer will always be returned immediately. + // Only way to go back from Initializer to None is if there is an assignment + // to null after a previous assignment to a non-null value. + var currentState = InitializerState.None; + + foreach (var statement in statements) + { + if (statement.IsKind(SyntaxKind.ReturnStatement) && currentState == InitializerState.None) + { + return InitializerState.WayToSkipInitializer; + } + else if (statement.IsKind(SyntaxKind.Block)) + { + var blockResult = ((BlockSyntax)statement).Statements.DoesBlockContainCertainInitializer(context, symbol); + if (CanSkipInitializer(blockResult, currentState)) + return InitializerState.WayToSkipInitializer; + if (blockResult == InitializerState.Initializer) + currentState = blockResult; + } + else if (statement.IsKind(SyntaxKind.UsingStatement)) + { + var blockResult = ((UsingStatementSyntax)statement).Statement.DoesBlockContainCertainInitializer(context, symbol); + if (CanSkipInitializer(blockResult, currentState)) + return InitializerState.WayToSkipInitializer; + if (blockResult == InitializerState.Initializer) + currentState = blockResult; + } + else if (statement.IsKind(SyntaxKind.ExpressionStatement)) + { + var expression = ((ExpressionStatementSyntax)statement).Expression; + if (expression.IsKind(SyntaxKind.SimpleAssignmentExpression)) + { + var assignmentExpression = (AssignmentExpressionSyntax)expression; + var identifier = assignmentExpression.Left; + if (identifier != null) + { + var right = assignmentExpression.Right; + if (right != null) + { + if (right.IsKind(SyntaxKind.NullLiteralExpression)) + currentState = InitializerState.None; + else if (symbol.Equals(context.SemanticModel.GetSymbolInfo(identifier).Symbol)) + currentState = InitializerState.Initializer; + } + } + } + } + else if (statement.IsKind(SyntaxKind.SwitchStatement)) + { + var switchStatement = (SwitchStatementSyntax)statement; + if (switchStatement.Sections.Any(s => s.Labels.Any(l => l.IsKind(SyntaxKind.DefaultSwitchLabel)))) + { + var sectionInitializerStates = switchStatement.Sections.Select(s => s.Statements.DoesBlockContainCertainInitializer(context, symbol)).ToList(); + if (sectionInitializerStates.All(sectionInitializerState => sectionInitializerState == InitializerState.Initializer)) + currentState = InitializerState.Initializer; + else if (sectionInitializerStates.Any(sectionInitializerState => CanSkipInitializer(sectionInitializerState, currentState))) + return InitializerState.WayToSkipInitializer; + } + } + else if (statement.IsKind(SyntaxKind.IfStatement)) + { + var ifStatement = (IfStatementSyntax)statement; + + var ifResult = ifStatement.Statement.DoesBlockContainCertainInitializer(context, symbol); + if (ifStatement.Else != null) + { + var elseResult = ifStatement.Else.Statement.DoesBlockContainCertainInitializer(context, symbol); + + if (ifResult == InitializerState.Initializer && elseResult == InitializerState.Initializer) + currentState = InitializerState.Initializer; + if (CanSkipInitializer(elseResult, currentState)) + return InitializerState.WayToSkipInitializer; + } + if (CanSkipInitializer(ifResult, currentState)) + { + return InitializerState.WayToSkipInitializer; + } + } + } + return currentState; + } + + private static bool CanSkipInitializer(InitializerState foundState, InitializerState currentState) => + foundState == InitializerState.WayToSkipInitializer && currentState == InitializerState.None; + + public static TNode WithoutAllTrivia(this TNode node) where TNode : SyntaxNode + { + var newNode = node.WithoutTrivia(); + var tokens = newNode.ChildTokens().ToList(); + var newTokens = tokens.ToDictionary(t => t, t => t.WithoutTrivia()); + newNode = newNode.ReplaceTokens(tokens, (o, _) => newTokens[o]); + var nodes = newNode.ChildNodes().ToList(); + var newNodes = nodes.ToDictionary(n => n, n => n.WithoutAllTrivia()); + newNode = newNode.ReplaceNodes(nodes, (o, _) => newNodes[o]); + newNode = newNode.WithAdditionalAnnotations(Formatter.Annotation); + return newNode; + } + + public static SyntaxToken WithoutTrivia(this SyntaxToken token) + { + var trivia = token.GetAllTrivia(); + var newToken = token.ReplaceTrivia(trivia, (o, _) => default(SyntaxTrivia)); + return newToken; + } + + private static readonly SyntaxTokenList publicToken = SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); + private static readonly SyntaxTokenList privateToken = SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); + private static readonly SyntaxTokenList protectedToken = SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); + private static readonly SyntaxTokenList protectedInternalToken = SyntaxFactory.TokenList( + SyntaxFactory.Token(SyntaxKind.ProtectedKeyword), SyntaxFactory.Token(SyntaxKind.InternalKeyword)); + private static readonly SyntaxTokenList internalToken = SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.InternalKeyword)); + public static SyntaxTokenList GetTokens(this Accessibility accessibility) + { + switch (accessibility) + { + case Accessibility.Public: + return publicToken; + case Accessibility.Private: + return privateToken; + case Accessibility.Protected: + return protectedToken; + case Accessibility.Internal: + return internalToken; + case Accessibility.ProtectedAndInternal: + return protectedInternalToken; + default: + throw new NotSupportedException(); + } + } + public static TypeDeclarationSyntax WithMembers(this TypeDeclarationSyntax typeDeclarationSyntax, SyntaxList members) + { + if (typeDeclarationSyntax is ClassDeclarationSyntax) + { + return (typeDeclarationSyntax as ClassDeclarationSyntax).WithMembers(members); + } + else if (typeDeclarationSyntax is StructDeclarationSyntax) + { + return (typeDeclarationSyntax as StructDeclarationSyntax).WithMembers(members); + } + else + { + throw new NotSupportedException(); + } + } + + /// + /// According to the C# Language Spec, item 6.4 + /// See online. + /// + /// The type to convert from + /// The type to convert to + public static bool HasImplicitNumericConversion(this ITypeSymbol from, ITypeSymbol to) + { + if (from == null || to == null) return false; + switch (from.SpecialType) + { + case SpecialType.System_SByte: + switch (to.SpecialType) + { + case SpecialType.System_SByte: + case SpecialType.System_Int16: + case SpecialType.System_Int32: + case SpecialType.System_Int64: + case SpecialType.System_Single: + case SpecialType.System_Double: + case SpecialType.System_Decimal: + return true; + default: + return false; + } + case SpecialType.System_Byte: + switch (to.SpecialType) + { + case SpecialType.System_Byte: + case SpecialType.System_Int16: + case SpecialType.System_UInt16: + case SpecialType.System_Int32: + case SpecialType.System_UInt32: + case SpecialType.System_Int64: + case SpecialType.System_UInt64: + case SpecialType.System_Single: + case SpecialType.System_Double: + case SpecialType.System_Decimal: + return true; + default: + return false; + } + case SpecialType.System_Int16: + switch (to.SpecialType) + { + case SpecialType.System_Int16: + case SpecialType.System_Int32: + case SpecialType.System_Int64: + case SpecialType.System_Single: + case SpecialType.System_Double: + case SpecialType.System_Decimal: + return true; + default: + return false; + } + case SpecialType.System_UInt16: + switch (to.SpecialType) + { + case SpecialType.System_Int32: + case SpecialType.System_UInt32: + case SpecialType.System_Int64: + case SpecialType.System_UInt64: + case SpecialType.System_Single: + case SpecialType.System_Double: + case SpecialType.System_Decimal: + return true; + default: + return false; + } + case SpecialType.System_Int32: + switch (to.SpecialType) + { + case SpecialType.System_Int32: + case SpecialType.System_Int64: + case SpecialType.System_Single: + case SpecialType.System_Double: + case SpecialType.System_Decimal: + return true; + default: + return false; + } + case SpecialType.System_UInt32: + switch (to.SpecialType) + { + case SpecialType.System_UInt32: + case SpecialType.System_Int64: + case SpecialType.System_UInt64: + case SpecialType.System_Single: + case SpecialType.System_Double: + case SpecialType.System_Decimal: + return true; + default: + return false; + } + case SpecialType.System_Int64: + switch (to.SpecialType) + { + case SpecialType.System_Int64: + case SpecialType.System_Single: + case SpecialType.System_Double: + case SpecialType.System_Decimal: + return true; + default: + return false; + } + case SpecialType.System_UInt64: + switch (to.SpecialType) + { + case SpecialType.System_UInt64: + case SpecialType.System_Single: + case SpecialType.System_Double: + case SpecialType.System_Decimal: + return true; + default: + return false; + } + case SpecialType.System_Char: + switch (to.SpecialType) + { + case SpecialType.System_UInt16: + case SpecialType.System_Int32: + case SpecialType.System_UInt32: + case SpecialType.System_Int64: + case SpecialType.System_UInt64: + case SpecialType.System_Char: + case SpecialType.System_Single: + case SpecialType.System_Double: + case SpecialType.System_Decimal: + return true; + default: + return false; + } + case SpecialType.System_Single: + switch (to.SpecialType) + { + case SpecialType.System_Single: + case SpecialType.System_Double: + return true; + default: + return false; + } + default: + return false; + } + } + + public static string FindAvailableIdentifierName(this SemanticModel semanticModel, int position, string baseName) + { + var name = baseName; + var inscrementer = 1; + while (semanticModel.LookupSymbols(position, name: name).Any()) + name = baseName + inscrementer++; + return name; + } + + public static bool IsImplementingInterface(this MemberDeclarationSyntax member, SemanticModel semanticModel) => + semanticModel.GetDeclaredSymbol(member).IsImplementingInterface(); + + public static bool IsImplementingInterface(this ISymbol memberSymbol) + { + if (memberSymbol == null) return false; + IMethodSymbol methodSymbol; + IEventSymbol eventSymbol; + IPropertySymbol propertySymbol; + if ((methodSymbol = memberSymbol as IMethodSymbol) != null) + { + if (methodSymbol.ExplicitInterfaceImplementations.Any()) return true; + } + else if ((propertySymbol = memberSymbol as IPropertySymbol) != null) + { + if (propertySymbol.ExplicitInterfaceImplementations.Any()) return true; + } + else if ((eventSymbol = memberSymbol as IEventSymbol) != null) + { + if (eventSymbol.ExplicitInterfaceImplementations.Any()) return true; + } + else return false; + var type = memberSymbol.ContainingType; + if (type == null) return false; + var interfaceMembersWithSameName = type.AllInterfaces.SelectMany(i => i.GetMembers(memberSymbol.Name)); + foreach (var interfaceMember in interfaceMembersWithSameName) + { + var implementation = type.FindImplementationForInterfaceMember(interfaceMember); + if (implementation != null && implementation.Equals(memberSymbol)) return true; + } + return false; + } } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Extensions/CSharpGeneratedCodeAnalysisExtensions.cs b/src/CSharp/CodeCracker/Extensions/CSharpGeneratedCodeAnalysisExtensions.cs index d7386b3c5..339e64ae1 100644 --- a/src/CSharp/CodeCracker/Extensions/CSharpGeneratedCodeAnalysisExtensions.cs +++ b/src/CSharp/CodeCracker/Extensions/CSharpGeneratedCodeAnalysisExtensions.cs @@ -1,8 +1,8 @@ using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System.Linq; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.CSharp; namespace CodeCracker { @@ -48,9 +48,13 @@ public static bool HasAutoGeneratedComment(this SyntaxTree tree) if (!firstToken.HasLeadingTrivia) return false; trivia = firstToken.LeadingTrivia; } - var commentLines = trivia.Where(t => t.IsKind(SyntaxKind.SingleLineCommentTrivia)).Take(2).ToList(); - if (commentLines.Count != 2) return false; - return commentLines[1].ToString() == "// "; + + var comments = trivia.Where(t => t.IsKind(SyntaxKind.SingleLineCommentTrivia) || t.IsKind(SyntaxKind.MultiLineCommentTrivia)); + return comments.Any(t => + { + var s = t.ToString(); + return s.Contains(" + /// Used to indicate what can be said about the initialization + /// of a symbol in a given block of statements. + /// + internal enum InitializerState + { + /// + /// Indicates that the block of statements does NOT initialize the symbol for certain. + /// + None, + /// + /// Indicates that the block of statements DOES initialize the symbol for certain. + /// + Initializer, + /// + /// Indicates that the block of statements contains a way to skip any initializers + /// following the given block of statements (for instance a return statement inside + /// an if statement can skip any initializers after the if statement). + /// + WayToSkipInitializer, + } +} \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Maintainability/XmlDocumentationAnalyzer.cs b/src/CSharp/CodeCracker/Maintainability/XmlDocumentationAnalyzer.cs index 520e845e2..5f987fddb 100644 --- a/src/CSharp/CodeCracker/Maintainability/XmlDocumentationAnalyzer.cs +++ b/src/CSharp/CodeCracker/Maintainability/XmlDocumentationAnalyzer.cs @@ -14,24 +14,38 @@ public sealed class XmlDocumentationAnalyzer : DiagnosticAnalyzer { internal static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.XmlDocumentationAnalyzer_Title), Resources.ResourceManager, typeof(Resources)); - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( - DiagnosticId.XmlDocumentation.ToDiagnosticId(), + internal static readonly DiagnosticDescriptor RuleMissingInCSharp = new DiagnosticDescriptor( + DiagnosticId.XmlDocumentation_MissingInCSharp.ToDiagnosticId(), + Title, + Title, + SupportedCategories.Maintainability, + DiagnosticSeverity.Info, + isEnabledByDefault: true, + helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.XmlDocumentation_MissingInCSharp)); + + internal static readonly DiagnosticDescriptor RuleMissingInXml = new DiagnosticDescriptor( + DiagnosticId.XmlDocumentation_MissingInXml.ToDiagnosticId(), Title, Title, SupportedCategories.Maintainability, DiagnosticSeverity.Warning, isEnabledByDefault: true, - helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.XmlDocumentation)); + helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.XmlDocumentation_MissingInXml)); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(RuleMissingInCSharp, RuleMissingInXml); public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(Analyzer, SyntaxKind.SingleLineDocumentationCommentTrivia); private static void Analyzer(SyntaxNodeAnalysisContext context) { + if (context.IsGenerated()) return; var documentationNode = (DocumentationCommentTriviaSyntax)context.Node; var method = GetMethodFromXmlDocumentation(documentationNode); if (method == null) return; + var elementNames = documentationNode.Content + .OfType() + .Select(element => element.Name?.LocalName.ValueText); + if (elementNames.Contains("inheritdoc")) return; var methodParameters = method.ParameterList.Parameters; var xElementsWitAttrs = documentationNode.Content.OfType() .Where(xEle => xEle.StartTag?.Name?.LocalName.ValueText == "param") @@ -43,22 +57,20 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) .ToImmutableHashSet(); var parameterWithDocParameter = (from key in keys - where key != null - let Parameter = methodParameters.FirstOrDefault(p => p.Identifier.ValueText == key) - let DocParameter = xElementsWitAttrs.FirstOrDefault(p => p.Identifier?.Identifier.ValueText == key) - select new { Parameter, DocParameter }); + where key != null + let Parameter = methodParameters.FirstOrDefault(p => p.Identifier.ValueText == key) + let DocParameter = xElementsWitAttrs.FirstOrDefault(p => p.Identifier?.Identifier.ValueText == key) + select new { Parameter, DocParameter }); if (parameterWithDocParameter.Any(p => p.Parameter == null)) { - var properties = new Dictionary {["kind"] = "nonexistentParam" }.ToImmutableDictionary(); - var diagnostic = Diagnostic.Create(Rule, documentationNode.GetLocation(), properties); + var diagnostic = Diagnostic.Create(RuleMissingInCSharp, documentationNode.GetLocation()); context.ReportDiagnostic(diagnostic); } if (parameterWithDocParameter.Any(p => p.DocParameter == null)) { - var properties = new Dictionary { ["kind"] = "missingDoc" }.ToImmutableDictionary(); - var diagnostic = Diagnostic.Create(Rule, documentationNode.GetLocation(), properties); + var diagnostic = Diagnostic.Create(RuleMissingInXml, documentationNode.GetLocation()); context.ReportDiagnostic(diagnostic); } } diff --git a/src/CSharp/CodeCracker/Maintainability/XmlDocumentationCodeFixProvider.cs b/src/CSharp/CodeCracker/Maintainability/XmlDocumentationCodeFixProvider.cs index 06e7b5cc7..60cf4f0aa 100644 --- a/src/CSharp/CodeCracker/Maintainability/XmlDocumentationCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Maintainability/XmlDocumentationCodeFixProvider.cs @@ -12,8 +12,6 @@ namespace CodeCracker.CSharp.Maintainability { public abstract class XmlDocumentationCodeFixProvider : CodeFixProvider { - public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.XmlDocumentation.ToDiagnosticId()); - public abstract SyntaxNode FixParameters(MethodDeclarationSyntax method, SyntaxNode root); protected async Task FixParametersAsync(Document document, Diagnostic diagnostic, CancellationToken c) diff --git a/src/CSharp/CodeCracker/Maintainability/XmlDocumentationRemoveNonExistentParametersCodeFixProvider.cs b/src/CSharp/CodeCracker/Maintainability/XmlDocumentationMissingInCSharpCodeFixProvider.cs similarity index 73% rename from src/CSharp/CodeCracker/Maintainability/XmlDocumentationRemoveNonExistentParametersCodeFixProvider.cs rename to src/CSharp/CodeCracker/Maintainability/XmlDocumentationMissingInCSharpCodeFixProvider.cs index 04e3e3e01..b0d5f4e9d 100644 --- a/src/CSharp/CodeCracker/Maintainability/XmlDocumentationRemoveNonExistentParametersCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Maintainability/XmlDocumentationMissingInCSharpCodeFixProvider.cs @@ -1,19 +1,22 @@ -using Microsoft.CodeAnalysis; +using CodeCracker.Properties; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading.Tasks; -using CodeCracker.Properties; namespace CodeCracker.CSharp.Maintainability { - [ExportCodeFixProvider(LanguageNames.CSharp, nameof(XmlDocumentationCodeFixProvider)), Shared] - public sealed class XmlDocumentationRemoveNonExistentParametersCodeFixProvider : XmlDocumentationCodeFixProvider + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(XmlDocumentationCodeFixProvider)), Shared] + public sealed class XmlDocumentationMissingInCSharpCodeFixProvider : XmlDocumentationCodeFixProvider { + public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.XmlDocumentation_MissingInCSharp.ToDiagnosticId()); + public override SyntaxNode FixParameters(MethodDeclarationSyntax method, SyntaxNode root) { var documentationNode = method.GetLeadingTrivia().Select(x => x.GetStructure()).OfType().First(); @@ -39,9 +42,8 @@ private static IEnumerable GetAllNodesToRemove(IEnumerable FixParametersAsync(context.Document, diagnostic, c)), diagnostic); + context.RegisterCodeFix(CodeAction.Create(Resources.XmlDocumentationRemoveNonExistentParametersCodeFixProvider_Title, c => FixParametersAsync(context.Document, diagnostic, c)), diagnostic); return Task.FromResult(0); } } -} +} \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Maintainability/XmlDocumentationCreateMissingParametersCodeFixProvider.cs b/src/CSharp/CodeCracker/Maintainability/XmlDocumentationMissingInXmlCodeFixProvider.cs similarity index 87% rename from src/CSharp/CodeCracker/Maintainability/XmlDocumentationCreateMissingParametersCodeFixProvider.cs rename to src/CSharp/CodeCracker/Maintainability/XmlDocumentationMissingInXmlCodeFixProvider.cs index b9012de39..36e268fca 100644 --- a/src/CSharp/CodeCracker/Maintainability/XmlDocumentationCreateMissingParametersCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Maintainability/XmlDocumentationMissingInXmlCodeFixProvider.cs @@ -1,19 +1,22 @@ -using System.Composition; -using System.Linq; -using System.Threading.Tasks; -using CodeCracker.Properties; +using CodeCracker.Properties; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; using static Microsoft.CodeAnalysis.CSharp.SyntaxKind; namespace CodeCracker.CSharp.Maintainability { - [ExportCodeFixProvider(LanguageNames.CSharp, nameof(XmlDocumentationCodeFixProvider)), Shared] - public sealed class XmlDocumentationCreateMissingParametersCodeFixProvider : XmlDocumentationCodeFixProvider + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(XmlDocumentationCodeFixProvider)), Shared] + public sealed class XmlDocumentationMissingInXmlCodeFixProvider : XmlDocumentationCodeFixProvider { + public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.XmlDocumentation_MissingInXml.ToDiagnosticId()); + private const string WHITESPACE = @" "; public override SyntaxNode FixParameters(MethodDeclarationSyntax method, SyntaxNode root) @@ -23,7 +26,7 @@ public override SyntaxNode FixParameters(MethodDeclarationSyntax method, SyntaxN var methodParameterWithDocParameter = GetMethodParametersWithDocParameters(method, documentationNode); var newFormation = newDocumentationNode.Content.OfType(); - + var nodesToAdd = methodParameterWithDocParameter.Where(p => p.Item2 == null) .Select(x => CreateParamenterXmlDocumentation(x.Item1.Identifier.ValueText, method.Identifier.ValueText)) .Union(newFormation) @@ -83,11 +86,8 @@ private static SyntaxList CreateXmlAttributes(string paramen public override sealed Task RegisterCodeFixesAsync(CodeFixContext context) { var diagnostic = context.Diagnostics.First(); - if (diagnostic.Properties["kind"] == "missingDoc") - context.RegisterCodeFix(CodeAction.Create(Resources.XmlDocumentationCreateMissingParametersCodeFixProvider_Title, c => FixParametersAsync(context.Document, diagnostic, c)), diagnostic); + context.RegisterCodeFix(CodeAction.Create(Resources.XmlDocumentationCreateMissingParametersCodeFixProvider_Title, c => FixParametersAsync(context.Document, diagnostic, c)), diagnostic); return Task.FromResult(0); } } - - -} +} \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Performance/EmptyFinalizerAnalyzer.cs b/src/CSharp/CodeCracker/Performance/EmptyFinalizerAnalyzer.cs index eaa7a5deb..cd5a4f333 100644 --- a/src/CSharp/CodeCracker/Performance/EmptyFinalizerAnalyzer.cs +++ b/src/CSharp/CodeCracker/Performance/EmptyFinalizerAnalyzer.cs @@ -17,7 +17,7 @@ public class EmptyFinalizerAnalyzer : DiagnosticAnalyzer + "Garbage Collector when no longer used." + "It will instead be placed in the finalizer queue needlessly using resources."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.EmptyFinalizer.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Performance/MakeLocalVariableConstWhenItIsPossibleAnalyzer.cs b/src/CSharp/CodeCracker/Performance/MakeLocalVariableConstWhenItIsPossibleAnalyzer.cs index c809e5db5..f49aba20a 100644 --- a/src/CSharp/CodeCracker/Performance/MakeLocalVariableConstWhenItIsPossibleAnalyzer.cs +++ b/src/CSharp/CodeCracker/Performance/MakeLocalVariableConstWhenItIsPossibleAnalyzer.cs @@ -12,10 +12,10 @@ public class MakeLocalVariableConstWhenItIsPossibleAnalyzer : DiagnosticAnalyzer { internal const string Title = "Make Local Variable Constant."; - internal const string MessageFormat = "This variables can be made const."; + internal const string MessageFormat = "This variable can be made const."; internal const string Category = SupportedCategories.Performance; const string Description = "This variable is assigned a constant value and never changed it can be made 'const'"; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.MakeLocalVariableConstWhenItIsPossible.ToDiagnosticId(), Title, MessageFormat, @@ -61,6 +61,7 @@ static bool IsDeclarationConstFriendly(LocalDeclarationStatementSyntax declarati // if reference type, value is null? var variableTypeName = declaration.Declaration.Type; var variableType = semanticModel.GetTypeInfo(variableTypeName).ConvertedType; + if (variableType.TypeKind == TypeKind.Pointer) return false; if (variableType.IsReferenceType && variableType.SpecialType != SpecialType.System_String && constantValue.Value != null) return false; // nullable? diff --git a/src/CSharp/CodeCracker/Performance/RemoveWhereWhenItIsPossibleAnalyzer.cs b/src/CSharp/CodeCracker/Performance/RemoveWhereWhenItIsPossibleAnalyzer.cs index 3671bb065..9ba262a53 100644 --- a/src/CSharp/CodeCracker/Performance/RemoveWhereWhenItIsPossibleAnalyzer.cs +++ b/src/CSharp/CodeCracker/Performance/RemoveWhereWhenItIsPossibleAnalyzer.cs @@ -25,9 +25,18 @@ public class RemoveWhereWhenItIsPossibleAnalyzer : DiagnosticAnalyzer "Any", "Single", "SingleOrDefault", - "Count" + "Count", + "FirstAsync", + "FirstOrDefaultAsync", + "LastAsync", + "LastOrDefaultAsync", + "AnyAsync", + "SingleAsync", + "SingleOrDefaultAsync", + "CountAsync" }; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.RemoveWhereWhenItIsPossible.ToDiagnosticId(), Title, MessageFormat, @@ -46,40 +55,36 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) { if (context.IsGenerated()) return; var whereInvoke = (InvocationExpressionSyntax)context.Node; - if (GetNameOfTheInvokedMethod(whereInvoke) != "Where") return; + var nameOfWhereInvoke = GetNameOfTheInvokedMethod(whereInvoke); + if (nameOfWhereInvoke?.ToString() != "Where") return; + if (ArgumentsDoNotMatch(whereInvoke)) return; var nextMethodInvoke = whereInvoke.Parent. FirstAncestorOrSelf(); + if (nextMethodInvoke == null) return; - var candidate = GetNameOfTheInvokedMethod(nextMethodInvoke); + var candidate = GetNameOfTheInvokedMethod(nextMethodInvoke)?.ToString(); if (!supportedMethods.Contains(candidate)) return; if (nextMethodInvoke.ArgumentList.Arguments.Any()) return; var properties = new Dictionary { { "methodName", candidate } }.ToImmutableDictionary(); - var diagnostic = Diagnostic.Create(Rule, GetNameExpressionOfTheInvokedMethod(whereInvoke).GetLocation(), properties, candidate); + var diagnostic = Diagnostic.Create(Rule, nameOfWhereInvoke.GetLocation(), properties, candidate); context.ReportDiagnostic(diagnostic); } - internal static string GetNameOfTheInvokedMethod(InvocationExpressionSyntax invoke) + private static bool ArgumentsDoNotMatch(InvocationExpressionSyntax whereInvoke) { - if (invoke == null) return null; - - var memberAccess = invoke.ChildNodes() - .OfType() - .FirstOrDefault(); - - return GetNameExpressionOfTheInvokedMethod(invoke)?.ToString(); + var arguments = whereInvoke.ArgumentList.Arguments; + if (arguments.Count != 1) return true; + var expression = arguments.First()?.Expression; + if (expression == null) return true; + if (expression is SimpleLambdaExpressionSyntax) return false; + var parenthesizedLambda = expression as ParenthesizedLambdaExpressionSyntax; + if (parenthesizedLambda == null) return true; + return parenthesizedLambda.ParameterList.Parameters.Count != 1; } - internal static SimpleNameSyntax GetNameExpressionOfTheInvokedMethod(InvocationExpressionSyntax invoke) - { - if (invoke == null) return null; - - var memberAccess = invoke.ChildNodes() - .OfType() - .FirstOrDefault(); - - return memberAccess?.Name; - } + private static SimpleNameSyntax GetNameOfTheInvokedMethod(InvocationExpressionSyntax invoke) => + invoke.ChildNodes().OfType().FirstOrDefault()?.Name; } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Performance/SealedAttributeAnalyzer.cs b/src/CSharp/CodeCracker/Performance/SealedAttributeAnalyzer.cs index 9f630d7b8..dc4cf7553 100644 --- a/src/CSharp/CodeCracker/Performance/SealedAttributeAnalyzer.cs +++ b/src/CSharp/CodeCracker/Performance/SealedAttributeAnalyzer.cs @@ -15,7 +15,7 @@ public class SealedAttributeAnalyzer : DiagnosticAnalyzer + "inheritence hierarchy of the attribute class. " + "Marking the type as sealed eliminate this search and can improve performance"; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId.SealedAttribute.ToDiagnosticId(), + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId.SealedAttribute.ToDiagnosticId(), Title, MessageFormat, Category, diff --git a/src/CSharp/CodeCracker/Performance/StringBuilderInLoopAnalyzer.cs b/src/CSharp/CodeCracker/Performance/StringBuilderInLoopAnalyzer.cs index cab72e9b4..60960be01 100644 --- a/src/CSharp/CodeCracker/Performance/StringBuilderInLoopAnalyzer.cs +++ b/src/CSharp/CodeCracker/Performance/StringBuilderInLoopAnalyzer.cs @@ -14,10 +14,9 @@ public class StringBuilderInLoopAnalyzer : DiagnosticAnalyzer internal const string Title = "Don't concatenate strings in loops"; internal const string MessageFormat = "Don't concatenate '{0}' in a loop"; internal const string Category = SupportedCategories.Performance; - const string Description = "Do not concatenate a string on a loop. It will alocate a lot of memory." - + "Use a StringBuilder instead. It will will require less allocation, less garbage collector work, less CPU cycles, and less overall time."; + const string Description = "Don't concatenate strings in a loop. Using a StringBuilder will require less memory and time."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), Title, MessageFormat, @@ -36,11 +35,11 @@ private static void AnalyzeAssignment(SyntaxNodeAnalysisContext context) { if (context.IsGenerated()) return; var assignmentExpression = (AssignmentExpressionSyntax)context.Node; - var whileStatement = assignmentExpression.FirstAncestorOfType(typeof(WhileStatementSyntax), + var loopStatement = assignmentExpression.FirstAncestorOfType(typeof(WhileStatementSyntax), typeof(ForStatementSyntax), typeof(ForEachStatementSyntax), typeof(DoStatementSyntax)); - if (whileStatement == null) return; + if (loopStatement == null) return; var semanticModel = context.SemanticModel; var arrayAccess = assignmentExpression.Left as ElementAccessExpressionSyntax; var symbolForAssignment = arrayAccess != null @@ -58,7 +57,17 @@ private static void AnalyzeAssignment(SyntaxNodeAnalysisContext context) } else if (type.Name != "String") return; // Do not analyze a string declared within the loop. - if (symbolForAssignment is ILocalSymbol && whileStatement.DescendantTokens(((ILocalSymbol)symbolForAssignment).DeclaringSyntaxReferences[0].Span).Any()) return; + if (symbolForAssignment is ILocalSymbol && loopStatement.DescendantTokens(((ILocalSymbol)symbolForAssignment).DeclaringSyntaxReferences[0].Span).Any()) return; + var memberAccess = assignmentExpression.Left as MemberAccessExpressionSyntax; + if (memberAccess != null) + { + var memberAccessExpressionSymbol = semanticModel.GetSymbolInfo(memberAccess.Expression).Symbol as ILocalSymbol; + if (memberAccessExpressionSymbol != null) + { + if (loopStatement.DescendantTokens(memberAccessExpressionSymbol.DeclaringSyntaxReferences[0].Span).Any()) + return; + } + } if (assignmentExpression.IsKind(SyntaxKind.SimpleAssignmentExpression)) { if (!(assignmentExpression.Right?.IsKind(SyntaxKind.AddExpression) ?? false)) return; @@ -74,4 +83,4 @@ private static void AnalyzeAssignment(SyntaxNodeAnalysisContext context) context.ReportDiagnostic(diagnostic); } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Performance/UseStaticRegexIsMatchAnalyzer.cs b/src/CSharp/CodeCracker/Performance/UseStaticRegexIsMatchAnalyzer.cs index c19ed7f34..19c4e2374 100644 --- a/src/CSharp/CodeCracker/Performance/UseStaticRegexIsMatchAnalyzer.cs +++ b/src/CSharp/CodeCracker/Performance/UseStaticRegexIsMatchAnalyzer.cs @@ -15,7 +15,7 @@ public class UseStaticRegexIsMatchAnalyzer : DiagnosticAnalyzer const string Description = "Instantiating the Regex object multiple times might be bad for performance. " + "You may want to use the static IsMatch method from Regex class and/or compile the regex."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.UseStaticRegexIsMatch.ToDiagnosticId(), Title, MessageFormat, @@ -40,7 +40,7 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) var methodSymbol = context.SemanticModel.GetSymbolInfo(memberExpression).Symbol; if (methodSymbol?.ContainingType.ToString() != "System.Text.RegularExpressions.Regex" || methodSymbol.IsStatic) return; - + if (!(memberExpression.Expression is IdentifierNameSyntax)) return; var variableSymbol = context.SemanticModel.GetSymbolInfo(((IdentifierNameSyntax)memberExpression.Expression).Identifier.Parent).Symbol; if (variableSymbol?.Kind != SymbolKind.Local) return; diff --git a/src/CSharp/CodeCracker/Performance/UseStaticRegexIsMatchCodeFixProvider.cs b/src/CSharp/CodeCracker/Performance/UseStaticRegexIsMatchCodeFixProvider.cs index 49629f5d5..9dd542c56 100644 --- a/src/CSharp/CodeCracker/Performance/UseStaticRegexIsMatchCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Performance/UseStaticRegexIsMatchCodeFixProvider.cs @@ -45,6 +45,9 @@ private async static Task MakeRegexStaticAsync(Document document, Diag newArgumentList = newArgumentList.Insert(2, SyntaxFactory.Argument(SyntaxFactory.IdentifierName("RegexOptions.Compiled"))); var memberExpression = (MemberAccessExpressionSyntax)invocationDeclaration.Expression; + //todo: use simplification with `.WithAdditionalAnnotations(Simplifier.Annotation)`, like the example below. + //right now it is not working because tests can't find System.Text.RegularExpressions.Regex, we need to fix that + //var isMatchExpression = SyntaxFactory.InvocationExpression(SyntaxFactory.IdentifierName("System.Text.RegularExpressions.Regex.IsMatch").WithAdditionalAnnotations(Simplifier.Annotation), var isMatchExpression = SyntaxFactory.InvocationExpression(SyntaxFactory.IdentifierName("Regex.IsMatch"), SyntaxFactory.ArgumentList(newArgumentList)) .WithLeadingTrivia(memberExpression.GetLeadingTrivia()) diff --git a/src/CSharp/CodeCracker/Properties/AssemblyInfo.cs b/src/CSharp/CodeCracker/Properties/AssemblyInfo.cs index 08892ea95..8c689a126 100644 --- a/src/CSharp/CodeCracker/Properties/AssemblyInfo.cs +++ b/src/CSharp/CodeCracker/Properties/AssemblyInfo.cs @@ -8,11 +8,11 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CodeCracker")] -[assembly: AssemblyCopyright("Copyright © 2014-2015")] +[assembly: AssemblyCopyright("Copyright © 2014-2018")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: NeutralResourcesLanguage("en")] [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("1.0.0.10")] -[assembly: InternalsVisibleTo("CodeCracker.Test.CSharp")] \ No newline at end of file +[assembly: AssemblyFileVersion("1.1.0.0")] +[assembly: InternalsVisibleTo("CodeCracker.Test.CSharp")] diff --git a/src/CSharp/CodeCracker/Refactoring/AddBracesToSwitchSectionsAnalyzer.cs b/src/CSharp/CodeCracker/Refactoring/AddBracesToSwitchSectionsAnalyzer.cs index bbbd7b15a..6ef64c506 100644 --- a/src/CSharp/CodeCracker/Refactoring/AddBracesToSwitchSectionsAnalyzer.cs +++ b/src/CSharp/CodeCracker/Refactoring/AddBracesToSwitchSectionsAnalyzer.cs @@ -14,7 +14,7 @@ public class AddBracesToSwitchSectionsAnalyzer : DiagnosticAnalyzer internal const string MessageFormat = "Add braces for each section in this switch"; internal const string Category = SupportedCategories.Refactoring; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.AddBracesToSwitchSections.ToDiagnosticId(), Title, MessageFormat, @@ -47,6 +47,8 @@ internal static bool HasBraces(SwitchSectionSyntax section) if (section.Statements.First() is BlockSyntax && section.Statements.Last() is BreakStatementSyntax) return true; break; + default: + break; } return false; } diff --git a/src/CSharp/CodeCracker/Refactoring/AllowMembersOrderingAnalyzer.cs b/src/CSharp/CodeCracker/Refactoring/AllowMembersOrderingAnalyzer.cs index 1a7148ccd..eda3b461a 100644 --- a/src/CSharp/CodeCracker/Refactoring/AllowMembersOrderingAnalyzer.cs +++ b/src/CSharp/CodeCracker/Refactoring/AllowMembersOrderingAnalyzer.cs @@ -13,7 +13,7 @@ public class AllowMembersOrderingAnalyzer : DiagnosticAnalyzer internal const string MessageFormat = "Ordering member inside this type."; internal const string Category = SupportedCategories.Refactoring; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.AllowMembersOrdering.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Refactoring/ChangeAnyToAllAnalyzer.cs b/src/CSharp/CodeCracker/Refactoring/ChangeAnyToAllAnalyzer.cs index 946b0b4d4..8e52451bb 100644 --- a/src/CSharp/CodeCracker/Refactoring/ChangeAnyToAllAnalyzer.cs +++ b/src/CSharp/CodeCracker/Refactoring/ChangeAnyToAllAnalyzer.cs @@ -3,19 +3,22 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; +using System.Linq; namespace CodeCracker.CSharp.Refactoring { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class ChangeAnyToAllAnalyzer : DiagnosticAnalyzer { + private const string speculativeAnnotationDescription = "ChangeAnyToAllAnalyzer_speculativeAnnotation"; + private static readonly SyntaxAnnotation speculativeAnnotation = new SyntaxAnnotation(speculativeAnnotationDescription); internal const string MessageAny = "Change Any to All"; internal const string MessageAll = "Change All to Any"; internal const string TitleAny = MessageAny; internal const string TitleAll = MessageAll; internal const string Category = SupportedCategories.Refactoring; - internal static DiagnosticDescriptor RuleAny = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor RuleAny = new DiagnosticDescriptor( DiagnosticId.ChangeAnyToAll.ToDiagnosticId(), TitleAny, MessageAny, @@ -23,7 +26,7 @@ public class ChangeAnyToAllAnalyzer : DiagnosticAnalyzer DiagnosticSeverity.Hidden, isEnabledByDefault: true, helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.ChangeAnyToAll)); - internal static DiagnosticDescriptor RuleAll = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor RuleAll = new DiagnosticDescriptor( DiagnosticId.ChangeAllToAny.ToDiagnosticId(), TitleAll, MessageAll, @@ -42,28 +45,35 @@ public class ChangeAnyToAllAnalyzer : DiagnosticAnalyzer private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context) { if (context.IsGenerated()) return; - var invocation = context.Node as InvocationExpressionSyntax; - if (invocation.Parent.IsKind(SyntaxKind.ExpressionStatement)) return; + var invocation = (InvocationExpressionSyntax)context.Node; + if (invocation.Parent?.IsKind(SyntaxKind.ExpressionStatement) ?? true) return; var diagnosticToRaise = GetCorrespondingDiagnostic(context.SemanticModel, invocation); if (diagnosticToRaise == null) return; - var diagnostic = Diagnostic.Create(diagnosticToRaise, ((MemberAccessExpressionSyntax)invocation.Expression).Name.GetLocation()); + var diagnostic = Diagnostic.Create(diagnosticToRaise, GetName(invocation).GetLocation()); context.ReportDiagnostic(diagnostic); } private static DiagnosticDescriptor GetCorrespondingDiagnostic(SemanticModel semanticModel, InvocationExpressionSyntax invocation) { - var methodName = (invocation?.Expression as MemberAccessExpressionSyntax)?.Name?.ToString(); - var nameToCheck = methodName == "Any" ? allName : methodName == "All" ? anyName : null; + var methodName = GetName(invocation); + var methodNameText = methodName?.ToString(); + var nameToCheck = methodNameText == "Any" ? allName : methodNameText == "All" ? anyName : null; if (nameToCheck == null) return null; - var invocationSymbol = semanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol; - if (invocationSymbol?.Parameters.Length != 1) return null; + var methodSymbol = semanticModel.GetSymbolInfo(invocation.Expression).Symbol as IMethodSymbol; + if (methodSymbol?.Parameters.Length != 1) return null; if (!IsLambdaWithoutBody(invocation)) return null; - var otherInvocation = invocation.WithExpression(((MemberAccessExpressionSyntax)invocation.Expression).WithName(nameToCheck)); - var otherInvocationSymbol = semanticModel.GetSpeculativeSymbolInfo(invocation.SpanStart, otherInvocation, SpeculativeBindingOption.BindAsExpression); - if (otherInvocationSymbol.Symbol == null) return null; - if (methodName == "Any") - return RuleAny; - return RuleAll; + if (!OtherMethodExists(invocation, nameToCheck, semanticModel)) return null; + return methodNameText == "Any" ? RuleAny : RuleAll; + } + + public static SimpleNameSyntax GetName(InvocationExpressionSyntax invocation) + { + SimpleNameSyntax methodName = null; + if (invocation.Expression.IsKind(SyntaxKind.MemberBindingExpression)) + methodName = ((MemberBindingExpressionSyntax)invocation.Expression).Name; + else if (invocation.Expression.IsKind(SyntaxKind.SimpleMemberAccessExpression)) + methodName = ((MemberAccessExpressionSyntax)invocation.Expression).Name; + return methodName; } private static bool IsLambdaWithoutBody(InvocationExpressionSyntax invocation) @@ -73,5 +83,35 @@ private static bool IsLambdaWithoutBody(InvocationExpressionSyntax invocation) if (lambda == null) return false; return !(lambda.Body is BlockSyntax); } + + private static bool OtherMethodExists(InvocationExpressionSyntax invocation, SimpleNameSyntax nameToCheck, SemanticModel semanticModel) + { + var otherExpression = CreateExpressionWithNewName(invocation, nameToCheck); + var statement = invocation.FirstAncestorOrSelfThatIsAStatement(); + SemanticModel speculativeModel; + if (statement != null) + { + var otherStatement = statement.ReplaceNode(invocation.Expression, otherExpression); + if (!semanticModel.TryGetSpeculativeSemanticModel(statement.SpanStart, otherStatement, out speculativeModel)) return false; + } + else + { + var arrow = (ArrowExpressionClauseSyntax)invocation.FirstAncestorOfKind(SyntaxKind.ArrowExpressionClause); + if (arrow == null) return false; + var otherArrow = arrow.ReplaceNode(invocation.Expression, otherExpression); + if (!semanticModel.TryGetSpeculativeSemanticModel(arrow.SpanStart, otherArrow, out speculativeModel)) return false; + } + var symbol = speculativeModel.GetSymbolInfo(speculativeModel.SyntaxTree.GetRoot().GetAnnotatedNodes(speculativeAnnotationDescription).First()).Symbol; + return symbol != null; + } + + public static ExpressionSyntax CreateExpressionWithNewName(InvocationExpressionSyntax invocation, SimpleNameSyntax nameToCheck) + { + var otherExpression = invocation.Expression.IsKind(SyntaxKind.MemberBindingExpression) + ? (ExpressionSyntax)((MemberBindingExpressionSyntax)invocation.Expression).WithName(nameToCheck).WithAdditionalAnnotations(speculativeAnnotation) + //avoid this, already checked before: if (invocation.Expression.IsKind(SyntaxKind.SimpleMemberAccessExpression)): + : ((MemberAccessExpressionSyntax)invocation.Expression).WithName(nameToCheck).WithAdditionalAnnotations(speculativeAnnotation); + return otherExpression; + } } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Refactoring/ChangeAnyToAllCodeFixProvider.cs b/src/CSharp/CodeCracker/Refactoring/ChangeAnyToAllCodeFixProvider.cs index dc7579599..f3798c535 100644 --- a/src/CSharp/CodeCracker/Refactoring/ChangeAnyToAllCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Refactoring/ChangeAnyToAllCodeFixProvider.cs @@ -42,18 +42,23 @@ private async static Task ConvertAsync(Document document, Location dia private static SyntaxNode ReplaceInvocation(InvocationExpressionSyntax invocation, ExpressionSyntax newInvocation, SyntaxNode root) { - if (invocation.Parent.IsKind(SyntaxKind.LogicalNotExpression)) - return root.ReplaceNode(invocation.Parent, newInvocation); - var negatedInvocation = SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, newInvocation); - var newRoot = root.ReplaceNode(invocation, negatedInvocation); + ExpressionSyntax lastExpression = invocation; + while (lastExpression.Parent.IsAnyKind(SyntaxKind.MemberBindingExpression, SyntaxKind.SimpleMemberAccessExpression, + SyntaxKind.ConditionalAccessExpression, SyntaxKind.LogicalNotExpression)) + lastExpression = (ExpressionSyntax)lastExpression.Parent; + var lastExpressionWithNewInvocation = lastExpression.ReplaceNode(invocation, newInvocation); + if (lastExpression.IsKind(SyntaxKind.LogicalNotExpression)) + return root.ReplaceNode(lastExpression, ((PrefixUnaryExpressionSyntax)lastExpressionWithNewInvocation).Operand); + var negatedLastExpression = SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, lastExpressionWithNewInvocation); + var newRoot = root.ReplaceNode(lastExpression, negatedLastExpression); return newRoot; } internal static ExpressionSyntax CreateNewInvocation(InvocationExpressionSyntax invocation) { - var methodName = ((MemberAccessExpressionSyntax)invocation.Expression).Name.ToString(); + var methodName = ChangeAnyToAllAnalyzer.GetName(invocation).ToString(); var nameToCheck = methodName == "Any" ? ChangeAnyToAllAnalyzer.allName : ChangeAnyToAllAnalyzer.anyName; - var newInvocation = invocation.WithExpression(((MemberAccessExpressionSyntax)invocation.Expression).WithName(nameToCheck)); + var newInvocation = invocation.WithExpression(ChangeAnyToAllAnalyzer.CreateExpressionWithNewName(invocation, nameToCheck)); var comparisonExpression = (ExpressionSyntax)((LambdaExpressionSyntax)newInvocation.ArgumentList.Arguments.First().Expression).Body; var newComparisonExpression = CreateNewComparison(comparisonExpression); newComparisonExpression = RemoveParenthesis(newComparisonExpression); diff --git a/src/CSharp/CodeCracker/Refactoring/ComputeExpressionAnalyzer.cs b/src/CSharp/CodeCracker/Refactoring/ComputeExpressionAnalyzer.cs index 8b883aebb..63224cd90 100644 --- a/src/CSharp/CodeCracker/Refactoring/ComputeExpressionAnalyzer.cs +++ b/src/CSharp/CodeCracker/Refactoring/ComputeExpressionAnalyzer.cs @@ -15,7 +15,7 @@ public class ComputeExpressionAnalyzer : DiagnosticAnalyzer internal const string Category = SupportedCategories.Refactoring; const string Description = "You may change an expression for its value if the expression is made of literal values."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.ComputeExpression.ToDiagnosticId(), Title, Message, diff --git a/src/CSharp/CodeCracker/Refactoring/ComputeExpressionCodeFixProvider.cs b/src/CSharp/CodeCracker/Refactoring/ComputeExpressionCodeFixProvider.cs index bf7e40551..cdfd8a563 100644 --- a/src/CSharp/CodeCracker/Refactoring/ComputeExpressionCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Refactoring/ComputeExpressionCodeFixProvider.cs @@ -36,7 +36,7 @@ private async static Task ComputeExpressionAsync(Document document, Lo var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var node = root.FindNode(diagnosticLocation.SourceSpan); var parenthesized = node as ParenthesizedExpressionSyntax; - var expression = (BinaryExpressionSyntax)(parenthesized != null ? parenthesized.Expression : node); + var expression = (BinaryExpressionSyntax)(parenthesized != null ? parenthesized.Expression : node is ArgumentSyntax ? ((ArgumentSyntax)node).Expression : node); var newRoot = ComputeExpression(node, expression, root, semanticModel); if (newRoot == null) return null; var newDocument = document.WithSyntaxRoot(newRoot); @@ -47,7 +47,11 @@ internal static SyntaxNode ComputeExpression(SyntaxNode nodeToReplace, BinaryExp { var result = semanticModel.GetConstantValue(expression); if (!result.HasValue) return null; - var newExpression = SyntaxFactory.ParseExpression(System.Convert.ToString(result.Value, System.Globalization.CultureInfo.InvariantCulture)); + SyntaxNode newExpression = SyntaxFactory.ParseExpression(System.Convert.ToString(result.Value, System.Globalization.CultureInfo.InvariantCulture)); + if(nodeToReplace is ArgumentSyntax) + { + newExpression = SyntaxFactory.Argument((ExpressionSyntax)newExpression); + } var newRoot = root.ReplaceNode(nodeToReplace, newExpression); return newRoot; } diff --git a/src/CSharp/CodeCracker/Refactoring/IntroduceFieldFromConstructorAnalyzer.cs b/src/CSharp/CodeCracker/Refactoring/IntroduceFieldFromConstructorAnalyzer.cs index 545ee93f1..7b20fe932 100644 --- a/src/CSharp/CodeCracker/Refactoring/IntroduceFieldFromConstructorAnalyzer.cs +++ b/src/CSharp/CodeCracker/Refactoring/IntroduceFieldFromConstructorAnalyzer.cs @@ -1,4 +1,5 @@ -using Microsoft.CodeAnalysis; +using CodeCracker.Properties; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -11,12 +12,12 @@ namespace CodeCracker.CSharp.Refactoring [DiagnosticAnalyzer(LanguageNames.CSharp)] public class IntroduceFieldFromConstructorAnalyzer : DiagnosticAnalyzer { - internal const string Title = "Consider introduce field for constructor parameters."; - internal const string MessageFormat = "Introduce a field for parameter: {0}"; internal const string Category = SupportedCategories.Refactoring; - const string Description = "Consider introduce field for constructor parameters."; + internal static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.IntroduceFieldFromConstructorAnalyzer_Title), Resources.ResourceManager, typeof(Resources)); + internal static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.IntroduceFieldFromConstructorAnalyzer_Description), Resources.ResourceManager, typeof(Resources)); + internal static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.IntroduceFieldFromConstructorAnalyzer_MessageFormat), Resources.ResourceManager, typeof(Resources)); - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.IntroduceFieldFromConstructor.ToDiagnosticId(), Title, MessageFormat, @@ -34,6 +35,10 @@ private static void AnalyzeConstructor(SyntaxNodeAnalysisContext context) { if (context.IsGenerated()) return; var constructorMethod = (ConstructorDeclarationSyntax)context.Node; + + var type = constructorMethod.FirstAncestorOrSelf(); + if (type == null || !(type is ClassDeclarationSyntax || type is StructDeclarationSyntax)) return; + var parameters = constructorMethod.ParameterList.Parameters; if (constructorMethod.Body == null) return; diff --git a/src/CSharp/CodeCracker/Refactoring/IntroduceFieldFromConstructorCodeFixProvider.cs b/src/CSharp/CodeCracker/Refactoring/IntroduceFieldFromConstructorCodeFixProvider.cs index c931ab313..9351d73b9 100644 --- a/src/CSharp/CodeCracker/Refactoring/IntroduceFieldFromConstructorCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Refactoring/IntroduceFieldFromConstructorCodeFixProvider.cs @@ -1,3 +1,5 @@ +using CodeCracker.FixAllProviders; +using CodeCracker.Properties; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; @@ -13,13 +15,15 @@ namespace CodeCracker.CSharp.Refactoring { - [ExportCodeFixProvider(LanguageNames.CSharp, Name =nameof(IntroduceFieldFromConstructorCodeFixProvider)), Shared] - public class IntroduceFieldFromConstructorCodeFixProvider : CodeFixProvider + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(IntroduceFieldFromConstructorCodeFixProvider)), Shared] + public sealed class IntroduceFieldFromConstructorCodeFixProvider : CodeFixProvider, IFixDocumentInternalsOnly { + private static readonly FixAllProvider FixAllProvider = new DocumentCodeFixProviderAll(Resources.IntroduceFieldFromConstructorCodeFixProvider_Title); + private static readonly string MessageFormat = Resources.IntroduceFieldFromConstructorCodeFixProvider_MessageFormat; + public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.IntroduceFieldFromConstructor.ToDiagnosticId()); - public readonly static string MessageFormat = "Introduce field: {0} from constructor."; - public sealed override FixAllProvider GetFixAllProvider() => IntroduceFieldFromConstructorCodeFixAllProvider.Instance; + public sealed override FixAllProvider GetFixAllProvider() => FixAllProvider; public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) { @@ -29,49 +33,65 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) return Task.FromResult(0); } - public async static Task IntroduceFieldFromConstructorDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + public async Task FixDocumentAsync(SyntaxNode nodeWithDiagnostic, Document document, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var diagnosticSpan = diagnostic.Location.SourceSpan; - var parameter = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); - var constructor = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); + var parameter = nodeWithDiagnostic.AncestorsAndSelf().OfType().First(); + var constructor = nodeWithDiagnostic.AncestorsAndSelf().OfType().First(); var newRoot = IntroduceFieldFromConstructor(root, constructor, parameter); var newDocument = document.WithSyntaxRoot(newRoot); return document.WithSyntaxRoot(newRoot); } + public async Task IntroduceFieldFromConstructorDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var diagnosticSpan = diagnostic.Location.SourceSpan; + var nodeWithDiagnostic = root.FindToken(diagnosticSpan.Start).Parent; + return await FixDocumentAsync(nodeWithDiagnostic, document, cancellationToken); + } + public static SyntaxNode IntroduceFieldFromConstructor(SyntaxNode root, ConstructorDeclarationSyntax constructorStatement, ParameterSyntax parameter) { - var oldClass = constructorStatement.FirstAncestorOrSelf(); - var newClass = oldClass; + // There are no constructors in interfaces, therefore all types remaining type (class and struct) are fine. + var oldType = constructorStatement.FirstAncestorOrSelf(); + var newType = oldType; var fieldName = parameter.Identifier.ValueText; var fieldType = parameter.Type; - var members = ExtractMembersFromClass(oldClass.Members); + var members = ExtractMembersFromClass(oldType.Members); + var addMember = false; if (!members.Any(p => p.Key == fieldName && p.Value == fieldType.ToString())) { var identifierPostFix = 0; while (members.Any(p => p.Key == fieldName)) fieldName = parameter.Identifier.ValueText + ++identifierPostFix; - var newField = SyntaxFactory.FieldDeclaration(SyntaxFactory.VariableDeclaration(parameter.Type) - .WithVariables(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier(fieldName))))) - .WithModifiers(SyntaxFactory.TokenList(new[] { SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword) })) - .WithAdditionalAnnotations(Formatter.Annotation); - newClass = newClass.WithMembers(newClass.Members.Insert(0, newField)).WithoutAnnotations(Formatter.Annotation); + + addMember = true; } + var assignmentField = SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.ThisExpression(), SyntaxFactory.IdentifierName(fieldName)), SyntaxFactory.IdentifierName(parameter.Identifier.ValueText))); var newConstructor = constructorStatement.WithBody(constructorStatement.Body.AddStatements(assignmentField)); - newClass = newClass.ReplaceNode(newClass.DescendantNodes().OfType().First(), newConstructor); - var newRoot = root.ReplaceNode(oldClass, newClass); + newType = newType.ReplaceNode(constructorStatement, newConstructor); + + if (addMember) + { + var newField = SyntaxFactory.FieldDeclaration(SyntaxFactory.VariableDeclaration(parameter.Type) + .WithVariables(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier(fieldName))))) + .WithModifiers(SyntaxFactory.TokenList(new[] { SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword) })) + .WithAdditionalAnnotations(Formatter.Annotation); + newType = newType.WithMembers(newType.Members.Insert(0, newField)).WithoutAnnotations(Formatter.Annotation); + } + var newRoot = root.ReplaceNode(oldType, newType); return newRoot; } - private static Dictionary ExtractMembersFromClass(SyntaxList classMembers) + private static Dictionary ExtractMembersFromClass(SyntaxList typeMembers) { var members = new Dictionary(); - foreach (var m in classMembers) + foreach (var m in typeMembers) { var name = ""; if (m.IsKind(SyntaxKind.MethodDeclaration)) diff --git a/src/CSharp/CodeCracker/Refactoring/InvertForAnalyzer.cs b/src/CSharp/CodeCracker/Refactoring/InvertForAnalyzer.cs index fb2363ba7..75dc49e56 100644 --- a/src/CSharp/CodeCracker/Refactoring/InvertForAnalyzer.cs +++ b/src/CSharp/CodeCracker/Refactoring/InvertForAnalyzer.cs @@ -13,7 +13,7 @@ public class InvertForAnalyzer : DiagnosticAnalyzer internal const string MessageFormat = "Make it a for loop that {0} the counter."; internal const string Category = SupportedCategories.Refactoring; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.InvertFor.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Refactoring/MergeNestedIfAnalyzer.cs b/src/CSharp/CodeCracker/Refactoring/MergeNestedIfAnalyzer.cs index 0a04b580f..e4ffa333b 100644 --- a/src/CSharp/CodeCracker/Refactoring/MergeNestedIfAnalyzer.cs +++ b/src/CSharp/CodeCracker/Refactoring/MergeNestedIfAnalyzer.cs @@ -13,7 +13,7 @@ public class MergeNestedIfAnalyzer : DiagnosticAnalyzer internal const string MessageFormat = "Merge nested ifs into a single if"; internal const string Category = SupportedCategories.Refactoring; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.MergeNestedIf.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Refactoring/MergeNestedIfCodeFixProvider.cs b/src/CSharp/CodeCracker/Refactoring/MergeNestedIfCodeFixProvider.cs index 872ca8a8b..ec24113a7 100644 --- a/src/CSharp/CodeCracker/Refactoring/MergeNestedIfCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Refactoring/MergeNestedIfCodeFixProvider.cs @@ -40,8 +40,11 @@ private async static Task MergeIfsAsync(Document document, Location di private static SyntaxNode MergeIfs(IfStatementSyntax ifStatement, SyntaxNode root) { var nestedIf = (IfStatementSyntax)ifStatement.Statement.GetSingleStatementFromPossibleBlock(); + var nestedCondition = nestedIf.Condition; + if (nestedCondition.IsAnyKind(SyntaxKind.LogicalOrExpression, SyntaxKind.ConditionalExpression, SyntaxKind.CoalesceExpression)) + nestedCondition = SyntaxFactory.ParenthesizedExpression(nestedCondition); var newIf = ifStatement - .WithCondition(SyntaxFactory.BinaryExpression(SyntaxKind.LogicalAndExpression, ifStatement.Condition, nestedIf.Condition)) + .WithCondition(SyntaxFactory.BinaryExpression(SyntaxKind.LogicalAndExpression, ifStatement.Condition, nestedCondition)) .WithStatement(nestedIf.Statement) .WithLeadingTrivia(ifStatement.GetLeadingTrivia().AddRange(nestedIf.GetLeadingTrivia())) .WithAdditionalAnnotations(Formatter.Annotation); diff --git a/src/CSharp/CodeCracker/Refactoring/NumericLiteralAnalyzer.cs b/src/CSharp/CodeCracker/Refactoring/NumericLiteralAnalyzer.cs index e3d46b32b..f60caa320 100644 --- a/src/CSharp/CodeCracker/Refactoring/NumericLiteralAnalyzer.cs +++ b/src/CSharp/CodeCracker/Refactoring/NumericLiteralAnalyzer.cs @@ -13,7 +13,7 @@ public class NumericLiteralAnalyzer : DiagnosticAnalyzer internal const string Message = "You may change {0} to a {1} literal type."; internal const string Category = SupportedCategories.Refactoring; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.NumericLiteral.ToDiagnosticId(), Title, Message, diff --git a/src/CSharp/CodeCracker/Refactoring/ParameterRefactoryAnalyzer.cs b/src/CSharp/CodeCracker/Refactoring/ParameterRefactoryAnalyzer.cs index f5fdbe6dc..f113c416e 100644 --- a/src/CSharp/CodeCracker/Refactoring/ParameterRefactoryAnalyzer.cs +++ b/src/CSharp/CodeCracker/Refactoring/ParameterRefactoryAnalyzer.cs @@ -14,7 +14,7 @@ public class ParameterRefactoryAnalyzer : DiagnosticAnalyzer internal const string MessageFormat = "When the method has more than three parameters, use new class."; internal const string Category = SupportedCategories.Refactoring; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.ParameterRefactory.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Refactoring/ParameterRefactoryCodeFixProvider.cs b/src/CSharp/CodeCracker/Refactoring/ParameterRefactoryCodeFixProvider.cs index 9bc595e14..27740c976 100644 --- a/src/CSharp/CodeCracker/Refactoring/ParameterRefactoryCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Refactoring/ParameterRefactoryCodeFixProvider.cs @@ -38,97 +38,69 @@ private async static Task NewClassAsync(Document document, Diagnostic SyntaxNode newRootParameter = null; if (oldNamespace == null) { - var newCompilation = NewCompilationFactory((CompilationUnitSyntax)oldClass.Parent, oldClass, oldMethod); + var newCompilation = AddParameterClassToCompilationUnitAndUpdateClassToUseNamespace((CompilationUnitSyntax)oldClass.Parent, oldClass, oldMethod); newRootParameter = root.ReplaceNode(oldClass.Parent, newCompilation); return document.WithSyntaxRoot(newRootParameter); } - var newNameSpace = NewNameSpaceFactory(oldNamespace, oldClass, oldMethod); - newRootParameter = root.ReplaceNode(oldNamespace, newNameSpace); + var newNamespace = AddParameterClassToNamespaceAndUpdateClassToUseNamespace(oldNamespace, oldClass, oldMethod); + newRootParameter = root.ReplaceNode(oldNamespace, newNamespace).WithAdditionalAnnotations(Formatter.Annotation); return document.WithSyntaxRoot(newRootParameter); } - private static List NewPropertyClassFactory(MethodDeclarationSyntax methodOld) + private static List CreateProperties(MethodDeclarationSyntax method) { var newGetSyntax = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); var newSetSyntax = SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); var acessorSyntax = SyntaxFactory.AccessorList( - SyntaxFactory.Token(SyntaxKind.OpenBraceToken), - SyntaxFactory.List(new[] { newGetSyntax, newSetSyntax }), - SyntaxFactory.Token(SyntaxKind.CloseBraceToken)); + SyntaxFactory.Token(SyntaxKind.OpenBraceToken), + SyntaxFactory.List(new[] { newGetSyntax, newSetSyntax }), + SyntaxFactory.Token(SyntaxKind.CloseBraceToken)); var properties = new List(); - foreach (ParameterSyntax param in methodOld.ParameterList.Parameters) + foreach (ParameterSyntax param in method.ParameterList.Parameters) { - var property = SyntaxFactory.PropertyDeclaration( - default(SyntaxList), - SyntaxFactory.TokenList(new[] { SyntaxFactory.Token(SyntaxKind.PublicKeyword) }), - param.Type, - default(ExplicitInterfaceSpecifierSyntax), - SyntaxFactory.Identifier(FirstLetteToUpper(param.Identifier.Text)), - acessorSyntax); + var property = SyntaxFactory.PropertyDeclaration(param.Type, FirstLetteToUpper(param.Identifier.Text)) + .WithModifiers(SyntaxFactory.TokenList(new[] { SyntaxFactory.Token(SyntaxKind.PublicKeyword) })) + .WithAccessorList(acessorSyntax); properties.Add(property); - } return properties; } - private static ClassDeclarationSyntax NewClassParameterFactory(string newNameClass, List Property) + private static ClassDeclarationSyntax CreateParameterClass(string newNameClass, MethodDeclarationSyntax oldMethod) { + var properties = CreateProperties(oldMethod); return SyntaxFactory.ClassDeclaration(newNameClass) - .WithMembers(SyntaxFactory.List(Property)) - .WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword))) - .WithAdditionalAnnotations(Formatter.Annotation); - + .WithMembers(SyntaxFactory.List(properties)) + .WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword))); } - private static NamespaceDeclarationSyntax NewNameSpaceFactory(NamespaceDeclarationSyntax OldNameSpace, ClassDeclarationSyntax OldClass, MethodDeclarationSyntax OldMethod) + private static NamespaceDeclarationSyntax AddParameterClassToNamespaceAndUpdateClassToUseNamespace(NamespaceDeclarationSyntax oldNamespace, ClassDeclarationSyntax oldClass, MethodDeclarationSyntax oldMethod) { - var newNameSpace = OldNameSpace; - var className = $"NewClass{OldMethod.Identifier.Text}"; - var memberNameSpaceOld = (from member in OldNameSpace.Members - where member == OldClass - select member).FirstOrDefault(); - newNameSpace = OldNameSpace.ReplaceNode(memberNameSpaceOld, NewClassFactory(className, OldClass, OldMethod)); - var newParameterClass = NewClassParameterFactory(className, NewPropertyClassFactory(OldMethod)); - newNameSpace = newNameSpace - .WithMembers(newNameSpace.Members.Add(newParameterClass)) - .WithAdditionalAnnotations(Formatter.Annotation); - return newNameSpace; + var className = $"NewClass{oldMethod.Identifier.Text}"; + var newParameterClass = CreateParameterClass(className, oldMethod); + var newNamespace = oldNamespace.ReplaceNode(oldClass, UpdateClassToUseNewParameterClass(className, oldClass, oldMethod)) + .AddMembers(newParameterClass); + return newNamespace; } - private static ClassDeclarationSyntax NewClassFactory(string className, ClassDeclarationSyntax classOld, MethodDeclarationSyntax methodOld) + private static ClassDeclarationSyntax UpdateClassToUseNewParameterClass(string className, ClassDeclarationSyntax classOld, MethodDeclarationSyntax methodOld) { - - var newParameter = SyntaxFactory.Parameter(SyntaxFactory.Identifier($"{className} {FirstLetteToLower(className)}")); - - var paremeters = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList().Add(newParameter)) - .WithAdditionalAnnotations(Formatter.Annotation); - - - var newMethod = SyntaxFactory.MethodDeclaration(methodOld.ReturnType, methodOld.Identifier.Text) - .WithModifiers(methodOld.Modifiers) - .WithParameterList(paremeters) - .WithBody(methodOld.Body) - .WithAdditionalAnnotations(Formatter.Annotation); - + var newParameter = SyntaxFactory.Parameter(SyntaxFactory.Identifier(FirstLetteToLower(className))).WithType(SyntaxFactory.ParseTypeName(className)); + var parameters = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList().Add(newParameter)); + var newMethod = methodOld.WithParameterList(parameters); var newClass = classOld.ReplaceNode(methodOld, newMethod); - return newClass; } - private static CompilationUnitSyntax NewCompilationFactory(CompilationUnitSyntax OldCompilation, ClassDeclarationSyntax OldClass, MethodDeclarationSyntax OldMethod) + private static CompilationUnitSyntax AddParameterClassToCompilationUnitAndUpdateClassToUseNamespace(CompilationUnitSyntax oldCompilation, ClassDeclarationSyntax oldClass, MethodDeclarationSyntax oldMethod) { - var newNameSpace = OldCompilation; - var className = $"NewClass{OldMethod.Identifier.Text}"; - var OldMemberNameSpace = (from member in OldCompilation.Members - where member == OldClass - select member).FirstOrDefault(); - newNameSpace = OldCompilation.ReplaceNode(OldMemberNameSpace, NewClassFactory(className, OldClass, OldMethod)); - var newParameterClass = NewClassParameterFactory(className, NewPropertyClassFactory(OldMethod)); - return newNameSpace.WithMembers(newNameSpace.Members.Add(newParameterClass)) - .WithAdditionalAnnotations(Formatter.Annotation); - + var className = $"NewClass{oldMethod.Identifier.Text}"; + var newParameterClass = CreateParameterClass(className, oldMethod); + var newNamespace = oldCompilation.ReplaceNode(oldClass, UpdateClassToUseNewParameterClass(className, oldClass, oldMethod)) + .AddMembers(newParameterClass); + return newNamespace; } private static string FirstLetteToUpper(string text) => diff --git a/src/CSharp/CodeCracker/Refactoring/PropertyChangedEventArgsUnnecessaryAllocationAnalyzer.cs b/src/CSharp/CodeCracker/Refactoring/PropertyChangedEventArgsUnnecessaryAllocationAnalyzer.cs new file mode 100644 index 000000000..a3f061c3e --- /dev/null +++ b/src/CSharp/CodeCracker/Refactoring/PropertyChangedEventArgsUnnecessaryAllocationAnalyzer.cs @@ -0,0 +1,238 @@ +using CodeCracker.Properties; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFacts; +using static Microsoft.CodeAnalysis.CSharp.SyntaxKind; + +namespace CodeCracker.CSharp.Refactoring +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class PropertyChangedEventArgsUnnecessaryAllocationAnalyzer : DiagnosticAnalyzer + { + private const string PropertyChangedEventArgsClassName = "PropertyChangedEventArgs"; + + internal const string Category = SupportedCategories.Refactoring; + + private static readonly IdentifierExtractor ExtractIdentifier = new IdentifierExtractor(); + private static readonly IsArgumentALiteralOrNameof IsAnyArgumentLiteralOrNameof = new IsArgumentALiteralOrNameof(); + + internal static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.PropertyChangedEventArgsUnnecessaryAllocation_Title), Resources.ResourceManager, typeof(Resources)); + internal static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.PropertyChangedEventArgsUnnecessaryAllocation_Description), Resources.ResourceManager, typeof(Resources)); + internal static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.PropertyChangedEventArgsUnnecessaryAllocation_MessageFormat), Resources.ResourceManager, typeof(Resources)); + + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( + DiagnosticId.PropertyChangedEventArgsUnnecessaryAllocation.ToDiagnosticId(), + Title, + MessageFormat, + Category, + DiagnosticSeverity.Hidden, + isEnabledByDefault: true, + description: Description, + helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.PropertyChangedEventArgsUnnecessaryAllocation)); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(LanguageVersion.CSharp6, PropertyChangedCreation, SyntaxKind.ObjectCreationExpression); + } + + private static void PropertyChangedCreation(SyntaxNodeAnalysisContext context) + { + var propertyChangedEventArgsCreationExpr = (ObjectCreationExpressionSyntax)context.Node; + var identifier = propertyChangedEventArgsCreationExpr.Type.Accept(ExtractIdentifier); + if (ShouldReportDiagnostic(propertyChangedEventArgsCreationExpr, identifier.ValueText)) + { + var data = new PropertyChangedEventArgsAnalyzerData(propertyChangedEventArgsCreationExpr); + + context.ReportDiagnostic(Diagnostic.Create(Rule, propertyChangedEventArgsCreationExpr.GetLocation(), data.ToDiagnosticProperties())); + } + } + + private static bool ShouldReportDiagnostic(ObjectCreationExpressionSyntax propertyChangedExpr, string identifierName) => + IsPropertyChangedEventArgs(identifierName) + && propertyChangedExpr.ArgumentList.Accept(IsAnyArgumentLiteralOrNameof) + && !IsAlreadyStatic(propertyChangedExpr); + + private static bool IsPropertyChangedEventArgs(string s) => string.Equals(PropertyChangedEventArgsClassName, s, StringComparison.Ordinal); + + private static bool IsAlreadyStatic(ObjectCreationExpressionSyntax objectCreationExpr) + { + var result = false; + var memberForObjectCreationExpr = objectCreationExpr.FirstAncestorOrSelfThatIsAMember(); + switch (memberForObjectCreationExpr.Kind()) + { + case SyntaxKind.ConstructorDeclaration: + var constructorDeclaration = (ConstructorDeclarationSyntax)memberForObjectCreationExpr; + result = ContainsStaticModifier(constructorDeclaration.Modifiers); + break; + case SyntaxKind.FieldDeclaration: + var fieldDeclaration = (FieldDeclarationSyntax)memberForObjectCreationExpr; + result = ContainsStaticModifier(fieldDeclaration.Modifiers); + break; + default: + break; + } + return result; + } + + private static bool ContainsStaticModifier(SyntaxTokenList modifiers) => modifiers.Any(StaticKeyword); + + private class IdentifierExtractor : CSharpSyntaxVisitor + { + public override SyntaxToken VisitIdentifierName(IdentifierNameSyntax node) => node.Identifier; + public override SyntaxToken VisitQualifiedName(QualifiedNameSyntax node) => VisitIdentifierName((IdentifierNameSyntax)node.Right); + } + + private class IsArgumentALiteralOrNameof : CSharpSyntaxVisitor + { + public override bool VisitArgumentList(ArgumentListSyntax node) => node.Arguments.Any(arg => arg.Accept(this)); + public override bool VisitArgument(ArgumentSyntax node) => node.Expression.Accept(this); + public override bool VisitLiteralExpression(LiteralExpressionSyntax node) => true; + + public override bool VisitIdentifierName(IdentifierNameSyntax node) + => string.Equals("nameof", node.Identifier.ValueText, StringComparison.Ordinal); + + public override bool VisitInvocationExpression(InvocationExpressionSyntax node) => node.Expression.Accept(this); + } + } + + public class PropertyChangedEventArgsAnalyzerData + { + private const string ArgumentKeyName = "Argument"; + private const string IsNullKeyName = "IsNull"; + private const string IsNameofKeyName = "NameOf"; + private const string TypeKeyName = "Type"; + private const string SuffixAllProperties = "AllProperties"; + + public readonly string FullTypeName; + public readonly string ArgumentName; + public readonly bool ArgumentIsNullLiteral; + public readonly bool ArgumentIsNameofExpression; + public readonly string StaticFieldIdentifierNameProposition; + + private PropertyChangedEventArgsAnalyzerData(string fullTypeName, string argumentName, string isNullLiteral, string isNameof) + { + FullTypeName = fullTypeName; + ArgumentName = argumentName ?? string.Empty; + ArgumentIsNullLiteral = bool.Parse(isNullLiteral); + ArgumentIsNameofExpression = bool.Parse(isNameof); + + StaticFieldIdentifierNameProposition = $"PropertyChangedEventArgsFor{SuffixForStaticInstance()}"; + } + + public PropertyChangedEventArgsAnalyzerData(ObjectCreationExpressionSyntax propertyChangedInstanceCreationExpr) + { + if (propertyChangedInstanceCreationExpr == null) + throw new ArgumentNullException(nameof(propertyChangedInstanceCreationExpr)); + var analyzer = new PropertyChangedCreationSyntaxAnalyzer(); + propertyChangedInstanceCreationExpr.ArgumentList.Accept(analyzer); + FullTypeName = propertyChangedInstanceCreationExpr.Type.ToString(); + ArgumentName = analyzer.IdentifierName; + ArgumentIsNullLiteral = analyzer.NullLiteralExpressionFound; + ArgumentIsNameofExpression = analyzer.NameofExpressionFound; + } + + public string StaticFieldIdentifierName(IEnumerable nameHints) => nameHints.Contains(StaticFieldIdentifierNameProposition) ? + CreateNewIdenfitierName(StaticFieldIdentifierNameProposition, 1, nameHints) : StaticFieldIdentifierNameProposition; + + public MemberDeclarationSyntax PropertyChangedEventArgsStaticField(IEnumerable nameHints) + { + return FieldDeclaration(List(), + TokenList(Token(PrivateKeyword), Token(StaticKeyword), Token(ReadOnlyKeyword)), + VariableDeclaration(FieldType(FullTypeName), VariableName(StaticFieldIdentifierName(nameHints)))); + } + + public ImmutableDictionary ToDiagnosticProperties() + { + var dict = ImmutableDictionary.CreateBuilder(); + + dict.Add(ArgumentKeyName, ArgumentName); + dict.Add(IsNullKeyName, ArgumentIsNullLiteral.ToString()); + dict.Add(IsNameofKeyName, ArgumentIsNameofExpression.ToString()); + dict.Add(TypeKeyName, FullTypeName); + + return dict.ToImmutable(); + } + + public static PropertyChangedEventArgsAnalyzerData FromDiagnosticProperties(ImmutableDictionary properties) + { + return new PropertyChangedEventArgsAnalyzerData( + properties[TypeKeyName], properties[ArgumentKeyName], properties[IsNullKeyName], properties[IsNameofKeyName]); + } + + private EqualsValueClauseSyntax PropertyChangedEventArgsInstance() => + EqualsValueClause(Token(EqualsToken), + ObjectCreationExpression(ParseTypeName(FullTypeName), + ArgumentList( + SingletonSeparatedList( + PropertyChangedEventArgsCtorArgument())), default(InitializerExpressionSyntax))); + + private ArgumentSyntax PropertyChangedEventArgsCtorArgument() => + Argument(ArgumentIsNameofExpression + ? ParseExpression($"nameof({ArgumentName})") + : ArgumentIsNullLiteral ? LiteralExpression(NullLiteralExpression) : StringLiteral(ArgumentName)); + + private string SuffixForStaticInstance() + { + return ArgumentIsNullLiteral || ArgumentNameIsStar() ? SuffixAllProperties : MakeValidIdentifier(ArgumentName); + } + + private SeparatedSyntaxList VariableName(string fieldName) => + SeparatedList(new[] + { + VariableDeclarator(fieldName) + .WithInitializer(PropertyChangedEventArgsInstance()) + }); + + private bool ArgumentNameIsStar() => string.Equals(ArgumentName, "*", StringComparison.OrdinalIgnoreCase); + + private static string MakeValidIdentifier(string s) => IsValidIdentifier(s) ? s : SanitizeIdentifierName(s); + + private static string SanitizeIdentifierName(string s) => s.ToCharArray() + .Aggregate(new StringBuilder(), + (sanitized, nextChar) => IsValidIdentifier($"{sanitized.ToString()}{nextChar}") ? sanitized.Append(nextChar) : sanitized) + .ToString(); + + private static LiteralExpressionSyntax StringLiteral(string s) => LiteralExpression(StringLiteralExpression, Literal(s)); + + private static IdentifierNameSyntax FieldType(string type) => IdentifierName(type); + + private static string CreateNewIdenfitierName(string oldName, int extension, IEnumerable nameHints) + { + var number = int.Parse(new string(oldName.ToCharArray().Reverse().TakeWhile(char.IsNumber).DefaultIfEmpty('0').ToArray())); + var proposition = $"{oldName}{number + extension}"; + return nameHints.Contains(proposition) ? CreateNewIdenfitierName(oldName, extension + 1, nameHints) : proposition; + } + + private class PropertyChangedCreationSyntaxAnalyzer : CSharpSyntaxWalker + { + public bool NullLiteralExpressionFound { get; private set; } + public bool NameofExpressionFound { get; private set; } + public string IdentifierName { get; private set; } + + public override void VisitLiteralExpression(LiteralExpressionSyntax node) + { + NameofExpressionFound = false; + NullLiteralExpressionFound = node.IsKind(NullLiteralExpression); + IdentifierName = node.Token.ValueText; + } + + public override void VisitInvocationExpression(InvocationExpressionSyntax node) + { + NameofExpressionFound = true; + base.VisitInvocationExpression(node); + } + + public override void VisitIdentifierName(IdentifierNameSyntax node) => IdentifierName = node.Identifier.ValueText; + } + } +} \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Refactoring/PropertyChangedEventArgsUnnecessaryAllocationCodeFixProvider.cs b/src/CSharp/CodeCracker/Refactoring/PropertyChangedEventArgsUnnecessaryAllocationCodeFixProvider.cs new file mode 100644 index 000000000..834906f67 --- /dev/null +++ b/src/CSharp/CodeCracker/Refactoring/PropertyChangedEventArgsUnnecessaryAllocationCodeFixProvider.cs @@ -0,0 +1,88 @@ +using CodeCracker.Properties; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Text; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace CodeCracker.CSharp.Refactoring +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PropertyChangedEventArgsUnnecessaryAllocationCodeFixProvider)), Shared] + public sealed class PropertyChangedEventArgsUnnecessaryAllocationCodeFixProvider : CodeFixProvider + { + public LocalizableString CodeActionTitle = new LocalizableResourceString(nameof(Resources.PropertyChangedEventArgsUnnecessaryAllocation_CodeActionTitle), Resources.ResourceManager, typeof(Resources)); + + public override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create(DiagnosticId.PropertyChangedEventArgsUnnecessaryAllocation.ToDiagnosticId()); + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + var diagnostic = context.Diagnostics.First(); + context.RegisterCodeFix( + CodeAction.Create(CodeActionTitle.ToString(), + token => ChangePropertyChangedEventArgsToStaticAsync(context.Document, diagnostic.Location, diagnostic.Properties, token), + nameof(PropertyChangedEventArgsUnnecessaryAllocationCodeFixProvider)), diagnostic); + + return Task.FromResult(true); + } + + private static async Task ChangePropertyChangedEventArgsToStaticAsync(Document document, Location location, + ImmutableDictionary properties, CancellationToken cancellationToken) + { + var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken); + var data = PropertyChangedEventArgsAnalyzerData.FromDiagnosticProperties(properties); + var newSyntaxRoot = new PropertyChangedUnnecessaryAllocationRewriter(data, location.SourceSpan).Visit(syntaxRoot); + return document.WithSyntaxRoot(newSyntaxRoot); + } + + private class PropertyChangedUnnecessaryAllocationRewriter : CSharpSyntaxRewriter + { + private readonly PropertyChangedEventArgsAnalyzerData contextData; + private readonly TextSpan diagnosticLocation; + private bool diagnosticLocationFound; + private IEnumerable nameHints; + + public PropertyChangedUnnecessaryAllocationRewriter(PropertyChangedEventArgsAnalyzerData contextData, TextSpan diagnosticLocation) + { + this.contextData = contextData; + this.diagnosticLocation = diagnosticLocation; + } + + public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) + { + nameHints = node.Members.OfType() + .SelectMany(fd => fd.Declaration.Variables.Select(vds => vds.Identifier.ValueText)); + + var traverseResult = base.VisitClassDeclaration(node) as ClassDeclarationSyntax; + var result = diagnosticLocationFound ? AddPropertyChangedEventArgsStaticField(traverseResult, nameHints ?? Enumerable.Empty()) : traverseResult; + diagnosticLocationFound = false; + return result; + } + + public override SyntaxNode VisitObjectCreationExpression(ObjectCreationExpressionSyntax node) + { + if(node.Span == diagnosticLocation) + { + diagnosticLocationFound = true; + + return ParseExpression(contextData.StaticFieldIdentifierName(nameHints ?? Enumerable.Empty())) + .WithLeadingTrivia(node.GetLeadingTrivia()) + .WithTrailingTrivia(node.GetTrailingTrivia()); + } + return base.VisitObjectCreationExpression(node); + } + + private ClassDeclarationSyntax AddPropertyChangedEventArgsStaticField(ClassDeclarationSyntax declaration, IEnumerable nameHints) => declaration + .WithMembers(declaration.Members.Insert(0, contextData.PropertyChangedEventArgsStaticField(nameHints).WithAdditionalAnnotations(Formatter.Annotation))); + } + } +} diff --git a/src/CSharp/CodeCracker/Refactoring/ReplaceWithGetterOnlyAutoPropertyAnalyzer.cs b/src/CSharp/CodeCracker/Refactoring/ReplaceWithGetterOnlyAutoPropertyAnalyzer.cs new file mode 100644 index 000000000..cc8b4ef13 --- /dev/null +++ b/src/CSharp/CodeCracker/Refactoring/ReplaceWithGetterOnlyAutoPropertyAnalyzer.cs @@ -0,0 +1,74 @@ +using CodeCracker.Properties; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System; +using System.Collections.Immutable; +using System.Linq; + +namespace CodeCracker.CSharp.Refactoring +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ReplaceWithGetterOnlyAutoPropertyAnalyzer : DiagnosticAnalyzer + { + internal const string Category = SupportedCategories.Refactoring; + internal static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.ReplaceWithGetterOnlyAutoPropertyAnalyzer_Title), Resources.ResourceManager, typeof(Resources)); + internal static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.ReplaceWithGetterOnlyAutoPropertyAnalyzer_Description), Resources.ResourceManager, typeof(Resources)); + internal static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.ReplaceWithGetterOnlyAutoPropertyAnalyzer_MessageFormat), Resources.ResourceManager, typeof(Resources)); + + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( + DiagnosticId.ReplaceWithGetterOnlyAutoProperty.ToDiagnosticId(), + Title, + MessageFormat, + Category, + DiagnosticSeverity.Hidden, + isEnabledByDefault: true, + description: Description, + helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.ReplaceWithGetterOnlyAutoProperty)); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSymbolAction(LanguageVersion.CSharp6, AnalyzeSymbol, SymbolKind.Property); + } + + private static void AnalyzeSymbol(SymbolAnalysisContext context) + { + if (context.IsGenerated()) return; + var namedTypeSymbol = (IPropertySymbol)context.Symbol; + var properties = GetPropsWithOnlyGettersAndReadonlyBackingField(namedTypeSymbol, context); + if (properties == null) return; + var diagnostic = Diagnostic.Create(Rule, properties.Locations[0], properties.Name); + context.ReportDiagnostic(diagnostic); + } + private static ISymbol GetPropsWithOnlyGettersAndReadonlyBackingField(IPropertySymbol propertySymbol, SymbolAnalysisContext context) + { + if (!propertySymbol.IsReadOnly || propertySymbol.IsStatic || !propertySymbol.CanBeReferencedByName) return null; + var getMethod = propertySymbol.GetMethod; + if (getMethod == null) return null; + var reference = getMethod.DeclaringSyntaxReferences.FirstOrDefault(); + if (reference == null) return null; + var declaration = reference.GetSyntax(context.CancellationToken) as AccessorDeclarationSyntax; + if (declaration?.Body == null) return null; + var returnNode = declaration.Body.ChildNodes().FirstOrDefault(); + if (returnNode?.Kind() != SyntaxKind.ReturnStatement) return null; + var fieldNode = returnNode.ChildNodes().FirstOrDefault(); + if (fieldNode == null) return null; + if (fieldNode.Kind() == SyntaxKind.SimpleMemberAccessExpression) + fieldNode = (fieldNode as MemberAccessExpressionSyntax).Name; + if (fieldNode.Kind() != SyntaxKind.IdentifierName) return null; + var model = context.Compilation.GetSemanticModel(fieldNode.SyntaxTree); + var symbolInfo = model.GetSymbolInfo(fieldNode).Symbol as IFieldSymbol; + if (symbolInfo != null && + symbolInfo.IsReadOnly && + (symbolInfo.DeclaredAccessibility == Accessibility.Private || symbolInfo.DeclaredAccessibility == Accessibility.NotApplicable) && + symbolInfo.ContainingType == propertySymbol.ContainingType && + symbolInfo.Type.Equals(propertySymbol.Type)) + + return propertySymbol; + return null; + } + } +} diff --git a/src/CSharp/CodeCracker/Refactoring/ReplaceWithGetterOnlyAutoPropertyCodeFixProvider.cs b/src/CSharp/CodeCracker/Refactoring/ReplaceWithGetterOnlyAutoPropertyCodeFixProvider.cs new file mode 100644 index 000000000..d8b59a702 --- /dev/null +++ b/src/CSharp/CodeCracker/Refactoring/ReplaceWithGetterOnlyAutoPropertyCodeFixProvider.cs @@ -0,0 +1,150 @@ +using CodeCracker.Properties; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Text; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System; +using CodeCracker.FixAllProviders; + +namespace CodeCracker.CSharp.Refactoring +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ReplaceWithGetterOnlyAutoPropertyCodeFixProvider)), Shared] + public class ReplaceWithGetterOnlyAutoPropertyCodeFixProvider : CodeFixProvider, IFixDocumentInternalsOnly + { + private static readonly FixAllProvider FixAllProvider = new DocumentCodeFixProviderAll(Resources.ReplaceWithGetterOnlyAutoPropertyCodeFixProvider_Title); + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.ReplaceWithGetterOnlyAutoProperty.ToDiagnosticId()); + + public override FixAllProvider GetFixAllProvider() => FixAllProvider; + + public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) + { + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + + context.RegisterCodeFix( + CodeAction.Create( + title: Resources.ReplaceWithGetterOnlyAutoPropertyCodeFixProvider_Title, + createChangedDocument: c => ReplaceByGetterOnlyAutoPropertyAsync(context.Document, diagnosticSpan, c), + equivalenceKey: nameof(ReplaceWithGetterOnlyAutoPropertyCodeFixProvider)), + diagnostic); + return Task.FromResult(0); + } + private async Task ReplaceByGetterOnlyAutoPropertyAsync(Document document, TextSpan propertyDeclarationSpan, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var node = root.FindNode(propertyDeclarationSpan); + return await FixDocumentAsync(node, document, cancellationToken).ConfigureAwait(false); + } + + public async Task FixDocumentAsync(SyntaxNode nodeWithDiagnostic, Document document, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await document.GetSemanticModelAsync().ConfigureAwait(false); + var newRoot = await ReplacePropertyInSyntaxRootAsync(nodeWithDiagnostic, cancellationToken, semanticModel, root).ConfigureAwait(false); + var newDocument = document.WithSyntaxRoot(newRoot); + return newDocument; + } + + + private static async Task ReplacePropertyInSyntaxRootAsync(SyntaxNode propertyDeclarationSyntaxNode, CancellationToken cancellationToken, SemanticModel semanticModel, SyntaxNode root) + { + var property = propertyDeclarationSyntaxNode.AncestorsAndSelf().OfType().First(); + var fieldVariableDeclaratorSyntax = await GetFieldDeclarationSyntaxNodeAsync(property, cancellationToken, semanticModel).ConfigureAwait(false); + if (fieldVariableDeclaratorSyntax == null) return root; + var fieldReferences = await GetFieldReferencesAsync(fieldVariableDeclaratorSyntax, cancellationToken, semanticModel).ConfigureAwait(false); + var nodesToUpdate = fieldReferences.Cast().Union(Enumerable.Repeat(property, 1)).Union(Enumerable.Repeat(fieldVariableDeclaratorSyntax, 1)); + var newRoot = FixWithTrackNode(root, property, fieldVariableDeclaratorSyntax, nodesToUpdate); + return newRoot; + } + + private static SyntaxNode FixWithTrackNode(SyntaxNode root, PropertyDeclarationSyntax property, VariableDeclaratorSyntax fieldVariableDeclaratorSyntax, IEnumerable nodesToUpdate) + { + var newRoot = root.TrackNodes(nodesToUpdate); + var fieldReferences = nodesToUpdate.OfType(); + foreach (var identifier in fieldReferences) + { + var trackedIdentifierNode = newRoot.GetCurrentNode(identifier); + var newIdentifierExpression = SyntaxFactory.IdentifierName(property.Identifier.Text); + newIdentifierExpression = newIdentifierExpression.WithLeadingTrivia(trackedIdentifierNode.GetLeadingTrivia()).WithTrailingTrivia(trackedIdentifierNode.GetTrailingTrivia()).WithAdditionalAnnotations(Formatter.Annotation); + newRoot = newRoot.ReplaceNode(trackedIdentifierNode, newIdentifierExpression); + } + var prop = newRoot.GetCurrentNode(nodesToUpdate.OfType().Single()); + var fieldInitilization = GetFieldInitialization(fieldVariableDeclaratorSyntax); + var getter = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + var accessorList = SyntaxFactory.AccessorList( + SyntaxFactory.List(new[] { + getter + })); + var newProp = prop.WithAccessorList(accessorList); + if (fieldInitilization != null) + newProp = newProp.WithInitializer(fieldInitilization).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + newProp = newProp.WithLeadingTrivia(prop.GetLeadingTrivia()).WithTrailingTrivia(prop.GetTrailingTrivia()).WithAdditionalAnnotations(Formatter.Annotation); + newRoot = newRoot.ReplaceNode(prop, newProp); + var variableDeclarator = newRoot.GetCurrentNode(nodesToUpdate.OfType().Single()); + var declaration = variableDeclarator.AncestorsAndSelf().OfType().First(); + if (declaration.Variables.Count == 1) + { + var fieldDeclaration = declaration.AncestorsAndSelf().OfType().First(); + newRoot = newRoot.RemoveNode(fieldDeclaration, SyntaxRemoveOptions.KeepUnbalancedDirectives); + } + else + newRoot = newRoot.RemoveNode(variableDeclarator, SyntaxRemoveOptions.KeepUnbalancedDirectives); + return newRoot; + } + + private static EqualsValueClauseSyntax GetFieldInitialization(VariableDeclaratorSyntax fieldVariableDeclaratorSyntax) + { + var declaration = fieldVariableDeclaratorSyntax.AncestorsAndSelf().OfType().First(); + if (declaration == null) + return null; + var variableWithPotentialInitializer = declaration.Variables.SkipWhile(v => v != fieldVariableDeclaratorSyntax).FirstOrDefault(v => v.Initializer != null); + if (variableWithPotentialInitializer == null) + return null; + var initializer = variableWithPotentialInitializer.Initializer; + return initializer; + } + + private static async Task> GetFieldReferencesAsync(VariableDeclaratorSyntax fieldDeclarationSyntax, CancellationToken cancellationToken, SemanticModel semanticModel) + { + HashSet fieldReferences = null; + var fieldSymbol = semanticModel.GetDeclaredSymbol(fieldDeclarationSyntax, cancellationToken); + var declaredInType = fieldSymbol.ContainingType; + var references = declaredInType.DeclaringSyntaxReferences.Where(r => r.SyntaxTree == semanticModel.SyntaxTree); + foreach (var reference in references) + { + var allNodes = (await reference.GetSyntaxAsync(cancellationToken)).DescendantNodes(); + var allFieldReferenceNodes = from n in allNodes.OfType() + where n.Identifier.Text == fieldSymbol.Name + let nodeSymbolInfo = semanticModel.GetSymbolInfo(n, cancellationToken) + where object.Equals(nodeSymbolInfo.Symbol, fieldSymbol) + select n; + foreach (var fieldReference in allFieldReferenceNodes) + { + (fieldReferences ?? (fieldReferences = new HashSet())).Add(fieldReference); + } + } + return fieldReferences ?? Enumerable.Empty(); + } + + private static async Task GetFieldDeclarationSyntaxNodeAsync(PropertyDeclarationSyntax propertyDeclaration, CancellationToken cancellationToken, SemanticModel semanticModel) + { + var propertySymbol = semanticModel.GetDeclaredSymbol(propertyDeclaration, cancellationToken); + var declaredProperty = propertySymbol.GetMethod.DeclaringSyntaxReferences.FirstOrDefault(); + var declaredPropertySyntax = await declaredProperty.GetSyntaxAsync(cancellationToken); + var fieldIdentifier = declaredPropertySyntax.DescendantNodesAndTokens().FirstOrDefault(n => n.IsNode && n.Kind() == SyntaxKind.IdentifierName); + var fieldInfo = semanticModel.GetSymbolInfo(fieldIdentifier.AsNode()); + var fieldDeclaration = fieldInfo.Symbol.DeclaringSyntaxReferences.FirstOrDefault(); + var fieldDeclarationSyntax = await fieldDeclaration.GetSyntaxAsync(); + return fieldDeclarationSyntax as VariableDeclaratorSyntax; + } + } +} diff --git a/src/CSharp/CodeCracker/Refactoring/SplitIntoNestedIfAnalyzer.cs b/src/CSharp/CodeCracker/Refactoring/SplitIntoNestedIfAnalyzer.cs index ef483a284..e37d41246 100644 --- a/src/CSharp/CodeCracker/Refactoring/SplitIntoNestedIfAnalyzer.cs +++ b/src/CSharp/CodeCracker/Refactoring/SplitIntoNestedIfAnalyzer.cs @@ -13,7 +13,7 @@ public class SplitIntoNestedIfAnalyzer : DiagnosticAnalyzer internal const string Message = "Split into nested if."; internal const string Category = SupportedCategories.Refactoring; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.SplitIntoNestedIf.ToDiagnosticId(), Title, Message, diff --git a/src/CSharp/CodeCracker/Refactoring/SplitIntoNestedIfFixAllProvider.cs b/src/CSharp/CodeCracker/Refactoring/SplitIntoNestedIfFixAllProvider.cs index f1bf997d9..3ca46c2ef 100644 --- a/src/CSharp/CodeCracker/Refactoring/SplitIntoNestedIfFixAllProvider.cs +++ b/src/CSharp/CodeCracker/Refactoring/SplitIntoNestedIfFixAllProvider.cs @@ -11,7 +11,7 @@ public sealed class SplitIntoNestedIfFixAllProvider : FixAllProvider { private static readonly SyntaxAnnotation nestedIfAnnotation = new SyntaxAnnotation(nameof(SplitIntoNestedIfFixAllProvider)); private SplitIntoNestedIfFixAllProvider() { } - public static SplitIntoNestedIfFixAllProvider Instance = new SplitIntoNestedIfFixAllProvider(); + public static readonly SplitIntoNestedIfFixAllProvider Instance = new SplitIntoNestedIfFixAllProvider(); public override Task GetFixAsync(FixAllContext fixAllContext) { switch (fixAllContext.Scope) @@ -25,8 +25,9 @@ public override Task GetFixAsync(FixAllContext fixAllContext) case FixAllScope.Solution: return Task.FromResult(CodeAction.Create(SplitIntoNestedIfCodeFixProvider.MessageFormat, ct => GetFixedSolutionAsync(fixAllContext))); + default: + return null; } - return null; } private async static Task GetFixedSolutionAsync(FixAllContext fixAllContext) diff --git a/src/CSharp/CodeCracker/Refactoring/StringRepresentationAnalyzer.cs b/src/CSharp/CodeCracker/Refactoring/StringRepresentationAnalyzer.cs index 7e44a8e6a..3c7bbd8a3 100644 --- a/src/CSharp/CodeCracker/Refactoring/StringRepresentationAnalyzer.cs +++ b/src/CSharp/CodeCracker/Refactoring/StringRepresentationAnalyzer.cs @@ -11,7 +11,7 @@ namespace CodeCracker.CSharp.Refactoring [DiagnosticAnalyzer(LanguageNames.CSharp)] public class StringRepresentationAnalyzer : DiagnosticAnalyzer { - internal static DiagnosticDescriptor RegularStringRule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor RegularStringRule = new DiagnosticDescriptor( DiagnosticId.StringRepresentation_RegularString.ToDiagnosticId(), "Regular string", "Change to regular string", @@ -20,7 +20,7 @@ public class StringRepresentationAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.StringRepresentation_RegularString)); - internal static DiagnosticDescriptor VerbatimStringRule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor VerbatimStringRule = new DiagnosticDescriptor( DiagnosticId.StringRepresentation_VerbatimString.ToDiagnosticId(), "Verbatim string", "Change to verbatim string", diff --git a/src/CSharp/CodeCracker/Refactoring/StringRepresentationCodeFixProvider.cs b/src/CSharp/CodeCracker/Refactoring/StringRepresentationCodeFixProvider.cs index 3f1934ac1..bd8d714c6 100644 --- a/src/CSharp/CodeCracker/Refactoring/StringRepresentationCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Refactoring/StringRepresentationCodeFixProvider.cs @@ -13,7 +13,7 @@ namespace CodeCracker.CSharp.Refactoring { - [ExportCodeFixProvider(LanguageNames.CSharp, nameof(StringRepresentationCodeFixProvider)), Shared] + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(StringRepresentationCodeFixProvider)), Shared] public class StringRepresentationCodeFixProvider : CodeFixProvider { public const string Id = nameof(StringRepresentationCodeFixProvider); diff --git a/src/CSharp/CodeCracker/Reliability/UseConfigureAwaitFalseAnalyzer.cs b/src/CSharp/CodeCracker/Reliability/UseConfigureAwaitFalseAnalyzer.cs index e73b3bcf6..4af4bbf13 100644 --- a/src/CSharp/CodeCracker/Reliability/UseConfigureAwaitFalseAnalyzer.cs +++ b/src/CSharp/CodeCracker/Reliability/UseConfigureAwaitFalseAnalyzer.cs @@ -13,7 +13,7 @@ public class UseConfigureAwaitFalseAnalyzer : DiagnosticAnalyzer internal const string MessageFormat = "Consider using ConfigureAwait(false) on the awaited task."; internal const string Category = SupportedCategories.Reliability; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.UseConfigureAwaitFalse.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Style/AlwaysUseVarAnalyzer.cs b/src/CSharp/CodeCracker/Style/AlwaysUseVarAnalyzer.cs index 87e9795a6..7e5c5aab2 100644 --- a/src/CSharp/CodeCracker/Style/AlwaysUseVarAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/AlwaysUseVarAnalyzer.cs @@ -16,17 +16,28 @@ public class AlwaysUseVarAnalyzer : DiagnosticAnalyzer const string Description = "Usage of an implicit type improve readability of the code.\r\n" + "Code depending on types for their readability should be refactored with better variable " + "names or by introducing well-named methods."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor RuleNonPrimitives = new DiagnosticDescriptor( DiagnosticId.AlwaysUseVar.ToDiagnosticId(), Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, - description:Description, + description: Description, helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.AlwaysUseVar)); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + internal static readonly DiagnosticDescriptor RulePrimitives = new DiagnosticDescriptor( + DiagnosticId.AlwaysUseVarOnPrimitives.ToDiagnosticId(), + Title, + MessageFormat, + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: Description, + helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.AlwaysUseVarOnPrimitives)); + + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create(RuleNonPrimitives, RulePrimitives); public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement); @@ -42,6 +53,7 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) .FirstOrDefault(); if (variableDeclaration.Type.IsVar) return; + var isDynamic = (variableDeclaration.Type as IdentifierNameSyntax)?.Identifier.ValueText == "dynamic"; var semanticModel = context.SemanticModel; var variableTypeName = localDeclaration.Declaration.Type; @@ -52,9 +64,15 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) if (variable.Initializer == null) return; var conversion = semanticModel.ClassifyConversion(variable.Initializer.Value, variableType); if (!conversion.IsIdentity) return; + if (isDynamic) + { + var expressionReturnType = semanticModel.GetTypeInfo(variable.Initializer.Value); + if (expressionReturnType.Type.SpecialType == SpecialType.System_Object) return; + } } - var diagnostic = Diagnostic.Create(Rule, variableDeclaration.Type.GetLocation()); + var rule = variableType.IsPrimitive() ? RulePrimitives : RuleNonPrimitives; + var diagnostic = Diagnostic.Create(rule, variableDeclaration.Type.GetLocation()); context.ReportDiagnostic(diagnostic); } } diff --git a/src/CSharp/CodeCracker/Style/AlwaysUseVarCodeFixProvider.cs b/src/CSharp/CodeCracker/Style/AlwaysUseVarCodeFixProvider.cs index f88f139ad..664cf3b40 100644 --- a/src/CSharp/CodeCracker/Style/AlwaysUseVarCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Style/AlwaysUseVarCodeFixProvider.cs @@ -1,4 +1,5 @@ -using Microsoft.CodeAnalysis; +using System.Collections.Generic; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; @@ -16,7 +17,7 @@ namespace CodeCracker.CSharp.Style public class AlwaysUseVarCodeFixProvider : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(DiagnosticId.AlwaysUseVar.ToDiagnosticId()); + ImmutableArray.Create(DiagnosticId.AlwaysUseVar.ToDiagnosticId(), DiagnosticId.AlwaysUseVarOnPrimitives.ToDiagnosticId()); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; @@ -30,15 +31,49 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) private async static Task UseVarAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var diagnosticSpan = diagnostic.Location.SourceSpan; var localDeclaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); - var variableDeclaration = localDeclaration.ChildNodes() - .OfType() - .FirstOrDefault(); - var @var = SyntaxFactory.IdentifierName("var") - .WithLeadingTrivia(variableDeclaration.Type.GetLeadingTrivia()) - .WithTrailingTrivia(variableDeclaration.Type.GetTrailingTrivia()); - var newRoot = root.ReplaceNode(variableDeclaration.Type, @var); + var variableDeclaration = localDeclaration.Declaration; + var numVariables = variableDeclaration.Variables.Count; + + var newVariableDeclarations = new VariableDeclarationSyntax[numVariables]; + + var varSyntax = SyntaxFactory.IdentifierName("var"); + + //Create a new var declaration for each variable. + for (var i = 0; i < numVariables; i++) + { + var originalVariable = variableDeclaration.Variables[i]; + var newLeadingTrivia = originalVariable.GetLeadingTrivia(); + + //Get the trivia from the separator as well + if (i != 0) + { + newLeadingTrivia = newLeadingTrivia.InsertRange(0, + variableDeclaration.Variables.GetSeparator(i-1).GetAllTrivia()); + } + + newVariableDeclarations[i] = SyntaxFactory.VariableDeclaration(varSyntax) + .AddVariables(originalVariable.WithLeadingTrivia(newLeadingTrivia)); + } + + //ensure trivia for leading type node is preserved + var originalTypeSyntax = variableDeclaration.Type; + newVariableDeclarations[0] = newVariableDeclarations[0] + .WithType((TypeSyntax) varSyntax.WithSameTriviaAs(originalTypeSyntax)); + + var newLocalDeclarationStatements = newVariableDeclarations.Select(SyntaxFactory.LocalDeclarationStatement).ToList(); + + + //Preserve the trivia for the entire statement at the start and at the end + newLocalDeclarationStatements[0] = + newLocalDeclarationStatements[0].WithLeadingTrivia(variableDeclaration.GetLeadingTrivia()); + var lastIndex = numVariables -1; + newLocalDeclarationStatements[lastIndex] = + newLocalDeclarationStatements[lastIndex].WithTrailingTrivia(localDeclaration.GetTrailingTrivia()); + + var newRoot = root.ReplaceNode(localDeclaration, newLocalDeclarationStatements); var newDocument = document.WithSyntaxRoot(newRoot); return newDocument; } diff --git a/src/CSharp/CodeCracker/Style/ConsoleWriteLineAnalyzer.cs b/src/CSharp/CodeCracker/Style/ConsoleWriteLineAnalyzer.cs index fb8a85ae1..d9d34cf1c 100644 --- a/src/CSharp/CodeCracker/Style/ConsoleWriteLineAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/ConsoleWriteLineAnalyzer.cs @@ -14,7 +14,7 @@ public class ConsoleWriteLineAnalyzer : DiagnosticAnalyzer internal static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.ConsoleWriteLineAnalyzer_MessageFormat), Resources.ResourceManager, typeof(Resources)); internal static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.ConsoleWriteLineAnalyzer_Description), Resources.ResourceManager, typeof(Resources)); - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.ConsoleWriteLine.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Style/ConsoleWriteLineCodeFixProvider.cs b/src/CSharp/CodeCracker/Style/ConsoleWriteLineCodeFixProvider.cs index e84754466..a77edc2b5 100644 --- a/src/CSharp/CodeCracker/Style/ConsoleWriteLineCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Style/ConsoleWriteLineCodeFixProvider.cs @@ -31,7 +31,7 @@ private async static Task MakeStringInterpolationAsync(Document docume { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var invocationExpression = root.FindToken(diagnostic.Location.SourceSpan.Start).Parent.AncestorsAndSelf().OfType().First(); - var newStringInterpolation = await StringFormatCodeFixProvider.CreateNewStringInterpolationAsync(document, invocationExpression, cancellationToken); + var newStringInterpolation = StringFormatCodeFixProvider.CreateNewStringInterpolation(invocationExpression); var newArgumentList = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList().Add(SyntaxFactory.Argument(newStringInterpolation))); var newRoot = root.ReplaceNode(invocationExpression.ArgumentList, newArgumentList); var newDocument = document.WithSyntaxRoot(newRoot); diff --git a/src/CSharp/CodeCracker/Style/ConvertLambdaExpressionToMethodGroupAnalyzer.cs b/src/CSharp/CodeCracker/Style/ConvertLambdaExpressionToMethodGroupAnalyzer.cs index 433230bff..bb335f76d 100644 --- a/src/CSharp/CodeCracker/Style/ConvertLambdaExpressionToMethodGroupAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/ConvertLambdaExpressionToMethodGroupAnalyzer.cs @@ -15,7 +15,7 @@ public class ConvertLambdaExpressionToMethodGroupAnalyzer : DiagnosticAnalyzer internal const string Category = SupportedCategories.Style; const string Description = "The extra unnecessary layer of indirection induced by the lambda expression may be avoided by passing the method group instead."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.ConvertLambdaExpressionToMethodGroup.ToDiagnosticId(), Title, MessageFormat, @@ -65,7 +65,13 @@ internal static InvocationExpressionSyntax GetInvocationIfAny(SyntaxNode node) : (node as ParenthesizedLambdaExpressionSyntax)?.Body; var invocation = body as InvocationExpressionSyntax; - if (invocation != null) return invocation; + + if (invocation != null) + { + if (invocation.Expression is GenericNameSyntax && (((GenericNameSyntax)invocation.Expression).TypeArgumentList.Arguments.Count > 0)) return null; + + return invocation; + } var possibleBlock = body as BlockSyntax; if (possibleBlock == null || possibleBlock.Statements.Count != 1) return null; diff --git a/src/CSharp/CodeCracker/Style/ConvertToExpressionBodiedMemberAnalyzer.cs b/src/CSharp/CodeCracker/Style/ConvertToExpressionBodiedMemberAnalyzer.cs index 6e76427b5..ede46efcd 100644 --- a/src/CSharp/CodeCracker/Style/ConvertToExpressionBodiedMemberAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/ConvertToExpressionBodiedMemberAnalyzer.cs @@ -14,7 +14,7 @@ public class ConvertToExpressionBodiedMemberAnalyzer : DiagnosticAnalyzer internal const string Category = SupportedCategories.Style; const string Description = "Usage of an expression bodied members improve readability of the code."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.ConvertToExpressionBodiedMember.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Style/ConvertToSwitchAnalyzer.cs b/src/CSharp/CodeCracker/Style/ConvertToSwitchAnalyzer.cs index e5cb11c13..4924daafe 100644 --- a/src/CSharp/CodeCracker/Style/ConvertToSwitchAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/ConvertToSwitchAnalyzer.cs @@ -17,7 +17,7 @@ public class ConvertToSwitchAnalyzer : DiagnosticAnalyzer const string Description = "Multiple 'if' and 'else if' on the same variable can be replaced with a 'switch'" + "on the variable\r\n\r\n" + "Note: This diagnostic trigger for 3 or more 'case' statements"; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.ConvertToSwitch.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Style/EmptyObjectInitializerAnalyzer.cs b/src/CSharp/CodeCracker/Style/EmptyObjectInitializerAnalyzer.cs index df365f84a..1ae23376f 100644 --- a/src/CSharp/CodeCracker/Style/EmptyObjectInitializerAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/EmptyObjectInitializerAnalyzer.cs @@ -12,10 +12,9 @@ public class EmptyObjectInitializerAnalyzer : DiagnosticAnalyzer internal const string Title = "Empty Object Initializer"; internal const string MessageFormat = "{0}"; internal const string Category = SupportedCategories.Style; - const string Description = "An empty object initializer doesn't add any information and only clutter the code.\r\n" - + "If there is no member to initialize, prefer using the standard constructor syntax."; + const string Description = "An object initializer without any arguments can be replaced with the standard constructor syntax."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.EmptyObjectInitializer.ToDiagnosticId(), Title, MessageFormat, @@ -38,9 +37,9 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) if (objectCreation.Initializer != null && !objectCreation.Initializer.Expressions.Any()) { - var diagnostic = Diagnostic.Create(Rule, objectCreation.Initializer.OpenBraceToken.GetLocation(), "Remove empty object initializer."); + var diagnostic = Diagnostic.Create(Rule, objectCreation.Initializer.OpenBraceToken.GetLocation(), "Remove the empty object initializer."); context.ReportDiagnostic(diagnostic); } } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Style/ExistenceOperatorAnalyzer.cs b/src/CSharp/CodeCracker/Style/ExistenceOperatorAnalyzer.cs index cba57bd31..c22b39f18 100644 --- a/src/CSharp/CodeCracker/Style/ExistenceOperatorAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/ExistenceOperatorAnalyzer.cs @@ -13,7 +13,7 @@ public class ExistenceOperatorAnalyzer : DiagnosticAnalyzer internal const string MessageFormat = "{0}"; internal const string Category = SupportedCategories.Style; const string Description = "The null-propagating operator allow for terse code to handle potentially null variables."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.ExistenceOperator.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Style/ForInArrayAnalyzer.cs b/src/CSharp/CodeCracker/Style/ForInArrayAnalyzer.cs index 98c6866de..d76eee1d0 100644 --- a/src/CSharp/CodeCracker/Style/ForInArrayAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/ForInArrayAnalyzer.cs @@ -4,6 +4,8 @@ using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; using System.Linq; +using System; +using System.Collections.Generic; namespace CodeCracker.CSharp.Style { @@ -11,10 +13,10 @@ namespace CodeCracker.CSharp.Style public class ForInArrayAnalyzer : DiagnosticAnalyzer { internal const string Title = "Use foreach"; - internal const string MessageFormat = "{0}"; + internal const string MessageFormat = "You can use foreach instead of for."; internal const string Category = SupportedCategories.Style; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.ForInArray.ToDiagnosticId(), Title, MessageFormat, @@ -45,6 +47,7 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) var semanticModel = context.SemanticModel; var arrayId = semanticModel.GetSymbolInfo(arrayAccessor.Expression).Symbol; if (arrayId == null) return; + if (!IsEnumerable(arrayId)) return; var forVariable = forStatement.Declaration.Variables.First(); var literalExpression = forVariable.Initializer.Value as LiteralExpressionSyntax; if (literalExpression == null || !literalExpression.IsKind(SyntaxKind.NumericLiteralExpression)) return; @@ -55,6 +58,8 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) if (t.Text != forVariable.Identifier.Text) return false; var elementAccess = t.GetAncestor(); if (elementAccess == null) return true; + var assignment = elementAccess.Parent as AssignmentExpressionSyntax; + if (assignment != null && assignment.Left == elementAccess) return true; var accessIdentifier = elementAccess.Expression as IdentifierNameSyntax; if (accessIdentifier == null) return true; var identifierSymbol = semanticModel.GetSymbolInfo(accessIdentifier).Symbol; @@ -62,20 +67,69 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) return !identifierSymbol.Equals(arrayId); }); if (otherUsesOfIndexToken != 0) return; - - var arrayAccessorSymbols = (from s in forBlock.Statements.OfType() - where s.Declaration.Variables.Count == 1 - let declaration = s.Declaration.Variables.First() - where declaration?.Initializer?.Value is ElementAccessExpressionSyntax - let init = (ElementAccessExpressionSyntax)declaration.Initializer.Value - let initSymbol = semanticModel.GetSymbolInfo(init.ArgumentList.Arguments.First().Expression).Symbol - where controlVarId.Equals(initSymbol) - let someArrayInit = semanticModel.GetSymbolInfo(init.Expression).Symbol - where arrayId.Equals(someArrayInit) - select arrayId).ToList(); - if (!arrayAccessorSymbols.Any()) return; - var diagnostic = Diagnostic.Create(Rule, forStatement.ForKeyword.GetLocation(), "You can use foreach instead of for."); + var iterableSymbols = (from s in forBlock.Statements.OfType() + where s.Declaration.Variables.Count == 1 + let declaration = s.Declaration.Variables.First() + where declaration?.Initializer?.Value is ElementAccessExpressionSyntax + let iterableSymbol = (ILocalSymbol)semanticModel.GetDeclaredSymbol(declaration) + let iterableType = iterableSymbol.Type + where !(iterableType.IsPrimitive() ^ iterableType.IsValueType) + let init = (ElementAccessExpressionSyntax)declaration.Initializer.Value + let initSymbol = semanticModel.GetSymbolInfo(init.ArgumentList.Arguments.First().Expression).Symbol + where controlVarId.Equals(initSymbol) + let someArrayInit = semanticModel.GetSymbolInfo(init.Expression).Symbol + where arrayId.Equals(someArrayInit) + select iterableSymbol).ToList(); + if (!iterableSymbols.Any()) return; + if (IsIterationVariableWritten(semanticModel, forBlock, iterableSymbols)) return; + var diagnostic = Diagnostic.Create(Rule, forStatement.ForKeyword.GetLocation()); context.ReportDiagnostic(diagnostic); } + + private static bool IsIterationVariableWritten(SemanticModel semanticModel, BlockSyntax forBlock, List iterableSymbols) + { + var forDescendants = forBlock.DescendantNodes(); + var assignments = (from assignmentExpression in forDescendants.OfType() + let assignmentLeftSymbol = semanticModel.GetSymbolInfo(assignmentExpression.Left).Symbol + where iterableSymbols.Any(i => i.Equals(assignmentLeftSymbol)) + select assignmentExpression).ToList(); + if (assignments.Any()) return true; + var refs = (from argument in forDescendants.OfType() + where argument.RefOrOutKeyword != null + let argumentExpressionSymbol = semanticModel.GetSymbolInfo(argument.Expression).Symbol + where iterableSymbols.Any(i => i.Equals(argumentExpressionSymbol)) + select argument).ToList(); + if (refs.Any()) return true; + var postfixUnaries = (from postfixUnaryExpression in forDescendants.OfType() + let operandSymbol = semanticModel.GetSymbolInfo(postfixUnaryExpression.Operand).Symbol + where iterableSymbols.Any(i => i.Equals(operandSymbol)) + select postfixUnaryExpression).ToList(); + if (postfixUnaries.Any()) return true; + var prefixUnaries = (from postfixUnaryExpression in forDescendants.OfType() + let operandSymbol = semanticModel.GetSymbolInfo(postfixUnaryExpression.Operand).Symbol + where iterableSymbols.Any(i => i.Equals(operandSymbol)) + select postfixUnaryExpression).ToList(); + if (prefixUnaries.Any()) return true; + return false; + } + + private static bool IsEnumerable(ISymbol arrayId) + { + var type = (arrayId as ILocalSymbol)?.Type + ?? (arrayId as IParameterSymbol)?.Type + ?? (arrayId as IPropertySymbol)?.Type + ?? (arrayId as IFieldSymbol)?.Type; + if (type == null) return false; + if (type.AllInterfaces.Any(i => i.ToString() == "System.Collections.IEnumerable")) return true; + var allReturnTypes = type.GetMembers("GetEnumerator") + .Select(m => m as IMethodSymbol) + .Where(m => m != null) + .Select(m => m.ReturnType).ToList(); + if (allReturnTypes.Any(t => t.ToString() == "System.Collections.IEnumerator")) return true; + var hasGetEnumerator = allReturnTypes.SelectMany(t => t.AllInterfaces) + .Distinct() + .Any(i => i.ToString() == "System.Collections.IEnumerator"); + return hasGetEnumerator; + } } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Style/InterfaceNameAnalyzer.cs b/src/CSharp/CodeCracker/Style/InterfaceNameAnalyzer.cs index a4270b9a1..4cce637cd 100644 --- a/src/CSharp/CodeCracker/Style/InterfaceNameAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/InterfaceNameAnalyzer.cs @@ -14,7 +14,7 @@ public class InterfaceNameAnalyzer : DiagnosticAnalyzer internal const string Category = SupportedCategories.Style; const string Description = "Consider naming interfaces starting with 'I'."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.InterfaceName.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Style/ObjectInitializerAnalyzer.cs b/src/CSharp/CodeCracker/Style/ObjectInitializerAnalyzer.cs index 7dac5fc99..4e67acf8b 100644 --- a/src/CSharp/CodeCracker/Style/ObjectInitializerAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/ObjectInitializerAnalyzer.cs @@ -18,7 +18,7 @@ public class ObjectInitializerAnalyzer : DiagnosticAnalyzer const string Description = "When possible an object initializer should be used to initialize the properties of an " + "object instead of multiple assignments."; - internal static DiagnosticDescriptor RuleAssignment = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor RuleAssignment = new DiagnosticDescriptor( DiagnosticId.ObjectInitializer_Assignment.ToDiagnosticId(), TitleLocalDeclaration, MessageFormat, @@ -28,7 +28,7 @@ public class ObjectInitializerAnalyzer : DiagnosticAnalyzer description: Description, helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.ObjectInitializer_Assignment)); - internal static DiagnosticDescriptor RuleLocalDeclaration = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor RuleLocalDeclaration = new DiagnosticDescriptor( DiagnosticId.ObjectInitializer_LocalDeclaration.ToDiagnosticId(), TitleLocalDeclaration, MessageFormat, @@ -55,9 +55,11 @@ private static void AnalyzeAssignment(SyntaxNodeAnalysisContext context) if (expressionStatement?.Expression?.IsNotKind(SyntaxKind.SimpleAssignmentExpression) ?? true) return; var assignmentExpression = (AssignmentExpressionSyntax)expressionStatement.Expression; if (assignmentExpression.Right.IsNotKind(SyntaxKind.ObjectCreationExpression)) return; + if (((ObjectCreationExpressionSyntax)assignmentExpression.Right).Initializer?.IsKind(SyntaxKind.CollectionInitializerExpression) ?? false) return; var variableSymbol = semanticModel.GetSymbolInfo(assignmentExpression.Left).Symbol; - var assignmentExpressions = FindAssignmentExpressions(semanticModel, expressionStatement, variableSymbol); - if (!assignmentExpressions.Any()) return; + var assignmentExpressionStatements = FindAssignmentExpressions(semanticModel, expressionStatement, variableSymbol); + if (!assignmentExpressionStatements.Any()) return; + if (HasAssignmentUsingDeclaredVariable(semanticModel, variableSymbol, assignmentExpressionStatements)) return; var diagnostic = Diagnostic.Create(RuleAssignment, expressionStatement.GetLocation(), "You can use initializers in here."); context.ReportDiagnostic(diagnostic); } @@ -70,8 +72,11 @@ private static void AnalyzeLocalDeclaration(SyntaxNodeAnalysisContext context) if (localDeclarationStatement == null) return; if (localDeclarationStatement.Declaration?.Variables.Count != 1) return; var variable = localDeclarationStatement.Declaration.Variables.Single(); - if ((variable.Initializer as EqualsValueClauseSyntax)?.Value.IsNotKind(SyntaxKind.ObjectCreationExpression) ?? true) return; + var equalsValueClauseSyntax = variable.Initializer as EqualsValueClauseSyntax; + if (equalsValueClauseSyntax?.Value.IsNotKind(SyntaxKind.ObjectCreationExpression) ?? true) return; + if (((ObjectCreationExpressionSyntax)equalsValueClauseSyntax.Value).Initializer?.IsKind(SyntaxKind.CollectionInitializerExpression) ?? false) return; var variableSymbol = semanticModel.GetDeclaredSymbol(variable); + if (((ILocalSymbol)variableSymbol).Type.TypeKind == TypeKind.Dynamic) return; var assignmentExpressionStatements = FindAssignmentExpressions(semanticModel, localDeclarationStatement, variableSymbol); if (!assignmentExpressionStatements.Any()) return; if (HasAssignmentUsingDeclaredVariable(semanticModel, variableSymbol, assignmentExpressionStatements)) return; diff --git a/src/CSharp/CodeCracker/Style/ObjectInitializerCodeFixProvider.cs b/src/CSharp/CodeCracker/Style/ObjectInitializerCodeFixProvider.cs index ae5a9ddfb..258d8975a 100644 --- a/src/CSharp/CodeCracker/Style/ObjectInitializerCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Style/ObjectInitializerCodeFixProvider.cs @@ -58,7 +58,7 @@ private static Document MakeObjectInitializer(Document document, SyntaxNode root private static Document MakeObjectInitializer(Document document, SyntaxNode root, StatementSyntax statement, ISymbol variableSymbol, SemanticModel semanticModel) { var blockParent = statement.FirstAncestorOrSelf(); - var objectCreationExpression = statement.DescendantNodes().OfType().Single(); + var objectCreationExpression = statement.DescendantNodes().OfType().First(); var newBlockParent = CreateNewBlockParent(statement, semanticModel, objectCreationExpression, variableSymbol); var newRoot = root.ReplaceNode(blockParent, newBlockParent); var newDocument = document.WithSyntaxRoot(newRoot); @@ -80,14 +80,13 @@ private static BlockSyntax CreateNewBlockParent(StatementSyntax statement, Seman if (blockStatement.Equals(statement)) { var initializationExpressions = new List(); - foreach (var expressionStatement in assignmentExpressions) { var assignmentExpression = expressionStatement.Expression as AssignmentExpressionSyntax; var memberAccess = assignmentExpression.Left as MemberAccessExpressionSyntax; var propertyIdentifier = memberAccess.Name as IdentifierNameSyntax; var newAssignmentExpression = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, propertyIdentifier, assignmentExpression.Right); - initializationExpressions.Add(newAssignmentExpression); + initializationExpressions.Add(newAssignmentExpression.WithTriviaFrom(expressionStatement)); } if (objectCreationExpression.Initializer != null) @@ -103,7 +102,15 @@ private static BlockSyntax CreateNewBlockParent(StatementSyntax statement, Seman initializationExpressions.InsertRange(0, existentInitilizers); } - var initializers = SyntaxFactory.SeparatedList(initializationExpressions); + // Trailing trivia will be added before the separator if a simple separator list is used. This builds the separator token for expression + // such that the trailing trivia from the original expression is added after the comma on the same line. + var initializerSeparators = initializationExpressions.Take(initializationExpressions.Count - 1).Select(expr => SyntaxFactory.Token(SyntaxKind.CommaToken).WithTrailingTrivia(expr.GetTrailingTrivia())).ToList(); + var lastInitializer = initializationExpressions.Last(); // Preserve the last initializer before rebuilding the list. + // Get all but the last initializer without the trailing trivia. Trivia will be added after the separator from the list above. + initializationExpressions = initializationExpressions.Take(initializationExpressions.Count - 1).Select(expr => expr.WithoutTrailingTrivia()).ToList(); + initializationExpressions.Add(lastInitializer); // Add the last initializer with all of its trivia. + + var initializers = SyntaxFactory.SeparatedList(initializationExpressions, initializerSeparators); var newObjectCreationExpression = objectCreationExpression.WithInitializer( SyntaxFactory.InitializerExpression( @@ -124,7 +131,7 @@ private static BlockSyntax CreateNewBlockParent(StatementSyntax statement, Seman .WithTrailingTrivia(statement.GetTrailingTrivia()) .WithAdditionalAnnotations(Formatter.Annotation); newBlockParent = newBlockParent.AddStatements(newLocalDeclarationStatement); - i += initializationExpressions.Count; + i += assignmentExpressions.Count; } else { diff --git a/src/CSharp/CodeCracker/Style/PropertyPrivateSetAnalyzer.cs b/src/CSharp/CodeCracker/Style/PropertyPrivateSetAnalyzer.cs index 0abbdeb82..093a20c9d 100644 --- a/src/CSharp/CodeCracker/Style/PropertyPrivateSetAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/PropertyPrivateSetAnalyzer.cs @@ -10,11 +10,11 @@ namespace CodeCracker.CSharp.Style public class PropertyPrivateSetAnalyzer : DiagnosticAnalyzer { internal const string Title = "You should change to 'private set' whenever possible."; - internal const string MessageFormat = "Consider use a 'private set'."; + internal const string MessageFormat = "Consider using a 'private set'."; internal const string Category = SupportedCategories.Style; const string Description = "Use private set for automatic properties."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.PropertyPrivateSet.ToDiagnosticId(), Title, MessageFormat, @@ -39,6 +39,7 @@ private static void AnalyzeClass(SyntaxNodeAnalysisContext context) var setAcessor = (invocationExpression.AccessorList.Accessors[0].Keyword.Text == "set") ? invocationExpression.AccessorList.Accessors[0] : invocationExpression.AccessorList.Accessors[1]; if (setAcessor.Modifiers.Count != 0) return; + if (invocationExpression.Modifiers.Any(SyntaxKind.PrivateKeyword)) return; var error = string.Format(MessageFormat, MessageFormat); diff --git a/src/CSharp/CodeCracker/Style/RemoveAsyncFromMethodAnalyzer.cs b/src/CSharp/CodeCracker/Style/RemoveAsyncFromMethodAnalyzer.cs index b89088111..8d68344e1 100644 --- a/src/CSharp/CodeCracker/Style/RemoveAsyncFromMethodAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/RemoveAsyncFromMethodAnalyzer.cs @@ -15,7 +15,7 @@ public class RemoveAsyncFromMethodAnalyzer : DiagnosticAnalyzer internal const string Category = SupportedCategories.Style; const string Description = "Remove Async termination when method is not asynchronous."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.RemoveAsyncFromMethod.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Style/RemoveCommentedCodeAnalyzer.cs b/src/CSharp/CodeCracker/Style/RemoveCommentedCodeAnalyzer.cs index 765dc1e10..a25c49944 100644 --- a/src/CSharp/CodeCracker/Style/RemoveCommentedCodeAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/RemoveCommentedCodeAnalyzer.cs @@ -14,7 +14,7 @@ public class RemoveCommentedCodeAnalyzer : DiagnosticAnalyzer internal const string Title = "Remove commented code."; internal const string MessageFormat = "Commented code should be removed."; internal const string Category = SupportedCategories.Style; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.RemoveCommentedCode.ToDiagnosticId(), Title, MessageFormat, @@ -52,7 +52,7 @@ private void AnalyzeSingleLineCommentTrivia(SyntaxTreeAnalysisContext context) } } - readonly CSharpParseOptions options = new CSharpParseOptions(documentationMode: DocumentationMode.None);//todo:bring kind: SourceCodeKind.Interactive back, it is not supported at the current release + readonly CSharpParseOptions options = new CSharpParseOptions(documentationMode: DocumentationMode.None, kind: SourceCodeKind.Script); bool CouldBeSourceCode(string source) { source = source.Trim(); diff --git a/src/CSharp/CodeCracker/Style/RemoveTrailingWhitespaceAnalyzer.cs b/src/CSharp/CodeCracker/Style/RemoveTrailingWhitespaceAnalyzer.cs index 37aabc147..e34368cda 100644 --- a/src/CSharp/CodeCracker/Style/RemoveTrailingWhitespaceAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/RemoveTrailingWhitespaceAnalyzer.cs @@ -16,7 +16,7 @@ public class RemoveTrailingWhitespaceAnalyzer : DiagnosticAnalyzer internal const string Category = SupportedCategories.Style; const string Description = "Trailing whitespaces are ugly and show sloppiness. Remove them."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.RemoveTrailingWhitespace.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Style/RemoveTrailingWhitespaceCodeFixProvider.cs b/src/CSharp/CodeCracker/Style/RemoveTrailingWhitespaceCodeFixProvider.cs index 0190ed41b..b4a94ff25 100644 --- a/src/CSharp/CodeCracker/Style/RemoveTrailingWhitespaceCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Style/RemoveTrailingWhitespaceCodeFixProvider.cs @@ -35,7 +35,7 @@ private static async Task RemoveTrailingWhiteSpaceAsync(Document docum { newRoot = root.ReplaceTrivia(trivia, new SyntaxTrivia[] { }); } - else if (trivia.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia, SyntaxKind.MultiLineDocumentationCommentTrivia)) + else if (trivia.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia, SyntaxKind.MultiLineDocumentationCommentTrivia, SyntaxKind.PragmaWarningDirectiveTrivia)) { var commentText = trivia.ToFullString(); var commentLines = commentText.Split(new[] { Environment.NewLine }, StringSplitOptions.None); diff --git a/src/CSharp/CodeCracker/Style/StringFormatAnalyzer.cs b/src/CSharp/CodeCracker/Style/StringFormatAnalyzer.cs index f2651e57a..55a9a819a 100644 --- a/src/CSharp/CodeCracker/Style/StringFormatAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/StringFormatAnalyzer.cs @@ -17,7 +17,7 @@ public class StringFormatAnalyzer : DiagnosticAnalyzer internal static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.StringFormatAnalyzer_MessageFormat), Resources.ResourceManager, typeof(Resources)); internal static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.StringFormatAnalyzer_Description), Resources.ResourceManager, typeof(Resources)); - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.StringFormat.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Style/StringFormatCodeFixProvider.cs b/src/CSharp/CodeCracker/Style/StringFormatCodeFixProvider.cs index fc3feda98..9772b3a3a 100644 --- a/src/CSharp/CodeCracker/Style/StringFormatCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Style/StringFormatCodeFixProvider.cs @@ -19,7 +19,7 @@ public class StringFormatCodeFixProvider : CodeFixProvider public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.StringFormat.ToDiagnosticId()); - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + public sealed override FixAllProvider GetFixAllProvider() => StringFormatFixAllProvider.Instance; public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) { @@ -32,16 +32,21 @@ private async static Task MakeStringInterpolationAsync(Document docume { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var invocationExpression = root.FindToken(diagnostic.Location.SourceSpan.Start).Parent.AncestorsAndSelf().OfType().First(); - var newStringInterpolation = await CreateNewStringInterpolationAsync(document, invocationExpression, cancellationToken); - var newRoot = root.ReplaceNode(invocationExpression, newStringInterpolation); + var semanticModel = await document.GetSemanticModelAsync(cancellationToken); + var newRoot = CreateNewStringInterpolation(root, invocationExpression); var newDocument = document.WithSyntaxRoot(newRoot); return newDocument; } - public static async Task CreateNewStringInterpolationAsync(Document document, InvocationExpressionSyntax invocationExpression, CancellationToken cancellationToken) + public static SyntaxNode CreateNewStringInterpolation(SyntaxNode root, InvocationExpressionSyntax invocationExpression) + { + var newStringInterpolation = CreateNewStringInterpolation(invocationExpression); + var newRoot = root.ReplaceNode(invocationExpression, newStringInterpolation); + return newRoot; + } + + public static InterpolatedStringExpressionSyntax CreateNewStringInterpolation(InvocationExpressionSyntax invocationExpression) { - var semanticModel = await document.GetSemanticModelAsync(cancellationToken); - var memberSymbol = semanticModel.GetSymbolInfo(invocationExpression.Expression).Symbol; var argumentList = invocationExpression.ArgumentList; var arguments = argumentList.Arguments; var formatLiteral = (LiteralExpressionSyntax)arguments[0].Expression; @@ -52,12 +57,13 @@ public static async Task CreateNewStringInte { var index = (int)((LiteralExpressionSyntax)interpolation.Expression).Token.Value; var expression = interpolationArgs[index].Expression; + expression = expression.WithoutAllTrivia(); var conditional = expression as ConditionalExpressionSyntax; if (conditional != null) expression = SyntaxFactory.ParenthesizedExpression(expression); expressionsToReplace.Add(interpolation.Expression, expression); } - var newStringInterpolation = analyzingInterpolation.ReplaceNodes(expressionsToReplace.Keys, (o, _) => expressionsToReplace[o].WithoutTrivia()); + var newStringInterpolation = analyzingInterpolation.ReplaceNodes(expressionsToReplace.Keys, (o, _) => expressionsToReplace[o]); return newStringInterpolation; } } -} +} \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Refactoring/IntroduceFieldFromConstructorCodeFixProviderAll.cs b/src/CSharp/CodeCracker/Style/StringFormatFixAllProvider.cs similarity index 58% rename from src/CSharp/CodeCracker/Refactoring/IntroduceFieldFromConstructorCodeFixProviderAll.cs rename to src/CSharp/CodeCracker/Style/StringFormatFixAllProvider.cs index 34abd1e24..055b0e517 100644 --- a/src/CSharp/CodeCracker/Refactoring/IntroduceFieldFromConstructorCodeFixProviderAll.cs +++ b/src/CSharp/CodeCracker/Style/StringFormatFixAllProvider.cs @@ -1,3 +1,4 @@ +using CodeCracker.Properties; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; @@ -5,29 +6,29 @@ using System.Linq; using System.Threading.Tasks; -namespace CodeCracker.CSharp.Refactoring +namespace CodeCracker.CSharp.Style { - public sealed class IntroduceFieldFromConstructorCodeFixAllProvider : FixAllProvider + public sealed class StringFormatFixAllProvider : FixAllProvider { - private static readonly SyntaxAnnotation introduceFieldAnnotation = new SyntaxAnnotation(nameof(IntroduceFieldFromConstructorCodeFixAllProvider)); - private IntroduceFieldFromConstructorCodeFixAllProvider() { } - public static IntroduceFieldFromConstructorCodeFixAllProvider Instance = new IntroduceFieldFromConstructorCodeFixAllProvider(); - + private static readonly SyntaxAnnotation stringFormatAnnotation = new SyntaxAnnotation(nameof(StringFormatFixAllProvider)); + private StringFormatFixAllProvider() { } + public static readonly StringFormatFixAllProvider Instance = new StringFormatFixAllProvider(); public override Task GetFixAsync(FixAllContext fixAllContext) { switch (fixAllContext.Scope) { case FixAllScope.Document: - return Task.FromResult(CodeAction.Create(IntroduceFieldFromConstructorCodeFixProvider.MessageFormat, - async ct => fixAllContext.Document.WithSyntaxRoot(await GetFixedDocumentAsync(fixAllContext, fixAllContext.Document)))); + return Task.FromResult(CodeAction.Create(Resources.StringFormatCodeFixProvider_Title, + async ct => fixAllContext.Document.WithSyntaxRoot(await GetFixedDocumentAsync(fixAllContext, fixAllContext.Document).ConfigureAwait(false)))); case FixAllScope.Project: - return Task.FromResult(CodeAction.Create(IntroduceFieldFromConstructorCodeFixProvider.MessageFormat, + return Task.FromResult(CodeAction.Create(Resources.StringFormatCodeFixProvider_Title, ct => GetFixedProjectAsync(fixAllContext, fixAllContext.Project))); case FixAllScope.Solution: - return Task.FromResult(CodeAction.Create(IntroduceFieldFromConstructorCodeFixProvider.MessageFormat, + return Task.FromResult(CodeAction.Create(Resources.StringFormatCodeFixProvider_Title, ct => GetFixedSolutionAsync(fixAllContext))); + default: + return null; } - return null; } private async static Task GetFixedSolutionAsync(FixAllContext fixAllContext) @@ -52,22 +53,16 @@ private async static Task GetFixedDocumentAsync(FixAllContext fixAll { var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false); var root = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); - var nodes = diagnostics.Select(d => root.FindNode(d.Location.SourceSpan)).Where(n => !n.IsMissing); - var newRoot = root.ReplaceNodes(nodes, (original, rewritten) => original.WithAdditionalAnnotations(introduceFieldAnnotation)); - var semanticModel = await document.GetSemanticModelAsync(fixAllContext.CancellationToken).ConfigureAwait(false); + var nodes = diagnostics.Select(d => root.FindNode(d.Location.SourceSpan, getInnermostNodeForTie: true).FirstAncestorOrSelfOfType()).Where(n => !n.IsMissing).ToList(); + var newRoot = root.ReplaceNodes(nodes, (original, rewritten) => rewritten.WithAdditionalAnnotations(stringFormatAnnotation)); while (true) { - var annotatedNodes = newRoot.GetAnnotatedNodes(introduceFieldAnnotation); + var annotatedNodes = newRoot.GetAnnotatedNodes(stringFormatAnnotation); var node = annotatedNodes.FirstOrDefault(); if (node == null) break; - - var constructorMethod = (ConstructorDeclarationSyntax)node.Parent.Parent; - var parameter = (ParameterSyntax)node; - newRoot = IntroduceFieldFromConstructorCodeFixProvider.IntroduceFieldFromConstructor(newRoot, constructorMethod, parameter); - node = newRoot.GetAnnotatedNodes(introduceFieldAnnotation).First(); - newRoot = newRoot.ReplaceNode(node, node.WithoutAnnotations(introduceFieldAnnotation)); + newRoot = StringFormatCodeFixProvider.CreateNewStringInterpolation(newRoot, (InvocationExpressionSyntax)node); } return newRoot; } } -} +} \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Style/SwitchToAutoPropAnalyzer.cs b/src/CSharp/CodeCracker/Style/SwitchToAutoPropAnalyzer.cs index 8cb745d9f..a51c2dff6 100644 --- a/src/CSharp/CodeCracker/Style/SwitchToAutoPropAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/SwitchToAutoPropAnalyzer.cs @@ -17,7 +17,7 @@ public class SwitchToAutoPropAnalyzer : DiagnosticAnalyzer internal static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.SwitchToAutoPropAnalyzer_Description), Resources.ResourceManager, typeof(Resources)); internal const string Category = SupportedCategories.Style; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.SwitchToAutoProp.ToDiagnosticId(), Title, MessageFormat, @@ -45,7 +45,6 @@ private static void AnalyzeProperty(SyntaxNodeAnalysisContext context, bool canH if (property.AccessorList?.Accessors.Count != 2) return; if (property.AccessorList.Accessors.Any(a => a.Body == null)) return; if (property.AccessorList.Accessors.Any(a => a.Body.Statements.Count != 1)) return; - if (property.AccessorList.Accessors.Any(a => a.Body.Statements.Count != 1)) return; var getter = property.AccessorList.Accessors.First(a => a.Keyword.ValueText == "get"); var getterReturn = getter.Body.Statements.First() as ReturnStatementSyntax; if (getterReturn == null) return; @@ -53,15 +52,17 @@ private static void AnalyzeProperty(SyntaxNodeAnalysisContext context, bool canH var setterExpressionStatement = setter.Body.Statements.First() as ExpressionStatementSyntax; var setterAssignmentExpression = setterExpressionStatement?.Expression as AssignmentExpressionSyntax; if (setterAssignmentExpression == null) return; - var returnIdentifier = getterReturn.Expression as IdentifierNameSyntax; + var returnIdentifier = (getterReturn.Expression is MemberAccessExpressionSyntax && + ((MemberAccessExpressionSyntax)getterReturn.Expression).Expression is ThisExpressionSyntax ? ((MemberAccessExpressionSyntax)getterReturn.Expression).Name : getterReturn.Expression) as IdentifierNameSyntax; if (returnIdentifier == null) return; var semanticModel = context.SemanticModel; - var returnIdentifierSymbol = semanticModel.GetSymbolInfo(returnIdentifier).Symbol; - if (returnIdentifierSymbol == null) return; - var assignmentLeftIdentifier = setterAssignmentExpression.Left as IdentifierNameSyntax; + var fieldSymbol = semanticModel.GetSymbolInfo(returnIdentifier).Symbol; + if (fieldSymbol == null) return; + var assignmentLeftIdentifier = (setterAssignmentExpression.Left is MemberAccessExpressionSyntax && + ((MemberAccessExpressionSyntax)setterAssignmentExpression.Left).Expression is ThisExpressionSyntax ? ((MemberAccessExpressionSyntax)setterAssignmentExpression.Left).Name : setterAssignmentExpression.Left) as IdentifierNameSyntax; if (assignmentLeftIdentifier == null) return; var assignmentLeftIdentifierSymbol = semanticModel.GetSymbolInfo(assignmentLeftIdentifier).Symbol; - if (!assignmentLeftIdentifierSymbol.Equals(returnIdentifierSymbol)) return; + if (!assignmentLeftIdentifierSymbol.Equals(fieldSymbol)) return; var assignmentRightIdentifier = setterAssignmentExpression.Right as IdentifierNameSyntax; if (assignmentRightIdentifier == null) return; if (assignmentRightIdentifier.Identifier.Text != "value") return; diff --git a/src/CSharp/CodeCracker/Style/SwitchToAutoPropCodeFixAllProvider.cs b/src/CSharp/CodeCracker/Style/SwitchToAutoPropCodeFixAllProvider.cs new file mode 100644 index 000000000..8fa876860 --- /dev/null +++ b/src/CSharp/CodeCracker/Style/SwitchToAutoPropCodeFixAllProvider.cs @@ -0,0 +1,131 @@ +using CodeCracker.Properties; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CodeCracker.CSharp.Style +{ + public sealed class SwitchToAutoPropCodeFixAllProvider : FixAllProvider + { + private SwitchToAutoPropCodeFixAllProvider() { } + + public static readonly SwitchToAutoPropCodeFixAllProvider Instance = new SwitchToAutoPropCodeFixAllProvider(); + public override Task GetFixAsync(FixAllContext fixAllContext) + { + switch (fixAllContext.Scope) + { + case FixAllScope.Document: + return Task.FromResult(CodeAction.Create(Resources.SwitchToAutoPropCodeFixProvider_Title, + async ct => await GetFixedSolutionAsync(fixAllContext, await GetSolutionWithDocsAsync(fixAllContext, fixAllContext.Document)))); + case FixAllScope.Project: + return Task.FromResult(CodeAction.Create(Resources.SwitchToAutoPropCodeFixProvider_Title, + async ct => await GetFixedSolutionAsync(fixAllContext, await GetSolutionWithDocsAsync(fixAllContext, fixAllContext.Project)))); + case FixAllScope.Solution: + return Task.FromResult(CodeAction.Create(Resources.SwitchToAutoPropCodeFixProvider_Title, + async ct => await GetFixedSolutionAsync(fixAllContext, await GetSolutionWithDocsAsync(fixAllContext, fixAllContext.Solution)))); + default: + return null; + } + } + + private async static Task GetSolutionWithDocsAsync(FixAllContext fixAllContext, Solution solution) + { + var docs = new List(); + var sol = new SolutionWithDocs { Docs = docs, Solution = solution }; + foreach (var pId in solution.Projects.Select(p => p.Id)) + { + var project = sol.Solution.GetProject(pId); + var newSol = await GetSolutionWithDocsAsync(fixAllContext, project).ConfigureAwait(false); + sol.Merge(newSol); + } + return sol; + } + + private async static Task GetSolutionWithDocsAsync(FixAllContext fixAllContext, Project project) + { + var docs = new List(); + var newSolution = project.Solution; + foreach (var document in project.Documents) + { + var doc = await GetDiagnosticsInDocAsync(fixAllContext, document); + if (doc.Equals(DiagnosticsInDoc.Empty)) continue; + docs.Add(doc); + newSolution = newSolution.WithDocumentSyntaxRoot(document.Id, doc.TrackedRoot); + } + var sol = new SolutionWithDocs { Docs = docs, Solution = newSolution }; + return sol; + } + + private async static Task GetSolutionWithDocsAsync(FixAllContext fixAllContext, Document document) + { + var docs = new List(); + var doc = await GetDiagnosticsInDocAsync(fixAllContext, document); + docs.Add(doc); + var newSolution = document.Project.Solution.WithDocumentSyntaxRoot(document.Id, doc.TrackedRoot); + var sol = new SolutionWithDocs { Docs = docs, Solution = newSolution }; + return sol; + } + + private static async Task GetDiagnosticsInDocAsync(FixAllContext fixAllContext, Document document) + { + var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false); + if (!diagnostics.Any()) return DiagnosticsInDoc.Empty; + var root = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); + var doc = DiagnosticsInDoc.Create(document.Id, diagnostics, root); + return doc; + } + + private async static Task GetFixedSolutionAsync(FixAllContext fixAllContext, SolutionWithDocs sol) + { + var newSolution = sol.Solution; + foreach (var doc in sol.Docs) + { + foreach (var node in doc.Nodes) + { + var document = newSolution.GetDocument(doc.DocumentId); + var root = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); + var trackedNode = root.GetCurrentNode(node); + var property = trackedNode.AncestorsAndSelf().OfType().First(); + newSolution = await SwitchToAutoPropCodeFixProvider.MakeAutoPropertyAsync(document, root, property, fixAllContext.CancellationToken); + } + } + return newSolution; + } + + private struct DiagnosticsInDoc + { + public static DiagnosticsInDoc Create(DocumentId documentId, IList diagnostics, SyntaxNode root) + { + var nodes = diagnostics.Select(d => root.FindNode(d.Location.SourceSpan)).Where(n => !n.IsMissing).ToList(); + var diagnosticsInDoc = new DiagnosticsInDoc + { + DocumentId = documentId, + TrackedRoot = root.TrackNodes(nodes), + Nodes = nodes + }; + return diagnosticsInDoc; + } + public DocumentId DocumentId; + public List Nodes; + public SyntaxNode TrackedRoot; + + private static readonly DiagnosticsInDoc empty = new DiagnosticsInDoc(); + public static DiagnosticsInDoc Empty => empty; + } + + private struct SolutionWithDocs + { + public Solution Solution; + public List Docs; + public void Merge(SolutionWithDocs sol) + { + Solution = sol.Solution; + Docs.AddRange(sol.Docs); + } + } + } +} diff --git a/src/CSharp/CodeCracker/Style/SwitchToAutoPropCodeFixProvider.cs b/src/CSharp/CodeCracker/Style/SwitchToAutoPropCodeFixProvider.cs index c480982e3..b5a2bdfd6 100644 --- a/src/CSharp/CodeCracker/Style/SwitchToAutoPropCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Style/SwitchToAutoPropCodeFixProvider.cs @@ -4,8 +4,10 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Rename; +using Microsoft.CodeAnalysis.Simplification; using System.Collections.Immutable; using System.Composition; using System.Linq; @@ -20,7 +22,7 @@ public class SwitchToAutoPropCodeFixProvider : CodeFixProvider public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.SwitchToAutoProp.ToDiagnosticId()); - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + public sealed override FixAllProvider GetFixAllProvider() => SwitchToAutoPropCodeFixAllProvider.Instance; public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) { @@ -33,62 +35,181 @@ private async static Task MakeAutoPropertyAsync(Document document, Dia { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var property = root.FindToken(diagnostic.Location.SourceSpan.Start).Parent.AncestorsAndSelf().OfType().First(); - var semanticModel = await document.GetSemanticModelAsync(cancellationToken); - + return await MakeAutoPropertyAsync(document, root, property, cancellationToken); + } + public async static Task MakeAutoPropertyAsync(Document document, SyntaxNode root, PropertyDeclarationSyntax property, CancellationToken cancellationToken) + { + var semanticModel = await document.GetSemanticModelAsync(cancellationToken); var getterReturn = (ReturnStatementSyntax)property.AccessorList.Accessors.First(a => a.Keyword.ValueText == "get").Body.Statements.First(); - var returnIdentifier = (IdentifierNameSyntax)getterReturn.Expression; - var returnIdentifierSymbol = semanticModel.GetSymbolInfo(returnIdentifier).Symbol; - - - var variableDeclarator = (VariableDeclaratorSyntax)returnIdentifierSymbol.DeclaringSyntaxReferences.First().GetSyntax(); + var returnIdentifier = (IdentifierNameSyntax)(getterReturn.Expression is MemberAccessExpressionSyntax + ? ((MemberAccessExpressionSyntax)getterReturn.Expression).Name + : getterReturn.Expression); + var fieldSymbol = (IFieldSymbol)semanticModel.GetSymbolInfo(returnIdentifier).Symbol; + var variableDeclarator = (VariableDeclaratorSyntax)fieldSymbol.DeclaringSyntaxReferences.First().GetSyntax(); var fieldDeclaration = variableDeclarator.FirstAncestorOfType(); - - - root = root.TrackNodes(returnIdentifier, fieldDeclaration, property); - document = document.WithSyntaxRoot(root); - root = await document.GetSyntaxRootAsync(cancellationToken); - semanticModel = await document.GetSemanticModelAsync(cancellationToken); - returnIdentifier = root.GetCurrentNode(returnIdentifier); - returnIdentifierSymbol = semanticModel.GetSymbolInfo(returnIdentifier).Symbol; - - var newProperty = GetSimpleProperty(property, variableDeclarator) - .WithTrailingTrivia(property.AccessorList.GetTrailingTrivia()) - .WithLeadingTrivia(property.AccessorList.GetLeadingTrivia()) - .WithAdditionalAnnotations(Formatter.Annotation); - - var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, returnIdentifierSymbol, property.Identifier.ValueText, document.Project.Solution.Workspace.Options, cancellationToken); - document = newSolution.GetDocument(document.Id); - root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - root = root.InsertNodesAfter(root.GetCurrentNode(property), new[] { newProperty }); - - var multipleVariableDeclaration = fieldDeclaration.Declaration.Variables.Count > 1; - if (multipleVariableDeclaration) + var propertySymbol = semanticModel.GetDeclaredSymbol(property); + var newRoot = root.TrackNodes(returnIdentifier, fieldDeclaration, property, variableDeclarator); + //cycle + var newDocument = document.WithSyntaxRoot(newRoot); + newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken); + var newProperty = CreateAutoProperty(property, variableDeclarator, fieldSymbol, propertySymbol); + Solution newSolution; + if (IsExplicityImplementation(propertySymbol)) { - var newfieldDeclaration = fieldDeclaration.WithDeclaration(fieldDeclaration.Declaration.RemoveNode(variableDeclarator, SyntaxRemoveOptions.KeepNoTrivia)); - root = root.RemoveNode(root.GetCurrentNode(property), SyntaxRemoveOptions.KeepNoTrivia); - root = root.ReplaceNode(root.GetCurrentNode(fieldDeclaration), newfieldDeclaration); + semanticModel = await newDocument.GetSemanticModelAsync(cancellationToken); + returnIdentifier = newRoot.GetCurrentNode(returnIdentifier); + fieldSymbol = (IFieldSymbol)semanticModel.GetSymbolInfo(returnIdentifier).Symbol; + newSolution = await RenameSymbolAndKeepExplicitPropertiesBoundAsync(newDocument.Project.Solution, property.Identifier.ValueText, fieldSymbol, propertySymbol, cancellationToken); + newDocument = newSolution.GetDocument(newDocument.Id); + newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + newRoot = newRoot.ReplaceNode(newRoot.GetCurrentNode(property), newProperty); + newSolution = newSolution.WithDocumentSyntaxRoot(newDocument.Id, newRoot); } else { - root = root.RemoveNodes(root.GetCurrentNodes(new SyntaxNode[] { fieldDeclaration, property }), SyntaxRemoveOptions.KeepNoTrivia); + var currentProperty = newRoot.GetCurrentNode(property); + var type = (TypeDeclarationSyntax)currentProperty.Parent; + var propertyIndex = type.Members.IndexOf(currentProperty); + //Remove the property: this is needed otherwise the rename that happens bellow will not be able to + //correctly redirect the references to the field, as the property will conflict with the name. + //The conflict is specially troublesome for circular references, such as the one that caused the bug #702. + newRoot = newRoot.ReplaceNode(type, type.RemoveNode(currentProperty, SyntaxRemoveOptions.KeepNoTrivia)); + //cycle + newDocument = newDocument.WithSyntaxRoot(newRoot); + newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken); + semanticModel = await newDocument.GetSemanticModelAsync(cancellationToken); + fieldSymbol = (IFieldSymbol)semanticModel.GetDeclaredSymbol(newRoot.GetCurrentNode(variableDeclarator)); + //rename the field: + newSolution = await Renamer.RenameSymbolAsync(newDocument.Project.Solution, fieldSymbol, property.Identifier.ValueText, newDocument.Project.Solution.Workspace.Options, cancellationToken).ConfigureAwait(false); + //cycle + newDocument = newSolution.GetDocument(newDocument.Id); + newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + //add the property back: + var currentType = (TypeDeclarationSyntax)newRoot.GetCurrentNode(fieldDeclaration).Parent; + var newMembers = currentType.Members.Insert(propertyIndex, newProperty); + var newType = WithMembers(currentType, newMembers); + newRoot = newRoot.ReplaceNode(currentType, newType); + newSolution = newSolution.WithDocumentSyntaxRoot(newDocument.Id, newRoot); } + newDocument = newSolution.GetDocument(newDocument.Id); + newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + newRoot = RemoveField(newRoot, variableDeclarator, fieldDeclaration); + return newSolution.WithDocumentSyntaxRoot(newDocument.Id, newRoot); + } - document = document.WithSyntaxRoot(root); - return document.Project.Solution; + private static SyntaxNode RemoveField(SyntaxNode root, VariableDeclaratorSyntax variableDeclarator, FieldDeclarationSyntax fieldDeclaration) + { + var currentField = root.GetCurrentNode(fieldDeclaration); + var multipleVariableDeclaration = fieldDeclaration.Declaration.Variables.Count > 1; + root = multipleVariableDeclaration + ? root.ReplaceNode(currentField, fieldDeclaration + .WithDeclaration(fieldDeclaration.Declaration.RemoveNode(variableDeclarator, SyntaxRemoveOptions.KeepNoTrivia))) + : root.RemoveNode(currentField, SyntaxRemoveOptions.KeepNoTrivia); + return root; } - private static PropertyDeclarationSyntax GetSimpleProperty(PropertyDeclarationSyntax property, VariableDeclaratorSyntax variableDeclarator) + private static async Task RenameSymbolAndKeepExplicitPropertiesBoundAsync(Solution solution, string propertyName, + ISymbol returnIdentifierSymbol, IPropertySymbol propertySymbol, CancellationToken cancellationToken) { - var simpleGetSetPropetie = property.WithAccessorList(SyntaxFactory.AccessorList(SyntaxFactory.List(new[] { + var interfaceType = propertySymbol.ExplicitInterfaceImplementations.First().ContainingType; + var references = await SymbolFinder.FindReferencesAsync(returnIdentifierSymbol, solution, cancellationToken).ConfigureAwait(false); + var documentGroups = references.SelectMany(r => r.Locations).GroupBy(loc => loc.Document); + var newSolution = solution; + var propertyIdentifier = SyntaxFactory.IdentifierName(propertyName); + foreach (var documentGroup in documentGroups) + { + var referencingDocument = documentGroup.Key; + referencingDocument = newSolution.GetDocument(referencingDocument.Id); + var referencingDocRoot = await referencingDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var diagnosticNodes = documentGroup.Select(referenceLocation => referencingDocRoot.FindNode(referenceLocation.Location.SourceSpan)).ToList(); + referencingDocRoot = referencingDocRoot.TrackNodes(diagnosticNodes); + foreach (var diagnosticNode in diagnosticNodes) + { + var trackedNode = referencingDocRoot.GetCurrentNode(diagnosticNode); + var identifierName = (IdentifierNameSyntax)trackedNode; + var memberAccess = identifierName.FirstAncestorOfKind(SyntaxKind.SimpleMemberAccessExpression); + if (memberAccess != null && memberAccess.Name == identifierName) + { + var newMemberAccess = memberAccess.WithExpression( + SyntaxFactory.ParenthesizedExpression( + SyntaxFactory.CastExpression(SyntaxFactory.ParseTypeName(interfaceType.ToString()), memberAccess.Expression))) + .WithName(propertyIdentifier) + .WithAdditionalAnnotations(Simplifier.Annotation); + referencingDocRoot = referencingDocRoot.ReplaceNode(memberAccess, newMemberAccess); + } + else + { + var newMemberAccess = SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ParenthesizedExpression( + SyntaxFactory.CastExpression(SyntaxFactory.ParseTypeName(interfaceType.ToString()), SyntaxFactory.ParseExpression("this"))), + propertyIdentifier) + .WithAdditionalAnnotations(Simplifier.Annotation); + referencingDocRoot = referencingDocRoot.ReplaceNode(trackedNode, newMemberAccess); + } + } + newSolution = newSolution.WithDocumentSyntaxRoot(referencingDocument.Id, referencingDocRoot); + } + return newSolution; + } + + private static PropertyDeclarationSyntax CreateAutoProperty(PropertyDeclarationSyntax property, VariableDeclaratorSyntax variableDeclarator, + IFieldSymbol fieldSymbol, IPropertySymbol propertySymbol) + { + var newProperty = property.WithAccessorList(SyntaxFactory.AccessorList(SyntaxFactory.List(new[] { SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)), SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) }))); - return variableDeclarator.Initializer == null ? - simpleGetSetPropetie : - simpleGetSetPropetie.WithInitializer(variableDeclarator.Initializer) + newProperty = variableDeclarator.Initializer == null ? + newProperty : + newProperty.WithInitializer(variableDeclarator.Initializer) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + newProperty = CreatePropertyWithCorrectAccessibility(newProperty, fieldSymbol, propertySymbol); + newProperty = newProperty + .WithTriviaFrom(property) + .WithAdditionalAnnotations(Formatter.Annotation); + return newProperty; + } + + private static PropertyDeclarationSyntax CreatePropertyWithCorrectAccessibility(PropertyDeclarationSyntax property, IFieldSymbol fieldSymbol, IPropertySymbol propertySymbol) + { + if (IsExplicityImplementation(propertySymbol)) + return property; + var existingModifiers = property.Modifiers.Where(m => + { + var modifierText = m.ValueText; + return modifierText != "private" + && modifierText != "protected" + && modifierText != "public" + && modifierText != "internal"; + }); + var newAccessibilityModifiers = propertySymbol.DeclaredAccessibility + .GetMinimumCommonAccessibility(fieldSymbol.DeclaredAccessibility) + .GetTokens() + .Aggregate(existingModifiers, (ts, t) => + ts.Any(tt => tt.ValueText == t.ValueText) ? ts : ts.Union(new[] { t }).ToArray()) + .OrderBy(t => t.ValueText); + var newProperty = property.WithModifiers(SyntaxFactory.TokenList(newAccessibilityModifiers)); + return newProperty; + } + + private static bool IsExplicityImplementation(IPropertySymbol propertySymbol) => propertySymbol.ExplicitInterfaceImplementations.Any(); + + private static TypeDeclarationSyntax WithMembers(TypeDeclarationSyntax type, SyntaxList newMembers) + { + TypeDeclarationSyntax newType; + var classDeclaration = type as ClassDeclarationSyntax; + if (classDeclaration != null) + { + newType = classDeclaration.WithMembers(newMembers); + } + else + { + var structDeclaration = (StructDeclarationSyntax)type; + newType = structDeclaration.WithMembers(newMembers); + } + return newType; } } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Style/TaskNameAsyncAnalyzer.cs b/src/CSharp/CodeCracker/Style/TaskNameAsyncAnalyzer.cs index d3f91f05a..160d792e2 100644 --- a/src/CSharp/CodeCracker/Style/TaskNameAsyncAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/TaskNameAsyncAnalyzer.cs @@ -3,18 +3,19 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; +using System.Linq; namespace CodeCracker.CSharp.Style { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class TaskNameAsyncAnalyzer : DiagnosticAnalyzer { - internal const string Title = "Async method can be terminating with 'Async' name."; + internal const string Title = "Asynchronous method can be terminated with the 'Async' keyword."; internal const string MessageFormat = "Change method name to {0}"; internal const string Category = SupportedCategories.Style; - const string Description = "Async method can be terminating with 'Async' name."; + const string Description = "Asynchronous method can be terminated with the 'Async' keyword."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.TaskNameAsync.ToDiagnosticId(), Title, MessageFormat, @@ -32,26 +33,23 @@ private static void AnalyzeMethod(SyntaxNodeAnalysisContext context) { if (context.IsGenerated()) return; var method = (MethodDeclarationSyntax)context.Node; + var semanticModel = context.SemanticModel; + if (method.IsImplementingInterface(semanticModel)) return; if (method.Identifier.ToString().EndsWith("Async")) return; if (method.Modifiers.Any(SyntaxKind.NewKeyword, SyntaxKind.OverrideKeyword)) return; - var errorMessage = method.Identifier.ToString() + "Async"; var diag = Diagnostic.Create(Rule, method.Identifier.GetLocation(), errorMessage); - if (method.Modifiers.Any(SyntaxKind.AsyncKeyword)) { context.ReportDiagnostic(diag); return; } - var returnType = context.SemanticModel.GetSymbolInfo(method.ReturnType).Symbol as INamedTypeSymbol; + var returnType = semanticModel.GetSymbolInfo(method.ReturnType).Symbol as INamedTypeSymbol; if (returnType == null) return; - - if (returnType.ToString() == "System.Threading.Tasks.Task" || - (returnType.IsGenericType && returnType.ConstructedFrom.ToString() == "System.Threading.Tasks.Task")) - { - context.ReportDiagnostic(diag); - } - + if (returnType.ToString() != "System.Threading.Tasks.Task" && + (!returnType.IsGenericType || returnType.ConstructedFrom.ToString() != "System.Threading.Tasks.Task")) + return; + context.ReportDiagnostic(diag); } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Style/TaskNameAsyncCodeFixProvider.cs b/src/CSharp/CodeCracker/Style/TaskNameAsyncCodeFixProvider.cs index ac8192347..01b9aa6d3 100644 --- a/src/CSharp/CodeCracker/Style/TaskNameAsyncCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Style/TaskNameAsyncCodeFixProvider.cs @@ -12,7 +12,7 @@ namespace CodeCracker.CSharp.Style { - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(TaskNameAsyncCodeFixProvider)), Shared] + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CodeFixProvider)), Shared] public class TaskNameAsyncCodeFixProvider : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.TaskNameAsync.ToDiagnosticId()); @@ -22,16 +22,36 @@ public class TaskNameAsyncCodeFixProvider : CodeFixProvider public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) { var diagnostic = context.Diagnostics.First(); + + if (GetMethodName(diagnostic) == "Main" && IsUsingCSharp7(diagnostic)) + return Task.FromResult(0); + context.RegisterCodeFix(CodeAction.Create("Change method name including 'Async'.", c => ChangeMethodNameAsync(context.Document, diagnostic, c), nameof(TaskNameAsyncCodeFixProvider)), diagnostic); + return Task.FromResult(0); } + private static string GetMethodName(Diagnostic diagnostic) + { + return diagnostic.Location.SourceTree.ToString().Substring(diagnostic.Location.SourceSpan.Start, diagnostic.Location.SourceSpan.End - diagnostic.Location.SourceSpan.Start); + } + + private static bool IsUsingCSharp7(Diagnostic diagnostic) + { + return ((CSharpParseOptions)diagnostic.Location.SourceTree.Options).LanguageVersion.ToString() == "CSharp7"; + } + private static async Task ChangeMethodNameAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var methodStatement = root.FindToken(diagnostic.Location.SourceSpan.Start).Parent.AncestorsAndSelf().OfType().First(); var semanticModel = await document.GetSemanticModelAsync(cancellationToken); - var newName = methodStatement.Identifier.ToString() + "Async"; + var oldName = methodStatement.Identifier.ToString(); + if (HasAsyncCorrectlyInName(oldName)) + { + oldName = oldName.Remove(oldName.IndexOf("Async"), "Async".Length); + } + var newName = oldName + "Async"; var solution = document.Project.Solution; var symbol = semanticModel.GetDeclaredSymbol(methodStatement, cancellationToken); var options = solution.Workspace.Options; @@ -39,5 +59,25 @@ private static async Task ChangeMethodNameAsync(Document document, Dia options, cancellationToken).ConfigureAwait(false); return newSolution; } + + private static bool HasAsyncCorrectlyInName(string name) + { + var index = name.IndexOf("Async"); + if (index < 0) + { + return false; + } + + var postAsyncLetterIndex = index + "Async".Length; + if (postAsyncLetterIndex >= name.Length) + { + return false; + } + + var valueAfterAsync = name[postAsyncLetterIndex]; + return char.IsDigit(valueAfterAsync) + || (char.IsLetter(valueAfterAsync) && char.IsUpper(valueAfterAsync)) + || valueAfterAsync == '_'; + } } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Style/TernaryOperatorAnalyzer.cs b/src/CSharp/CodeCracker/Style/TernaryOperatorAnalyzer.cs index 1231a8705..0b53d35e5 100644 --- a/src/CSharp/CodeCracker/Style/TernaryOperatorAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/TernaryOperatorAnalyzer.cs @@ -15,7 +15,7 @@ public class TernaryOperatorAnalyzer : DiagnosticAnalyzer internal const string Category = SupportedCategories.Style; internal const string MessageFormatForIfWithAssignment = "{0}"; - internal static DiagnosticDescriptor RuleForIfWithReturn = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor RuleForIfWithReturn = new DiagnosticDescriptor( DiagnosticId.TernaryOperator_Return.ToDiagnosticId(), Title, MessageFormatForIfWithReturn, @@ -24,7 +24,7 @@ public class TernaryOperatorAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.TernaryOperator_Return)); - internal static DiagnosticDescriptor RuleForIfWithAssignment = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor RuleForIfWithAssignment = new DiagnosticDescriptor( DiagnosticId.TernaryOperator_Assignment.ToDiagnosticId(), Title, MessageFormatForIfWithAssignment, diff --git a/src/CSharp/CodeCracker/Style/TernaryOperatorCodeFixProvider.cs b/src/CSharp/CodeCracker/Style/TernaryOperatorCodeFixProvider.cs index 8cb60e2fc..b36fa9584 100644 --- a/src/CSharp/CodeCracker/Style/TernaryOperatorCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Style/TernaryOperatorCodeFixProvider.cs @@ -4,32 +4,18 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System; namespace CodeCracker.CSharp.Style { - public abstract class TernaryOperatorCodeFixProviderBase : CodeFixProvider - { - protected static ExpressionSyntax MakeTernaryOperand(ExpressionSyntax expression, SemanticModel semanticModel, ITypeSymbol type, TypeSyntax typeSyntax) - { - if (type?.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) - { - var constValue = semanticModel.GetConstantValue(expression); - if (constValue.HasValue && constValue.Value == null) - { - return SyntaxFactory.CastExpression(typeSyntax, expression); - } - } - return expression; - } - } - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(TernaryOperatorWithReturnCodeFixProvider)), Shared] - public class TernaryOperatorWithReturnCodeFixProvider : TernaryOperatorCodeFixProviderBase + public class TernaryOperatorWithReturnCodeFixProvider : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.TernaryOperator_Return.ToDiagnosticId()); @@ -48,19 +34,14 @@ private static async Task MakeTernaryAsync(Document document, Diagnost var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var diagnosticSpan = diagnostic.Location.SourceSpan; var ifStatement = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); - var statementInsideIf = (ReturnStatementSyntax)(ifStatement.Statement is BlockSyntax ? ((BlockSyntax)ifStatement.Statement).Statements.Single() : ifStatement.Statement); + var returnStatementInsideIf = (ReturnStatementSyntax)(ifStatement.Statement is BlockSyntax ? ((BlockSyntax)ifStatement.Statement).Statements.Single() : ifStatement.Statement); var elseStatement = ifStatement.Else; - var statementInsideElse = (ReturnStatementSyntax)(elseStatement.Statement is BlockSyntax ? ((BlockSyntax)elseStatement.Statement).Statements.Single() : elseStatement.Statement); - + var returnStatementInsideElse = (ReturnStatementSyntax)(elseStatement.Statement is BlockSyntax ? ((BlockSyntax)elseStatement.Statement).Statements.Single() : elseStatement.Statement); var semanticModel = await document.GetSemanticModelAsync(cancellationToken); - var type = semanticModel.GetTypeInfo(statementInsideIf.Expression).ConvertedType; - var typeSyntax = SyntaxFactory.IdentifierName(type.ToMinimalDisplayString(semanticModel, statementInsideIf.SpanStart)); - var trueExpression = MakeTernaryOperand(statementInsideIf.Expression, semanticModel, type, typeSyntax); - var falseExpression = MakeTernaryOperand(statementInsideElse.Expression, semanticModel, type, typeSyntax); - + var conditionalExpression = TernaryOperatorCodeFixHelper.CreateExpressions(returnStatementInsideIf.Expression, returnStatementInsideElse.Expression, ifStatement.Condition, semanticModel); var ternary = SyntaxFactory.ReturnStatement( - SyntaxFactory.ConditionalExpression(ifStatement.Condition, trueExpression, falseExpression)) + conditionalExpression) .WithLeadingTrivia(ifStatement.GetLeadingTrivia()) .WithTrailingTrivia(ifStatement.GetTrailingTrivia()) .WithAdditionalAnnotations(Formatter.Annotation); @@ -71,7 +52,7 @@ private static async Task MakeTernaryAsync(Document document, Diagnost } [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(TernaryOperatorWithAssignmentCodeFixProvider)), Shared] - public class TernaryOperatorWithAssignmentCodeFixProvider : TernaryOperatorCodeFixProviderBase + public class TernaryOperatorWithAssignmentCodeFixProvider : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.TernaryOperator_Assignment.ToDiagnosticId()); @@ -92,20 +73,16 @@ private static async Task MakeTernaryAsync(Document document, Diagnost var expressionInsideIf = (ExpressionStatementSyntax)(ifStatement.Statement is BlockSyntax ? ((BlockSyntax)ifStatement.Statement).Statements.Single() : ifStatement.Statement); var elseStatement = ifStatement.Else; var expressionInsideElse = (ExpressionStatementSyntax)(elseStatement.Statement is BlockSyntax ? ((BlockSyntax)elseStatement.Statement).Statements.Single() : elseStatement.Statement); - var assignmentExpressionInsideIf = (AssignmentExpressionSyntax)expressionInsideIf.Expression; var assignmentExpressionInsideElse = (AssignmentExpressionSyntax)expressionInsideElse.Expression; var semanticModel = await document.GetSemanticModelAsync(cancellationToken); - var type = semanticModel.GetTypeInfo(assignmentExpressionInsideIf).Type; - var typeSyntax = SyntaxFactory.IdentifierName(type.ToMinimalDisplayString(semanticModel, assignmentExpressionInsideIf.SpanStart)); - var trueExpression = MakeTernaryOperand(assignmentExpressionInsideIf.Right, semanticModel, type, typeSyntax); - var falseExpression = MakeTernaryOperand(assignmentExpressionInsideElse.Right, semanticModel, type, typeSyntax); + var conditionalExpression = TernaryOperatorCodeFixHelper.CreateExpressions(assignmentExpressionInsideIf.Right, assignmentExpressionInsideElse.Right, ifStatement.Condition, semanticModel); var ternary = SyntaxFactory.ExpressionStatement( SyntaxFactory.AssignmentExpression( assignmentExpressionInsideIf.Kind(), assignmentExpressionInsideIf.Left, - SyntaxFactory.ConditionalExpression(ifStatement.Condition, trueExpression, falseExpression))) + conditionalExpression)) .WithLeadingTrivia(ifStatement.GetLeadingTrivia()) .WithTrailingTrivia(ifStatement.GetTrailingTrivia()) .WithAdditionalAnnotations(Formatter.Annotation); @@ -114,4 +91,161 @@ private static async Task MakeTernaryAsync(Document document, Diagnost return newDocument; } } + + internal static class TernaryOperatorCodeFixHelper + { + public static ExpressionSyntax CreateExpressions(ExpressionSyntax ifExpression, ExpressionSyntax elseExpression, ExpressionSyntax ifStatementCondition, SemanticModel semanticModel) + { + + var methodCallArgApplied = TryApplyArgsOnMethodCalls(ifStatementCondition, ifExpression, elseExpression, semanticModel); + if (methodCallArgApplied != null) return methodCallArgApplied; + var constructorArgsApplied = TryApplyArgsOnConstructorCalls(ifStatementCondition, ifExpression, elseExpression, semanticModel); + if (constructorArgsApplied != null) return constructorArgsApplied; + var ifTypeInfo = semanticModel.GetTypeInfo(ifExpression); + var elseTypeInfo = semanticModel.GetTypeInfo(elseExpression); + var typeSyntax = SyntaxFactory.IdentifierName(ifTypeInfo.ConvertedType.ToMinimalDisplayString(semanticModel, ifExpression.SpanStart)); + ExpressionSyntax trueExpression; ExpressionSyntax falseExpression; + CreateExpressions(ifExpression, elseExpression, ifTypeInfo.Type, elseTypeInfo.Type, + ifTypeInfo.ConvertedType, elseTypeInfo.ConvertedType, typeSyntax, semanticModel, out trueExpression, out falseExpression); + return SyntaxFactory.ConditionalExpression(ifStatementCondition, trueExpression, falseExpression); + } + + private static ExpressionSyntax TryApplyArgsOnConstructorCalls(ExpressionSyntax ifStatementCondition, ExpressionSyntax ifExpression, ExpressionSyntax elseExpression, SemanticModel semanticModel) + { + if (ifExpression is ObjectCreationExpressionSyntax && elseExpression is ObjectCreationExpressionSyntax) + { + var ifObjCreation = ifExpression as ObjectCreationExpressionSyntax; + var elseObjCreation = elseExpression as ObjectCreationExpressionSyntax; + if ((ifObjCreation.Initializer == null && elseObjCreation.Initializer == null) || + ifObjCreation.Initializer != null && elseObjCreation.Initializer != null && + ifObjCreation.Initializer.GetText().ContentEquals(elseObjCreation.Initializer.GetText())) // Initializer are either absent or text equals + { + var ifMethodinfo = semanticModel.GetSymbolInfo(ifObjCreation); + var elseMethodinfo = semanticModel.GetSymbolInfo(elseObjCreation); + if (object.Equals(ifMethodinfo, elseMethodinfo)) //same constructor and overload + { + var findSingleArgumentIndexThatDiffers = FindSingleArgumentIndexThatDiffers(ifObjCreation.ArgumentList, elseObjCreation.ArgumentList, semanticModel); + if (findSingleArgumentIndexThatDiffers >= 0) + return SyntaxFactory.ObjectCreationExpression(ifObjCreation.Type, CreateMethodArgumentList(ifStatementCondition, ifObjCreation.ArgumentList, elseObjCreation.ArgumentList, findSingleArgumentIndexThatDiffers, semanticModel), ifObjCreation.Initializer); + } + } + } + return null; + } + + private static ExpressionSyntax TryApplyArgsOnMethodCalls(ExpressionSyntax ifStatementCondition, ExpressionSyntax ifExpression, ExpressionSyntax elseExpression, SemanticModel semanticModel) + { + if (ifExpression is InvocationExpressionSyntax && elseExpression is InvocationExpressionSyntax) + { + var ifInvocation = ifExpression as InvocationExpressionSyntax; + var elseInvocation = elseExpression as InvocationExpressionSyntax; + var ifMethodinfo = semanticModel.GetSymbolInfo(ifInvocation.Expression); + var elseMethodinfo = semanticModel.GetSymbolInfo(elseInvocation.Expression); + if (object.Equals(ifMethodinfo, elseMethodinfo) && ifMethodinfo.CandidateReason != CandidateReason.LateBound) //same method and overload, but not dynamic + { + if (ifInvocation.Expression.GetText().ContentEquals(elseInvocation.Expression.GetText())) //same 'path' to the invocation + { + var findSingleArgumentIndexThatDiffers = FindSingleArgumentIndexThatDiffers(ifInvocation.ArgumentList, elseInvocation.ArgumentList, semanticModel); + if (findSingleArgumentIndexThatDiffers >= 0) + return SyntaxFactory.InvocationExpression(ifInvocation.Expression, CreateMethodArgumentList(ifStatementCondition, ifInvocation.ArgumentList, elseInvocation.ArgumentList, findSingleArgumentIndexThatDiffers, semanticModel)); + } + } + } + return null; + } + + private static ArgumentListSyntax CreateMethodArgumentList(ExpressionSyntax ifStatementCondition, ArgumentListSyntax argList1, ArgumentListSyntax argList2, int argumentIndexThatDiffers, SemanticModel semanticModel) + { + var zipped = argList1.Arguments.Zip(argList2.Arguments, (a1, a2) => new { a1, a2 }).Select((a, i) => new { a.a1, a.a2, i }); + var argSelector = zipped.Select((args, i) => + (i == argumentIndexThatDiffers) ? + SyntaxFactory.Argument(GetConditionalExpressionForArgument(ifStatementCondition, args.a1.Expression, args.a2.Expression, semanticModel)) + : args.a1); + return SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(argSelector)); + } + + private static ConditionalExpressionSyntax GetConditionalExpressionForArgument(ExpressionSyntax ifStatementCondition, ExpressionSyntax ifExpression, ExpressionSyntax elseExpression, SemanticModel semanticModel) + { + var ifTypeInfo = semanticModel.GetTypeInfo(ifExpression); + var elseTypeInfo = semanticModel.GetTypeInfo(elseExpression); + var typeSyntax = SyntaxFactory.IdentifierName(ifTypeInfo.ConvertedType.ToMinimalDisplayString(semanticModel, ifExpression.SpanStart)); + ExpressionSyntax trueExpression; ExpressionSyntax falseExpression; + CreateExpressions(ifExpression, elseExpression, ifTypeInfo.Type, elseTypeInfo.Type, + ifTypeInfo.ConvertedType, elseTypeInfo.ConvertedType, typeSyntax, semanticModel, out trueExpression, out falseExpression); + return SyntaxFactory.ConditionalExpression(ifStatementCondition, trueExpression, falseExpression); + } + + private static int FindSingleArgumentIndexThatDiffers(ArgumentListSyntax argList1, ArgumentListSyntax argList2, SemanticModel semanticModel) + { + if (argList1.Arguments.Count != argList2.Arguments.Count) return -1; // in case of 'params' + var zipped = argList1.Arguments.Zip(argList2.Arguments, (a1, a2) => new { a1, a2 }).Select((a, i) => new { a.a1, a.a2, i }); + var singleMissmatch = zipped.Where(args => + { + var a1Text = args.a1.GetText(); + var a2Text = args.a2.GetText(); + return !a1Text.ContentEquals(a2Text); + }).Take(2).ToList(); + return (singleMissmatch.Count == 1) ? singleMissmatch[0].i : -1; + } + + private static void CreateExpressions(ExpressionSyntax ifExpression, ExpressionSyntax elseExpression, + ITypeSymbol ifType, ITypeSymbol elseType, + ITypeSymbol ifConvertedType, ITypeSymbol elseConvertedType, + TypeSyntax typeSyntax, SemanticModel semanticModel, + out ExpressionSyntax trueExpression, out ExpressionSyntax falseExpression) + { + var isNullable = false; + if (ifConvertedType?.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) + { + var constValue = semanticModel.GetConstantValue(ifExpression); + trueExpression = constValue.HasValue && constValue.Value == null + ? SyntaxFactory.CastExpression(typeSyntax, ifExpression) + : ifExpression; + isNullable = true; + } + else + { + trueExpression = ifExpression; + } + if (elseConvertedType?.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) + { + var constValue = semanticModel.GetConstantValue(elseExpression); + falseExpression = constValue.HasValue && constValue.Value == null + ? SyntaxFactory.CastExpression(typeSyntax, elseExpression) + : elseExpression; + isNullable = true; + } + else + { + falseExpression = elseExpression; + } + if (!elseType.HasImplicitNumericConversion(ifType) + && !IsEnumAndZero(ifType, elseExpression) + && !IsEnumAndZero(elseType, ifExpression) + && (!isNullable && !ifType.CanBeAssignedTo(elseType) || !elseType.CanBeAssignedTo(ifType)) + && ifType != ifConvertedType) + trueExpression = CastToConvertedType(ifExpression, ifConvertedType); + } + + private static bool IsEnumAndZero(ITypeSymbol type, ExpressionSyntax expression) => + type?.BaseType?.SpecialType == SpecialType.System_Enum && expression?.ToString() == "0"; + + private static ExpressionSyntax CastToConvertedType(ExpressionSyntax ifExpression, ITypeSymbol ifConvertedType) + => SyntaxFactory.CastExpression(SyntaxFactory.ParseTypeName(ifConvertedType.ToString()).WithAdditionalAnnotations(Simplifier.Annotation), ifExpression); + + private static bool CanBeAssignedTo(this ITypeSymbol type, ITypeSymbol possibleBaseType) + { + if (type == null || possibleBaseType == null) return true; + if (type.Kind == SymbolKind.ErrorType || possibleBaseType.Kind == SymbolKind.ErrorType) return true; + if (type == null || possibleBaseType == null) return true; + if (type.SpecialType == SpecialType.System_Object) return true; + var baseType = type; + while (baseType != null && baseType.SpecialType != SpecialType.System_Object) + { + if (baseType.Equals(possibleBaseType)) return true; + baseType = baseType.BaseType; + } + return false; + } + } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Style/UnnecessaryParenthesisAnalyzer.cs b/src/CSharp/CodeCracker/Style/UnnecessaryParenthesisAnalyzer.cs index 2021a075a..410eb60be 100644 --- a/src/CSharp/CodeCracker/Style/UnnecessaryParenthesisAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/UnnecessaryParenthesisAnalyzer.cs @@ -15,7 +15,7 @@ public class UnnecessaryParenthesisAnalyzer : DiagnosticAnalyzer const string Description = "There is no need to specify that the no-parameter constructor is used with " + " an initializer as it is implicit"; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.UnnecessaryParenthesis.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Style/UnnecessaryToStringInStringConcatenationAnalyzer.cs b/src/CSharp/CodeCracker/Style/UnnecessaryToStringInStringConcatenationAnalyzer.cs new file mode 100644 index 000000000..20bc013e4 --- /dev/null +++ b/src/CSharp/CodeCracker/Style/UnnecessaryToStringInStringConcatenationAnalyzer.cs @@ -0,0 +1,194 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; + +namespace CodeCracker.CSharp.Style +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class UnnecessaryToStringInStringConcatenationAnalyzer : DiagnosticAnalyzer + { + internal const string Title = "Unnecessary '.ToString()' call in string concatenation."; + internal const string MessageFormat = Title; + internal const string Category = SupportedCategories.Style; + const string Description = "The runtime automatically calls '.ToString()' method for" + + " string concatenation operations when there is no parameters. Remove them."; + + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( + DiagnosticId.UnnecessaryToStringInStringConcatenation.ToDiagnosticId(), + Title, + MessageFormat, + Category, + DiagnosticSeverity.Info, + customTags: WellKnownDiagnosticTags.Unnecessary, + isEnabledByDefault: true, + description: Description, + helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.UnnecessaryToStringInStringConcatenation)); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => + context.RegisterSyntaxNodeAction(Analyzer, SyntaxKind.AddExpression); + + private static void Analyzer(SyntaxNodeAnalysisContext context) + { + if (context.IsGenerated()) return; + var addExpression = (BinaryExpressionSyntax)context.Node; + + var hasInvocationExpression = addExpression.ChildNodesAndTokens().Any(x => x.IsKind(SyntaxKind.InvocationExpression)); + + //string concatenation must have an InvocationExpression + if (!hasInvocationExpression) return; + var invocationExpressionsThatHaveToStringCall = GetInvocationExpressionsThatHaveToStringCall(addExpression); + + var redundantToStringCalls = FilterInvocationsThatAreRedundant(invocationExpressionsThatHaveToStringCall, addExpression, context.SemanticModel, context.CancellationToken); + + foreach (var expression in redundantToStringCalls) + { + var lastDot = expression.Expression.ChildNodesAndTokens().Last(x => x.IsKind(SyntaxKind.DotToken)); + var toStringTextSpan = new TextSpan(lastDot.Span.Start, expression.ArgumentList.Span.End - lastDot.Span.Start); + var diagnostic = Diagnostic.Create(Rule, Location.Create(context.Node.SyntaxTree, toStringTextSpan)); + context.ReportDiagnostic(diagnostic); + } + } + + private static IEnumerable GetInvocationExpressionsThatHaveToStringCall(BinaryExpressionSyntax addExpression) + { + return addExpression.ChildNodes().OfType() + //Only default call to ToString method must be accepted + .Where(x => x.Expression.ToString().EndsWith(@".ToString") && !x.ArgumentList.Arguments.Any()); + } + + private static IEnumerable FilterInvocationsThatAreRedundant(IEnumerable invocationExpressionsThatHaveToStringCall, BinaryExpressionSyntax addExpression, SemanticModel semanticModel, CancellationToken cancellationToken) + { + foreach (var node in invocationExpressionsThatHaveToStringCall) + { + var toStringReceiver = GetTypeInfoOfReceiverOfToStringCall(node, semanticModel, cancellationToken); + //As long as the underlying type can not be resolved by the compiler (e.g. undefined type) + //removal is not save. + if (toStringReceiver == null || toStringReceiver.TypeKind==TypeKind.Error) + continue; + //If the underlying type is string, removal is save. + if (IsTypeSymbolSystem_String(toStringReceiver)) + yield return node; + var otherType = GetTypeInfoOfOtherNode(node, addExpression, semanticModel, cancellationToken); + if (otherType != null) + { + if (CheckAddOperationOverloadsOfTypes(toStringReceiver, otherType)) + { + yield return node; + } + } + } + } + + private static ITypeSymbol GetTypeInfoOfReceiverOfToStringCall(InvocationExpressionSyntax toStringCall, SemanticModel semanticModel, CancellationToken cancellationToken) + { + if (toStringCall.Expression is MemberAccessExpressionSyntax memberAccess) + { + return semanticModel.GetTypeInfo(memberAccess.Expression, cancellationToken).Type; + } + + return null; + } + + private static ITypeSymbol GetTypeInfoOfOtherNode(SyntaxNode toStringNode, BinaryExpressionSyntax addExpression, SemanticModel semanticModel, CancellationToken cancellationToken) + { + var otherNode = addExpression.Left == toStringNode + ? addExpression.Right + : addExpression.Right == toStringNode + ? addExpression.Left + : null; + if (otherNode != null) + { + return semanticModel.GetTypeInfo(otherNode, cancellationToken).Type; + } + + return null; + } + + private static bool CheckAddOperationOverloadsOfTypes(ITypeSymbol toStringReceiver, ITypeSymbol otherType) + { + //If the underlying type has a custom AddOperator this operator will take precedence over everything else + if (HasTypeCustomAddOperator(toStringReceiver)) + { + return false; + } + + //If the other side is a string the string concatenation will be applied and "ToString" will be implicit called by the concatenation operator + if (IsTypeSymbolSystem_String(otherType)) + { + return true; + } + + //If the underlying type is one of the build in types (numeric, datetime and so on) and the other side is not a string, + //the result a removal is hard to predict and might be wrong. + if (HasAdditionOperator(toStringReceiver)) + { + return false; + } + + //If both sides are delegates, the plus operator combines the delegates: + //https://msdn.microsoft.com/en-us/library/ms173175(v=vs.110).aspx + if (IsTypeSmybolDelegateType(toStringReceiver) && IsTypeSmybolDelegateType(otherType)) + { + return false; + } + + //There might be more cases were removal is save but for now we opt out. + return false; + } + + private static bool IsTypeSmybolDelegateType(ITypeSymbol typeSymbol) + => typeSymbol.TypeKind == TypeKind.Delegate; + + private static bool IsTypeSymbolSystem_String(ITypeSymbol typeSymbol) + => typeSymbol.SpecialType == SpecialType.System_String; + + + // see https://stackoverflow.com/a/41223159 + private static bool HasAdditionOperator(ITypeSymbol type) + { + switch (type.SpecialType) + { + case SpecialType.System_Enum: + case SpecialType.System_Boolean: + case SpecialType.System_Char: + case SpecialType.System_SByte: + case SpecialType.System_Byte: + case SpecialType.System_Int16: + case SpecialType.System_UInt16: + case SpecialType.System_Int32: + case SpecialType.System_UInt32: + case SpecialType.System_Int64: + case SpecialType.System_UInt64: + case SpecialType.System_Decimal: + case SpecialType.System_Single: + case SpecialType.System_Double: + //String has an addition operator but we are looking for types other than string with an addition overload. + //case SpecialType.System_String: + case SpecialType.System_IntPtr: + case SpecialType.System_UIntPtr: + case SpecialType.System_DateTime: + return true; + default: + break; + } + if (type.TypeKind == TypeKind.Enum) + return true; + return false; + } + + private static bool HasTypeCustomAddOperator(ITypeSymbol type) + { + var customAdditionOperators = type.GetMembers("op_Addition").OfType(); + return customAdditionOperators.Any(ms => ms.MethodKind == MethodKind.UserDefinedOperator); + } + } +} \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Style/UnnecessaryToStringInStringConcatenationCodeFixProvider.cs b/src/CSharp/CodeCracker/Style/UnnecessaryToStringInStringConcatenationCodeFixProvider.cs new file mode 100644 index 000000000..4d895d8f5 --- /dev/null +++ b/src/CSharp/CodeCracker/Style/UnnecessaryToStringInStringConcatenationCodeFixProvider.cs @@ -0,0 +1,54 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace CodeCracker.CSharp.Style +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UnnecessaryParenthesisCodeFixProvider)), Shared] + public class UnnecessaryToStringInStringConcatenationCodeFixProvider : CodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds => + ImmutableArray.Create(DiagnosticId.UnnecessaryToStringInStringConcatenation.ToDiagnosticId()); + + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) + { + var diagnostic = context.Diagnostics.First(); + context.RegisterCodeFix(CodeAction.Create("Remove unnecessary ToString", ct => RemoveUnnecessaryToStringAsync(context.Document, diagnostic, ct), nameof(UnnecessaryToStringInStringConcatenationCodeFixProvider)), diagnostic); + return Task.FromResult(0); + } + private static async Task RemoveUnnecessaryToStringAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + var invocationExpression = + root + .FindToken(diagnostic.Location.SourceSpan.Start) + .Parent + .AncestorsAndSelf() + .OfType() + .First(); + + var onlyMemberAccessNode = + invocationExpression + .ChildNodes() + .First() + .ChildNodes() + .First(); + + var newRoot = root.ReplaceNode(invocationExpression, onlyMemberAccessNode); + + var newDocument = document.WithSyntaxRoot(newRoot); + return newDocument; + } + + } + +} \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Style/UseEmptyStringAnalyzer.cs b/src/CSharp/CodeCracker/Style/UseEmptyStringAnalyzer.cs index ad98257eb..cfba91fb3 100644 --- a/src/CSharp/CodeCracker/Style/UseEmptyStringAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/UseEmptyStringAnalyzer.cs @@ -18,7 +18,7 @@ public class UseEmptyStringAnalyzer : DiagnosticAnalyzer internal const string Category = SupportedCategories.Style; const string Description = "Consider using " + EmptyString + InsteadStringEmpty; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.UseEmptyString.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Style/UseEmptyStringCodeFixProviderAll.cs b/src/CSharp/CodeCracker/Style/UseEmptyStringCodeFixProviderAll.cs index 516132f71..0b0915ee3 100644 --- a/src/CSharp/CodeCracker/Style/UseEmptyStringCodeFixProviderAll.cs +++ b/src/CSharp/CodeCracker/Style/UseEmptyStringCodeFixProviderAll.cs @@ -14,7 +14,7 @@ public sealed class UseEmptyStringCodeFixAllProvider : FixAllProvider { private static readonly SyntaxAnnotation useEmptyStringAnnotation = new SyntaxAnnotation(nameof(UseEmptyStringCodeFixAllProvider)); private UseEmptyStringCodeFixAllProvider() { } - public static UseEmptyStringCodeFixAllProvider Instance = new UseEmptyStringCodeFixAllProvider(); + public static readonly UseEmptyStringCodeFixAllProvider Instance = new UseEmptyStringCodeFixAllProvider(); public override Task GetFixAsync(FixAllContext fixAllContext) { @@ -42,8 +42,9 @@ public override Task GetFixAsync(FixAllContext fixAllContext) case FixAllScope.Solution: return Task.FromResult(CodeAction.Create(UseEmptyStringCodeFixProvider.MessageFormat, ct => GetFixedSolutionAsync(fixAllContext.WithCancellationToken(ct)))); + default: + return null; } - return null; } private async static Task GetFixedSolutionAsync(FixAllContext fixAllContext) diff --git a/src/CSharp/CodeCracker/Style/UseStringEmptyAnalyzer.cs b/src/CSharp/CodeCracker/Style/UseStringEmptyAnalyzer.cs index 42a184713..0edc4fe64 100644 --- a/src/CSharp/CodeCracker/Style/UseStringEmptyAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/UseStringEmptyAnalyzer.cs @@ -14,7 +14,7 @@ public class UseStringEmptyAnalyzer : DiagnosticAnalyzer internal const string MessageFormat = "Use 'String.Empty' instead of \"\""; internal const string Category = SupportedCategories.Style; const string Description = "Consider user 'String.Empty' instead of \"\""; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.UseStringEmpty.ToDiagnosticId(), Title, MessageFormat, @@ -33,10 +33,16 @@ private static void AnalyzeNodeVariableDeclaration(SyntaxNodeAnalysisContext con { if (context.IsGenerated()) return; var literal = context.Node as LiteralExpressionSyntax; - if (literal.ToString() != "\"\"" || literal.Ancestors().OfType().Any()) - return; + if (IsNoCandidateDiagnostic(literal)) return; var diagnostic = Diagnostic.Create(Rule, literal.GetLocation()); context.ReportDiagnostic(diagnostic); } + private static bool IsNoCandidateDiagnostic(LiteralExpressionSyntax literal) + { + return literal.ToString() != "\"\"" || + literal.Ancestors().OfType().Any() || + literal.Ancestors().OfType().Any(); + } + } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Style/UseStringEmptyCodeFixProviderAll.cs b/src/CSharp/CodeCracker/Style/UseStringEmptyCodeFixProviderAll.cs index a70674ab9..6d7e92057 100644 --- a/src/CSharp/CodeCracker/Style/UseStringEmptyCodeFixProviderAll.cs +++ b/src/CSharp/CodeCracker/Style/UseStringEmptyCodeFixProviderAll.cs @@ -14,7 +14,7 @@ public sealed class UseStringEmptyCodeFixAllProvider : FixAllProvider { private static readonly SyntaxAnnotation useStringEmptyAnnotation = new SyntaxAnnotation(nameof(UseStringEmptyCodeFixAllProvider)); private UseStringEmptyCodeFixAllProvider() { } - public static UseStringEmptyCodeFixAllProvider Instance = new UseStringEmptyCodeFixAllProvider(); + public static readonly UseStringEmptyCodeFixAllProvider Instance = new UseStringEmptyCodeFixAllProvider(); public override Task GetFixAsync(FixAllContext fixAllContext) { @@ -42,8 +42,9 @@ public override Task GetFixAsync(FixAllContext fixAllContext) case FixAllScope.Solution: return Task.FromResult(CodeAction.Create(UseStringEmptyCodeFixProvider.MessageFormat, ct => GetFixedSolutionAsync(fixAllContext.WithCancellationToken(ct)))); + default: + return null; } - return null; } private async static Task GetFixedSolutionAsync(FixAllContext fixAllContext) diff --git a/src/CSharp/CodeCracker/Usage/AbstractClassShouldNotHavePublicCtorsAnalyzer.cs b/src/CSharp/CodeCracker/Usage/AbstractClassShouldNotHavePublicCtorsAnalyzer.cs index 0acb2a014..273f37ee7 100644 --- a/src/CSharp/CodeCracker/Usage/AbstractClassShouldNotHavePublicCtorsAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/AbstractClassShouldNotHavePublicCtorsAnalyzer.cs @@ -1,20 +1,20 @@ -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; +using System.Linq; namespace CodeCracker.CSharp.Usage { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class AbstractClassShouldNotHavePublicCtorsAnalyzer : DiagnosticAnalyzer { - internal const string Title = "Abastract class should not have public constructors."; + internal const string Title = "Abstract class should not have public constructors."; internal const string MessageFormat = "Constructor should not be public."; internal const string Category = SupportedCategories.Usage; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.AbstractClassShouldNotHavePublicCtors.ToDiagnosticId(), Title, MessageFormat, @@ -33,7 +33,7 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) var ctor = (ConstructorDeclarationSyntax)context.Node; if (!ctor.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword))) return; - var @class = ctor.Ancestors().OfType().FirstOrDefault(); + var @class = ctor.Ancestors().FirstOrDefault() as ClassDeclarationSyntax; if (@class == null) return; if (!@class.Modifiers.Any(m => m.IsKind(SyntaxKind.AbstractKeyword))) return; @@ -41,4 +41,4 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) context.ReportDiagnostic(diagnostic); } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Usage/ArgumentExceptionAnalyzer.cs b/src/CSharp/CodeCracker/Usage/ArgumentExceptionAnalyzer.cs index 9cf229b82..b2a1daa96 100644 --- a/src/CSharp/CodeCracker/Usage/ArgumentExceptionAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/ArgumentExceptionAnalyzer.cs @@ -17,7 +17,7 @@ public class ArgumentExceptionAnalyzer : DiagnosticAnalyzer const string Description = "The string passed as the 'paramName' argument of ArgumentException constructor " + "must be the name of one of the method arguments.\r\n" + "It can be either specified directly or using the nameof() operator (C#6 only)"; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.ArgumentException.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Usage/CallExtensionMethodAsExtensionAnalyzer.cs b/src/CSharp/CodeCracker/Usage/CallExtensionMethodAsExtensionAnalyzer.cs index caf58b069..0a6f14de5 100644 --- a/src/CSharp/CodeCracker/Usage/CallExtensionMethodAsExtensionAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/CallExtensionMethodAsExtensionAnalyzer.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System; namespace CodeCracker.CSharp.Usage { @@ -17,7 +16,7 @@ public class CallExtensionMethodAsExtensionAnalyzer : DiagnosticAnalyzer internal const string MessageFormat = "Do not call '{0}' method of class '{1}' as a static method"; internal const string Category = SupportedCategories.Usage; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.CallExtensionMethodAsExtension.ToDiagnosticId(), Title, MessageFormat, @@ -47,33 +46,36 @@ private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context, Compila var methodCaller = childNodes.OfType().FirstOrDefault(); if (methodCaller == null) return; var argumentsCount = CountArguments(childNodes); + if (argumentsCount == 0) return; var classSymbol = GetCallerClassSymbol(context.SemanticModel, methodCaller.Expression); if (classSymbol == null || !classSymbol.MightContainExtensionMethods) return; var methodSymbol = GetCallerMethodSymbol(context.SemanticModel, methodCaller.Name, argumentsCount); if (methodSymbol == null || !methodSymbol.IsExtensionMethod) return; if (ContainsDynamicArgument(context.SemanticModel, childNodes)) return; - if (IsSelectingADifferentMethod(childNodes, methodCaller.Name, context.Node.SyntaxTree, methodSymbol, methodInvokeSyntax.FirstAncestorOrSelfThatIsAStatement(), compilation)) return; + if (IsSelectingADifferentMethod(childNodes, methodCaller.Name, context.Node.SyntaxTree, methodSymbol, methodInvokeSyntax, compilation)) return; context.ReportDiagnostic(Diagnostic.Create(Rule, methodCaller.GetLocation(), methodSymbol.Name, classSymbol.Name)); } - private static bool IsSelectingADifferentMethod(IEnumerable childNodes, SimpleNameSyntax methodName, SyntaxTree tree, IMethodSymbol methodSymbol, StatementSyntax invocationStatement, Compilation compilation) + private static bool IsSelectingADifferentMethod(IEnumerable childNodes, SimpleNameSyntax methodName, SyntaxTree tree, IMethodSymbol methodSymbol, ExpressionSyntax invocationExpression, Compilation compilation) { - var parameterExpressions = CallExtensionMethodAsExtensionCodeFixProvider.GetParameterExpressions(childNodes); + var parameterExpressions = GetParameterExpressions(childNodes); var firstArgument = parameterExpressions.FirstOrDefault(); - var argumentList = CallExtensionMethodAsExtensionCodeFixProvider.CreateArgumentListSyntaxFrom(parameterExpressions.Skip(1)); - var newInvocationStatement = SyntaxFactory.ExpressionStatement( - CallExtensionMethodAsExtensionCodeFixProvider.CreateInvocationExpression( - firstArgument, methodName, argumentList)).WithAdditionalAnnotations(introduceExtensionMethodAnnotation); + var argumentList = CreateArgumentListSyntaxFrom(parameterExpressions.Skip(1)); + var newInvocationStatement = CreateInvocationExpression(firstArgument, methodName, argumentList) + .WithAdditionalAnnotations(introduceExtensionMethodAnnotation); var extensionMethodNamespaceUsingDirective = SyntaxFactory.UsingDirective(methodSymbol.ContainingNamespace.ToNameSyntax()); var speculativeRootWithExtensionMethod = tree.GetCompilationUnitRoot() - .ReplaceNode(invocationStatement, newInvocationStatement) + .ReplaceNode(invocationExpression, newInvocationStatement) .AddUsings(extensionMethodNamespaceUsingDirective); - var speculativeModel = compilation.ReplaceSyntaxTree(tree, speculativeRootWithExtensionMethod.SyntaxTree) - .GetSemanticModel(speculativeRootWithExtensionMethod.SyntaxTree); - var speculativeInvocationStatement = speculativeRootWithExtensionMethod.SyntaxTree.GetCompilationUnitRoot().GetAnnotatedNodes(introduceExtensionMethodAnnotation).Single() as ExpressionStatementSyntax; + var speculativeTree = speculativeRootWithExtensionMethod.SyntaxTree; + var speculativeTreeOptions = (CSharpParseOptions)speculativeTree.Options; + var speculativeTreeWithCorrectLanguageVersion = speculativeTree.WithRootAndOptions(speculativeRootWithExtensionMethod, speculativeTreeOptions.WithLanguageVersion(((CSharpParseOptions)tree.Options).LanguageVersion)); + var speculativeModel = compilation.ReplaceSyntaxTree(tree, speculativeTreeWithCorrectLanguageVersion) + .GetSemanticModel(speculativeTreeWithCorrectLanguageVersion); + var speculativeInvocationStatement = speculativeTreeWithCorrectLanguageVersion.GetCompilationUnitRoot().GetAnnotatedNodes(introduceExtensionMethodAnnotation).Single() as InvocationExpressionSyntax; var speculativeExtensionMethodSymbol = speculativeModel.GetSymbolInfo(speculativeInvocationStatement.Expression).Symbol as IMethodSymbol; var speculativeNonExtensionFormOfTheMethodSymbol = speculativeExtensionMethodSymbol?.GetConstructedReducedFrom(); - return speculativeNonExtensionFormOfTheMethodSymbol == null || !speculativeNonExtensionFormOfTheMethodSymbol.Equals(methodSymbol); + return speculativeNonExtensionFormOfTheMethodSymbol == null || speculativeNonExtensionFormOfTheMethodSymbol.ToString() != methodSymbol.ToString();//can't compare equality, as speculative symbol might be different } private static int CountArguments(IEnumerable childNodes) => @@ -97,5 +99,19 @@ private static bool ContainsDynamicArgument(SemanticModel sm, IEnumerable() .SelectMany(s => s.Arguments) .Any(a => sm.GetTypeInfo(a.Expression).Type?.Name == "dynamic"); + + public static IEnumerable GetParameterExpressions(IEnumerable childNodes) => + childNodes.OfType().SelectMany(s => s.Arguments).Select(s => s.Expression); + + public static ArgumentListSyntax CreateArgumentListSyntaxFrom(IEnumerable expressions) => + SyntaxFactory.ArgumentList().AddArguments(expressions.Select(s => SyntaxFactory.Argument(s)).ToArray()); + + public static InvocationExpressionSyntax CreateInvocationExpression(ExpressionSyntax sourceExpression, SimpleNameSyntax methodName, ArgumentListSyntax argumentList) => + SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + sourceExpression, + methodName), + argumentList); } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Usage/CallExtensionMethodAsExtensionCodeFixProvider.cs b/src/CSharp/CodeCracker/Usage/CallExtensionMethodAsExtensionCodeFixProvider.cs index 390a616b4..dad77490e 100644 --- a/src/CSharp/CodeCracker/Usage/CallExtensionMethodAsExtensionCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Usage/CallExtensionMethodAsExtensionCodeFixProvider.cs @@ -4,12 +4,12 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; -using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; + namespace CodeCracker.CSharp.Usage { [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CallExtensionMethodAsExtensionCodeFixProvider)), Shared] @@ -40,7 +40,7 @@ private static async Task CallAsExtensionAsync(Document document, Diag .First(); var childNodes = staticInvocationExpression.ChildNodes(); - var parameterExpressions = GetParameterExpressions(childNodes); + var parameterExpressions = CallExtensionMethodAsExtensionAnalyzer.GetParameterExpressions(childNodes); var firstArgument = parameterExpressions.FirstOrDefault(); var callerMethod = childNodes.OfType().FirstOrDefault(); @@ -50,7 +50,7 @@ private static async Task CallAsExtensionAsync(Document document, Diag staticInvocationExpression, firstArgument, callerMethod.Name, - CreateArgumentListSyntaxFrom(parameterExpressions.Skip(1)) + CallExtensionMethodAsExtensionAnalyzer.CreateArgumentListSyntaxFrom(parameterExpressions.Skip(1)) ).WithAdditionalAnnotations(Formatter.Annotation); var semanticModel = await document.GetSemanticModelAsync(); @@ -60,27 +60,13 @@ private static async Task CallAsExtensionAsync(Document document, Diag return newDocument; } - public static IEnumerable GetParameterExpressions(IEnumerable childNodes) => - childNodes.OfType().SelectMany(s => s.Arguments).Select(s => s.Expression); - - public static ArgumentListSyntax CreateArgumentListSyntaxFrom(IEnumerable expressions) => - SyntaxFactory.ArgumentList().AddArguments(expressions.Select(s => SyntaxFactory.Argument(s)).ToArray()); - private static CompilationUnitSyntax ReplaceStaticCallWithExtionMethodCall(CompilationUnitSyntax root, InvocationExpressionSyntax staticInvocationExpression, ExpressionSyntax sourceExpression, SimpleNameSyntax methodName, ArgumentListSyntax argumentList) { - var extensionInvocationExpression = CreateInvocationExpression(sourceExpression, methodName, argumentList) + var extensionInvocationExpression = CallExtensionMethodAsExtensionAnalyzer.CreateInvocationExpression(sourceExpression, methodName, argumentList) .WithLeadingTrivia(staticInvocationExpression.GetLeadingTrivia()); return root.ReplaceNode(staticInvocationExpression, extensionInvocationExpression); } - public static InvocationExpressionSyntax CreateInvocationExpression(ExpressionSyntax sourceExpression, SimpleNameSyntax methodName, ArgumentListSyntax argumentList) => - SyntaxFactory.InvocationExpression( - SyntaxFactory.MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - sourceExpression, - methodName), - argumentList); - private static CompilationUnitSyntax ImportNeededNamespace(CompilationUnitSyntax root, SemanticModel semanticModel, MemberAccessExpressionSyntax callerMethod) { var symbolInfo = semanticModel.GetSymbolInfo(callerMethod.Name); diff --git a/src/CSharp/CodeCracker/Usage/DisposableFieldNotDisposedAnalyzer.cs b/src/CSharp/CodeCracker/Usage/DisposableFieldNotDisposedAnalyzer.cs index d90948001..b90b12001 100644 --- a/src/CSharp/CodeCracker/Usage/DisposableFieldNotDisposedAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/DisposableFieldNotDisposedAnalyzer.cs @@ -16,7 +16,7 @@ public class DisposableFieldNotDisposedAnalyzer : DiagnosticAnalyzer internal const string Category = SupportedCategories.Usage; const string Description = "This class has a disposable field and is not disposing it."; - internal static DiagnosticDescriptor RuleForReturned = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor RuleForReturned = new DiagnosticDescriptor( DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), Title, MessageFormat, @@ -25,7 +25,7 @@ public class DisposableFieldNotDisposedAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: Description, helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.DisposableFieldNotDisposed_Returned)); - internal static DiagnosticDescriptor RuleForCreated = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor RuleForCreated = new DiagnosticDescriptor( DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), Title, MessageFormat, @@ -43,11 +43,12 @@ private static void AnalyzeField(SymbolAnalysisContext context) { if (context.IsGenerated()) return; var fieldSymbol = (IFieldSymbol)context.Symbol; + if (fieldSymbol.IsStatic) return; if (!fieldSymbol.Type.AllInterfaces.Any(i => i.ToString() == "System.IDisposable") && fieldSymbol.Type.ToString() != "System.IDisposable") return; var fieldSyntaxRef = fieldSymbol.DeclaringSyntaxReferences.FirstOrDefault(); var variableDeclarator = fieldSyntaxRef.GetSyntax() as VariableDeclaratorSyntax; if (variableDeclarator == null) return; - if (ContainingTypeImplementsIDisposableAndCallsItOnTheField(context, fieldSymbol, fieldSymbol.ContainingType)) return; + if (ContainingTypeImplementsIDisposableAndCallsItOnTheField(context, fieldSymbol)) return; var props = new Dictionary { { "variableIdentifier", variableDeclarator.Identifier.ValueText } }.ToImmutableDictionary(); if (variableDeclarator.Initializer?.Value is InvocationExpressionSyntax) context.ReportDiagnostic(Diagnostic.Create(RuleForReturned, variableDeclarator.GetLocation(), props, fieldSymbol.Name)); @@ -55,41 +56,77 @@ private static void AnalyzeField(SymbolAnalysisContext context) context.ReportDiagnostic(Diagnostic.Create(RuleForCreated, variableDeclarator.GetLocation(), props, fieldSymbol.Name)); } - private static bool ContainingTypeImplementsIDisposableAndCallsItOnTheField(SymbolAnalysisContext context, IFieldSymbol fieldSymbol, INamedTypeSymbol typeSymbol) + private static bool ContainingTypeImplementsIDisposableAndCallsItOnTheField(SymbolAnalysisContext context, IFieldSymbol fieldSymbol) { - if (typeSymbol == null) return false; - var iDisposableInterface = typeSymbol.AllInterfaces.FirstOrDefault(i => i.ToString() == "System.IDisposable"); - if (iDisposableInterface != null) + var containingType = fieldSymbol.ContainingType; + if (containingType == null) return false; + var iDisposableInterface = containingType.AllInterfaces.FirstOrDefault(i => i.ToString() == "System.IDisposable"); + if (iDisposableInterface == null) return false; + var disposableMethod = iDisposableInterface.GetMembers("Dispose").OfType().First(d => d.Arity == 0); + var disposeMethodSymbol = containingType.FindImplementationForInterfaceMember(disposableMethod) as IMethodSymbol; + if (disposeMethodSymbol == null) return false; + if (disposeMethodSymbol.IsAbstract) return true; + foreach (MethodDeclarationSyntax disposeMethod in disposeMethodSymbol.DeclaringSyntaxReferences.Select(sr => sr.GetSyntax())) { - var disposableMethod = iDisposableInterface.GetMembers("Dispose").OfType().First(d => d.Arity == 0); - var disposeMethodSymbol = typeSymbol.FindImplementationForInterfaceMember(disposableMethod) as IMethodSymbol; - if (disposeMethodSymbol != null) + if (disposeMethod == null) return false; + var semanticModel = context.Compilation.GetSemanticModel(disposeMethod.SyntaxTree); + if (CallsDisposeOnField(fieldSymbol, disposeMethod, semanticModel)) return true; + var invocations = disposeMethod.DescendantNodes().OfKind(SyntaxKind.InvocationExpression); + foreach (var invocation in invocations) { - var disposeMethod = (MethodDeclarationSyntax)disposeMethodSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(); - if (disposeMethod == null) return false; - if (disposeMethod.Modifiers.Any(SyntaxKind.AbstractKeyword)) return true; - var typeDeclaration = (TypeDeclarationSyntax)typeSymbol.DeclaringSyntaxReferences.FirstOrDefault().GetSyntax(); - var semanticModel = context.Compilation.GetSemanticModel(typeDeclaration.SyntaxTree); - if (CallsDisposeOnField(fieldSymbol, disposeMethod, semanticModel)) return true; + var invocationExpressionSymbol = semanticModel.GetSymbolInfo(invocation.Expression).Symbol; + if (invocationExpressionSymbol == null + || invocationExpressionSymbol.Kind != SymbolKind.Method + || invocationExpressionSymbol.Locations.Any(l => l.Kind != LocationKind.SourceFile) + || !invocationExpressionSymbol.ContainingType.Equals(containingType)) continue; + foreach (MethodDeclarationSyntax method in invocationExpressionSymbol.DeclaringSyntaxReferences.Select(sr => sr.GetSyntax())) + if (CallsDisposeOnField(fieldSymbol, method, semanticModel)) return true; } } return false; } - private static bool CallsDisposeOnField(IFieldSymbol fieldSymbol, MethodDeclarationSyntax disposeMethod, SemanticModel semanticModel) + private static bool CallsDisposeOnField(IFieldSymbol fieldSymbol, MethodDeclarationSyntax method, SemanticModel semanticModel) { - var hasDisposeCall = disposeMethod.Body.Statements.OfType() - .Any(exp => + var body = (SyntaxNode)method.Body ?? method.ExpressionBody; + var hasDisposeCall = body.DescendantNodes().OfKind(SyntaxKind.InvocationExpression) + .Any(invocation => { - var invocation = exp.Expression as InvocationExpressionSyntax; - if (!invocation?.Expression?.IsKind(SyntaxKind.SimpleMemberAccessExpression) ?? true) return false; - var memberAccess = (MemberAccessExpressionSyntax)invocation.Expression; - if (memberAccess.Name.Identifier.ToString() != "Dispose" || memberAccess.Name.Arity != 0) return false; - var memberAccessIdentificer = memberAccess.Expression as IdentifierNameSyntax; - if (memberAccessIdentificer == null) return false; - return fieldSymbol.Equals(semanticModel.GetSymbolInfo(memberAccessIdentificer).Symbol); + if (invocation?.Expression?.IsKind(SyntaxKind.SimpleMemberAccessExpression) ?? false) + { + return IsDisposeCallOnField(invocation, fieldSymbol, semanticModel); + + } + + if (invocation?.Expression?.IsKind(SyntaxKind.MemberBindingExpression) ?? false) + { + return IsDisposeWithNullPropagationCallOnField(invocation, fieldSymbol, semanticModel); + } + + return false; }); return hasDisposeCall; } + + private static bool IsDisposeCallOnField(InvocationExpressionSyntax expression, IFieldSymbol fieldSymbol, SemanticModel semanticModel) + { + var memberAccess = (MemberAccessExpressionSyntax)expression.Expression; + if (memberAccess?.Name == null) return false; + if (memberAccess.Name.Identifier.ToString() != "Dispose" || memberAccess.Name.Arity != 0) return false; + var result = fieldSymbol.Equals(semanticModel.GetSymbolInfo(memberAccess.Expression).Symbol); + return result; + } + + private static bool IsDisposeWithNullPropagationCallOnField(InvocationExpressionSyntax expression, IFieldSymbol fieldSymbol, SemanticModel semanticModel) + { + var memberBinding = (MemberBindingExpressionSyntax)expression.Expression; + if (memberBinding?.Name == null) return false; + if (memberBinding.Name.Identifier.ToString() != "Dispose" || memberBinding.Name.Arity != 0) return false; + + var conditionalAccessExpression = memberBinding.Parent.Parent as ConditionalAccessExpressionSyntax; + if (conditionalAccessExpression == null) return false; + var result = fieldSymbol.Equals(semanticModel.GetSymbolInfo(conditionalAccessExpression.Expression).Symbol); + return result; + } } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedAnalyzer.cs b/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedAnalyzer.cs index 506ba41fa..83544effa 100644 --- a/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedAnalyzer.cs @@ -16,8 +16,8 @@ public class DisposableVariableNotDisposedAnalyzer : DiagnosticAnalyzer internal const string Category = SupportedCategories.Usage; const string Description = "When a disposable object is created it should be disposed as soon as possible.\n" + "This warning will appear if you create a disposable object and don't store, return or dispose it."; - - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + public const string cantFix = "cantFix"; + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), Title, MessageFormat, @@ -36,22 +36,32 @@ private static void AnalyzeObjectCreation(SyntaxNodeAnalysisContext context) if (context.IsGenerated()) return; var objectCreation = context.Node as ObjectCreationExpressionSyntax; if (objectCreation == null) return; - if (objectCreation?.Parent is UsingStatementSyntax) return; - if (objectCreation?.Parent is ReturnStatementSyntax) return; - if (objectCreation.Ancestors().Any(i => i.IsAnyKind( + if (objectCreation.Parent == null) return; + + var originalNode = objectCreation; + SyntaxNode topSyntaxNode = originalNode; + while (topSyntaxNode.Parent.IsAnyKind(SyntaxKind.ParenthesizedExpression, SyntaxKind.ConditionalExpression, SyntaxKind.CastExpression, SyntaxKind.CoalesceExpression)) + topSyntaxNode = topSyntaxNode.Parent; + + if (topSyntaxNode.Parent.IsAnyKind(SyntaxKind.ReturnStatement, SyntaxKind.UsingStatement, SyntaxKind.YieldReturnStatement)) + return; + + if (topSyntaxNode.Ancestors().Any(i => i.IsAnyKind( + SyntaxKind.ArrowExpressionClause, SyntaxKind.ThisConstructorInitializer, SyntaxKind.BaseConstructorInitializer, - SyntaxKind.ObjectCreationExpression))) return; + SyntaxKind.ObjectCreationExpression))) + return; var semanticModel = context.SemanticModel; - var type = semanticModel.GetSymbolInfo(objectCreation.Type).Symbol as INamedTypeSymbol; + var type = semanticModel.GetSymbolInfo(originalNode.Type).Symbol as INamedTypeSymbol; if (type == null) return; if (!type.AllInterfaces.Any(i => i.ToString() == "System.IDisposable")) return; ISymbol identitySymbol = null; StatementSyntax statement = null; - if (objectCreation.Parent.IsKind(SyntaxKind.SimpleAssignmentExpression)) + if (topSyntaxNode.Parent.IsKind(SyntaxKind.SimpleAssignmentExpression)) { - var assignmentExpression = (AssignmentExpressionSyntax)objectCreation.Parent; + var assignmentExpression = (AssignmentExpressionSyntax)topSyntaxNode.Parent; identitySymbol = semanticModel.GetSymbolInfo(assignmentExpression.Left).Symbol; if (identitySymbol?.Kind != SymbolKind.Local) return; if (assignmentExpression.FirstAncestorOrSelf() == null) return; @@ -59,21 +69,51 @@ private static void AnalyzeObjectCreation(SyntaxNodeAnalysisContext context) if (usingStatement != null) return; statement = assignmentExpression.Parent as ExpressionStatementSyntax; } - else if (objectCreation.Parent.IsKind(SyntaxKind.EqualsValueClause) && objectCreation.Parent.Parent.IsKind(SyntaxKind.VariableDeclarator)) + else if (topSyntaxNode.Parent.IsKind(SyntaxKind.EqualsValueClause) && topSyntaxNode.Parent.Parent.IsKind(SyntaxKind.VariableDeclarator)) { - var variableDeclarator = (VariableDeclaratorSyntax)objectCreation.Parent.Parent; + var variableDeclarator = (VariableDeclaratorSyntax)topSyntaxNode.Parent.Parent; var variableDeclaration = variableDeclarator?.Parent as VariableDeclarationSyntax; identitySymbol = semanticModel.GetDeclaredSymbol(variableDeclarator); if (identitySymbol == null) return; var usingStatement = variableDeclaration?.Parent as UsingStatementSyntax; if (usingStatement != null) return; statement = variableDeclaration.Parent as LocalDeclarationStatementSyntax; + if (statement != null) + { + //Check inline using + var isUsing = false; + var isSemicolon = false; + var firstToken = statement.GetFirstToken(); + if (firstToken != null) + { + isUsing = firstToken.Text == "using"; + } + + var lastToken = statement.GetLastToken(); + if (lastToken != null) + { + isSemicolon = lastToken.Text == ";"; + } + + var isVariableDeclaration = statement.ChildNodes().First() is VariableDeclarationSyntax; + + if (isUsing && isVariableDeclaration && isSemicolon) return; + } + if ((statement?.FirstAncestorOrSelf()) == null) return; } + else if (topSyntaxNode.Parent.IsAnyKind(SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression)) + { + var anonymousFunction = topSyntaxNode.Parent as AnonymousFunctionExpressionSyntax; + var methodSymbol = semanticModel.GetSymbolInfo(anonymousFunction).Symbol as IMethodSymbol; + if (!methodSymbol.ReturnsVoid) return; + var props = new Dictionary { { "typeName", type.Name }, { cantFix, "" } }.ToImmutableDictionary(); + context.ReportDiagnostic(Diagnostic.Create(Rule, originalNode.GetLocation(), props, type.Name.ToString())); + } else { var props = new Dictionary { { "typeName", type.Name } }.ToImmutableDictionary(); - context.ReportDiagnostic(Diagnostic.Create(Rule, objectCreation.GetLocation(), props, type.Name.ToString())); + context.ReportDiagnostic(Diagnostic.Create(Rule, originalNode.GetLocation(), props, type.Name.ToString())); return; } if (statement != null && identitySymbol != null) @@ -81,47 +121,78 @@ private static void AnalyzeObjectCreation(SyntaxNodeAnalysisContext context) var isDisposeOrAssigned = IsDisposedOrAssigned(semanticModel, statement, (ILocalSymbol)identitySymbol); if (isDisposeOrAssigned) return; var props = new Dictionary { { "typeName", type.Name } }.ToImmutableDictionary(); - context.ReportDiagnostic(Diagnostic.Create(Rule, objectCreation.GetLocation(), props, type.Name.ToString())); + context.ReportDiagnostic(Diagnostic.Create(Rule, originalNode.GetLocation(), props, type.Name.ToString())); } } - private static bool IsDisposedOrAssigned(SemanticModel semanticModel, SyntaxNode node, ILocalSymbol identitySymbol) + private static bool IsDisposedOrAssigned(SemanticModel semanticModel, StatementSyntax statement, ILocalSymbol identitySymbol) { - var method = node.FirstAncestorOrSelf(); + var method = statement.FirstAncestorOrSelf(); if (method == null) return false; - if (IsReturned(method, semanticModel, identitySymbol)) return true; - foreach (var statement in method.Body.DescendantNodes().OfType()) + if (IsReturned(method, statement, semanticModel, identitySymbol)) return true; + foreach (var childStatement in method.Body.DescendantNodes().OfType()) { - if (statement.SpanStart > node.SpanStart - && (IsCorrectDispose(statement as ExpressionStatementSyntax, semanticModel, identitySymbol) - || IsAssignedToField(statement as ExpressionStatementSyntax, semanticModel, identitySymbol))) + if (childStatement.SpanStart > statement.SpanStart + && (IsCorrectDispose(childStatement as ExpressionStatementSyntax, semanticModel, identitySymbol) + || IsPassedAsArgument(childStatement, semanticModel, identitySymbol) + || IsAssignedToFieldOrProperty(childStatement as ExpressionStatementSyntax, semanticModel, identitySymbol))) return true; } return false; } - private static bool IsReturned(MethodDeclarationSyntax method, SemanticModel semanticModel, ILocalSymbol identitySymbol) + private static bool IsPassedAsArgument(StatementSyntax statement, SemanticModel semanticModel, ILocalSymbol identitySymbol) { - var returnTypeSymbol = semanticModel.GetTypeInfo(method.ReturnType).Type; + if (statement == null) return false; + var args = statement.DescendantNodes().OfKind(SyntaxKind.Argument); + foreach (var arg in args) + { + var argSymbol = semanticModel.GetSymbolInfo(arg.Expression).Symbol; + if (identitySymbol.Equals(argSymbol)) return true; + } + return false; + } + + + private static bool IsReturned(MethodDeclarationSyntax method, StatementSyntax statement, SemanticModel semanticModel, ILocalSymbol identitySymbol) + { + var anonymousFunction = statement.FirstAncestorOfKind(SyntaxKind.ParenthesizedLambdaExpression, + SyntaxKind.SimpleLambdaExpression, SyntaxKind.AnonymousMethodExpression) as AnonymousFunctionExpressionSyntax; + IMethodSymbol methodSymbol; + BlockSyntax body; + if (anonymousFunction != null) + { + methodSymbol = semanticModel.GetSymbolInfo(anonymousFunction).Symbol as IMethodSymbol; + body = anonymousFunction.Body as BlockSyntax; + } + else + { + methodSymbol = semanticModel.GetDeclaredSymbol(method); + body = method.Body; + } + if (body == null) return true; + var returnTypeSymbol = methodSymbol?.ReturnType; if (returnTypeSymbol == null) return false; if (returnTypeSymbol.SpecialType == SpecialType.System_Void) return false; - var returns = method.Body.DescendantNodes().OfType(); - var isReturning = returns.Any(r => + var bodyDescendantNodes = body.DescendantNodes().ToList(); + var returnExpressions = bodyDescendantNodes.OfType().Select(r => r.Expression).Union( + bodyDescendantNodes.OfKind(SyntaxKind.YieldReturnStatement).Select(yr => yr.Expression)); + var isReturning = returnExpressions.Any(returnExpression => { - var returnSymbol = semanticModel.GetSymbolInfo(r.Expression).Symbol; + var returnSymbol = semanticModel.GetSymbolInfo(returnExpression).Symbol; if (returnSymbol == null) return false; return returnSymbol.Equals(identitySymbol); }); return isReturning; } - private static bool IsAssignedToField(ExpressionStatementSyntax expressionStatement, SemanticModel semanticModel, ILocalSymbol identitySymbol) + private static bool IsAssignedToFieldOrProperty(ExpressionStatementSyntax expressionStatement, SemanticModel semanticModel, ILocalSymbol identitySymbol) { if (expressionStatement == null) return false; if (!expressionStatement.Expression.IsKind(SyntaxKind.SimpleAssignmentExpression)) return false; var assignment = (AssignmentExpressionSyntax)expressionStatement.Expression; var assignmentTarget = semanticModel.GetSymbolInfo(assignment.Left).Symbol; - if (assignmentTarget?.Kind != SymbolKind.Field) return false; + if (assignmentTarget?.Kind != SymbolKind.Field && assignmentTarget?.Kind != SymbolKind.Property) return false; var assignmentSource = semanticModel.GetSymbolInfo(assignment.Right).Symbol; return (identitySymbol.Equals(assignmentSource)); } @@ -130,17 +201,35 @@ private static bool IsCorrectDispose(ExpressionStatementSyntax expressionStateme { if (expressionStatement == null) return false; var invocation = expressionStatement.Expression as InvocationExpressionSyntax; - if (invocation?.ArgumentList.Arguments.Any() ?? true) return false; - var memberAccess = invocation.Expression as MemberAccessExpressionSyntax; - if (memberAccess == null) return false; + ExpressionSyntax expressionAccessed; + IdentifierNameSyntax memberAccessed; + if (invocation == null) + { + var conditionalAccessExpression = expressionStatement.Expression as ConditionalAccessExpressionSyntax; + if (conditionalAccessExpression == null) return false; + invocation = conditionalAccessExpression.WhenNotNull as InvocationExpressionSyntax; + var memberBinding = invocation?.Expression as MemberBindingExpressionSyntax; + if (memberBinding == null) return false; + expressionAccessed = conditionalAccessExpression.Expression; + memberAccessed = memberBinding.Name as IdentifierNameSyntax; + } + else + { + var memberAccess = invocation.Expression as MemberAccessExpressionSyntax; + if (memberAccess == null) return false; + expressionAccessed = memberAccess.Expression; + memberAccessed = memberAccess.Name as IdentifierNameSyntax; + } + if (memberAccessed == null) return false; + if (invocation.ArgumentList.Arguments.Any()) return false; ISymbol memberSymbol; - if (memberAccess.Expression.IsKind(SyntaxKind.IdentifierName)) + if (expressionAccessed.IsKind(SyntaxKind.IdentifierName)) { - memberSymbol = semanticModel.GetSymbolInfo(memberAccess.Expression).Symbol; + memberSymbol = semanticModel.GetSymbolInfo(expressionAccessed).Symbol; } - else if (memberAccess.Expression.IsKind(SyntaxKind.ParenthesizedExpression)) + else if (expressionAccessed is ParenthesizedExpressionSyntax) { - var parenthesizedExpression = (ParenthesizedExpressionSyntax)memberAccess.Expression; + var parenthesizedExpression = (ParenthesizedExpressionSyntax)expressionAccessed; var cast = parenthesizedExpression.Expression as CastExpressionSyntax; if (cast == null) return false; var catTypeSymbol = semanticModel.GetTypeInfo(cast.Type).Type; @@ -149,8 +238,6 @@ private static bool IsCorrectDispose(ExpressionStatementSyntax expressionStateme } else return false; if (memberSymbol == null || !memberSymbol.Equals(identitySymbol)) return false; - var memberAccessed = memberAccess.Name as IdentifierNameSyntax; - if (memberAccessed == null) return false; if (memberAccessed.Identifier.Text != "Dispose" || memberAccessed.Arity != 0) return false; var methodSymbol = semanticModel.GetSymbolInfo(memberAccessed).Symbol as IMethodSymbol; if (methodSymbol == null) return false; diff --git a/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedCodeFixProvider.cs b/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedCodeFixProvider.cs index 540f50796..084ebe27d 100644 --- a/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedCodeFixProvider.cs @@ -20,6 +20,7 @@ public class DisposableVariableNotDisposedCodeFixProvider : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId()); + public readonly static string MessageFormat = "Dispose object: '{0}'"; public sealed override FixAllProvider GetFixAllProvider() => DisposableVariableNotDisposedFixAllProvider.Instance; @@ -27,6 +28,7 @@ public class DisposableVariableNotDisposedCodeFixProvider : CodeFixProvider public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) { var diagnostic = context.Diagnostics.First(); + if (diagnostic.Properties.ContainsKey(DisposableVariableNotDisposedAnalyzer.cantFix)) return Task.FromResult(0); var title = string.Format(MessageFormat, diagnostic.Properties["typeName"]); context.RegisterCodeFix(CodeAction.Create(title, c => CreateUsingAsync(context.Document, diagnostic, c), nameof(DisposableVariableNotDisposedCodeFixProvider)), diagnostic); return Task.FromResult(0); @@ -46,60 +48,124 @@ private static async Task CreateUsingAsync(Document document, Diagnost public static SyntaxNode CreateUsing(SyntaxNode root, ObjectCreationExpressionSyntax objectCreation, SemanticModel semanticModel) { SyntaxNode newRoot; - if (objectCreation.Parent.IsKind(SyntaxKind.SimpleAssignmentExpression)) + var originalNode = objectCreation; + var topSyntaxNode = (SyntaxNode)originalNode; + + while (topSyntaxNode.Parent.IsAnyKind(SyntaxKind.ParenthesizedExpression, SyntaxKind.ConditionalExpression, SyntaxKind.CastExpression)) + topSyntaxNode = topSyntaxNode.Parent; + + + if (topSyntaxNode.Parent.IsKind(SyntaxKind.SimpleAssignmentExpression)) { - var assignmentExpression = (AssignmentExpressionSyntax)objectCreation.Parent; - var statement = assignmentExpression.Parent as ExpressionStatementSyntax; - var identitySymbol = (ILocalSymbol)semanticModel.GetSymbolInfo(assignmentExpression.Left).Symbol; - newRoot = UsedOutsideParentBlock(semanticModel, statement, identitySymbol) - ? CreateRootAddingDisposeToEndOfMethod(root, statement, identitySymbol) - : CreateRootWithUsing(root, statement, u => u.WithExpression(assignmentExpression)); + var assignmentExpression = (AssignmentExpressionSyntax)topSyntaxNode.Parent; + newRoot = CreateRootWithUsingFromSimpleAssigmentExpression(root, semanticModel, assignmentExpression); } - else if (objectCreation.Parent.IsKind(SyntaxKind.EqualsValueClause) && objectCreation.Parent.Parent.IsKind(SyntaxKind.VariableDeclarator)) + else if (topSyntaxNode.Parent.IsKind(SyntaxKind.EqualsValueClause) && topSyntaxNode.Parent.Parent.IsKind(SyntaxKind.VariableDeclarator)) { - var variableDeclarator = (VariableDeclaratorSyntax)objectCreation.Parent.Parent; - var variableDeclaration = (VariableDeclarationSyntax)variableDeclarator.Parent; - var statement = (LocalDeclarationStatementSyntax)variableDeclaration.Parent; - newRoot = CreateRootWithUsing(root, statement, u => u.WithDeclaration(variableDeclaration)); + var variableDeclarator = (VariableDeclaratorSyntax)topSyntaxNode.Parent.Parent; + newRoot = CreateRootWithUsingFromVaribleDeclaration(root, variableDeclarator); } - else if (objectCreation.Parent.IsKind(SyntaxKind.Argument)) + else if (topSyntaxNode.Parent.IsKind(SyntaxKind.Argument)) { - var identifierName = GetIdentifierName(objectCreation, semanticModel); - - var variableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.IdentifierName(@"var")) - .WithVariables(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier(identifierName)) - .WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.Token(SyntaxKind.EqualsToken), objectCreation)))); - - var arg = objectCreation.Parent as ArgumentSyntax; - var args = objectCreation.Parent.Parent as ArgumentListSyntax; - var newArgs = args.ReplaceNode(arg, arg.WithExpression(SyntaxFactory.IdentifierName(identifierName))); - - StatementSyntax statement = objectCreation.FirstAncestorOfType(); - if (statement != null) - { - var exprStatement = statement.ReplaceNode(args, newArgs); - var newUsingStatment = CreateUsingStatement(exprStatement, SyntaxFactory.Block(exprStatement)) - .WithDeclaration(variableDeclaration); - return root.ReplaceNode(statement, newUsingStatment); - } - - statement = (StatementSyntax)objectCreation.Ancestors().First(node => node is StatementSyntax); - var newStatement = statement.ReplaceNode(args, newArgs); - var statementsForUsing = new[] { newStatement }.Concat(GetChildStatementsAfter(statement)); - var usingBlock = SyntaxFactory.Block(statementsForUsing); - var usingStatement = CreateUsingStatement(newStatement, usingBlock) - .WithDeclaration(variableDeclaration); - var statementsToReplace = new List { statement }; - statementsToReplace.AddRange(statementsForUsing.Skip(1)); - newRoot = root.ReplaceNodes(statementsToReplace, (node, _) => node.Equals(statement) ? usingStatement : null); + var identifierName = GetIdentifierName(originalNode, semanticModel); + var childOfArgumentNode = topSyntaxNode; + + newRoot = CreateRootWithUsingFromArgument(root, (ExpressionSyntax)childOfArgumentNode, identifierName); + } + else if (topSyntaxNode.Parent.IsKind(SyntaxKind.SimpleMemberAccessExpression)) + { + var newVariableName = originalNode.Type.ToString(); + var accessedNode = topSyntaxNode; + newRoot = CreatRootWithUsingFromMemberAccessedNode(root, semanticModel, ref newVariableName, (ExpressionSyntax)accessedNode); } else { - newRoot = CreateRootWithUsing(root, (ExpressionStatementSyntax)objectCreation.Parent, u => u.WithExpression(objectCreation)); + newRoot = CreateRootWithUsing(root, (ExpressionStatementSyntax)topSyntaxNode.Parent, u => u.WithExpression((ExpressionSyntax)topSyntaxNode)); } return newRoot; } + private static SyntaxNode CreatRootWithUsingFromMemberAccessedNode(SyntaxNode root, SemanticModel semanticModel, ref string newVariableName, ExpressionSyntax accessedNode) + { + SyntaxNode newRoot; + var memberAccessStatement = accessedNode.Parent; + var newVariableNameParts = newVariableName.Split('.'); + newVariableName = newVariableNameParts[newVariableNameParts.Length - 1].ToLowerCaseFirstLetter(); + var parentStatement = memberAccessStatement.FirstAncestorOrSelfThatIsAStatement(); + var originalName = newVariableName; + for (int nameIncrement = 1; ; nameIncrement++) + { + var speculativeSymbol = semanticModel.GetSpeculativeSymbolInfo(parentStatement.GetLocation().SourceSpan.Start, SyntaxFactory.IdentifierName(newVariableName), SpeculativeBindingOption.BindAsExpression); + if (speculativeSymbol.Symbol == null) break; + newVariableName = originalName + nameIncrement; + } + var newVariable = SyntaxFactory.LocalDeclarationStatement(SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("var"), + SyntaxFactory.SeparatedList(new[] { + SyntaxFactory.VariableDeclarator(newVariableName).WithInitializer(SyntaxFactory.EqualsValueClause(accessedNode)) + }))); + newRoot = root.TrackNodes(parentStatement, accessedNode); + newRoot = newRoot.ReplaceNode(newRoot.GetCurrentNode(accessedNode), SyntaxFactory.IdentifierName(newVariableName)); + var newTrackedParentStatement = newRoot.GetCurrentNode(parentStatement); + newRoot = newRoot.InsertNodesBefore(newTrackedParentStatement, new[] { newVariable }); + var statement = (LocalDeclarationStatementSyntax)newRoot.GetCurrentNode(parentStatement).GetPreviousStatement(); + var variableDeclaration = statement.Declaration; + var variableDeclarator = variableDeclaration.Variables.First(); + newRoot = CreateRootWithUsing(newRoot, statement, u => u.WithDeclaration(variableDeclaration.WithoutLeadingTrivia())); + return newRoot; + } + + private static SyntaxNode CreateRootWithUsingFromVaribleDeclaration(SyntaxNode root, VariableDeclaratorSyntax variableDeclarator) + { + SyntaxNode newRoot; + var variableDeclaration = (VariableDeclarationSyntax)variableDeclarator.Parent; + var statement = (LocalDeclarationStatementSyntax)variableDeclaration.Parent; + newRoot = CreateRootWithUsing(root, statement, u => u.WithDeclaration(variableDeclaration.WithoutLeadingTrivia())); + return newRoot; + } + + private static SyntaxNode CreateRootWithUsingFromSimpleAssigmentExpression(SyntaxNode root, SemanticModel semanticModel, AssignmentExpressionSyntax assignmentExpression) + { + SyntaxNode newRoot; + var statement = assignmentExpression.Parent as ExpressionStatementSyntax; + var identitySymbol = (ILocalSymbol)semanticModel.GetSymbolInfo(assignmentExpression.Left).Symbol; + newRoot = UsedOutsideParentBlock(semanticModel, statement, identitySymbol) + ? CreateRootAddingDisposeToEndOfMethod(root, statement, identitySymbol) + : CreateRootWithUsing(root, statement, u => u.WithExpression(assignmentExpression)); + return newRoot; + } + + private static SyntaxNode CreateRootWithUsingFromArgument(SyntaxNode root, ExpressionSyntax childOfArgumentNode, string identifierName) + { + var arg = childOfArgumentNode.Parent as ArgumentSyntax; + + var variableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.IdentifierName(@"var")) + .WithVariables(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier(identifierName)) + .WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.Token(SyntaxKind.EqualsToken), childOfArgumentNode)))); + + + var args = arg.Parent as ArgumentListSyntax; + var newArgs = args.ReplaceNode(arg, arg.WithExpression(SyntaxFactory.IdentifierName(identifierName))); + + StatementSyntax statement = childOfArgumentNode.FirstAncestorOfType(); + if (statement != null) + { + var exprStatement = statement.ReplaceNode(args, newArgs); + var newUsingStatment = CreateUsingStatement(exprStatement, SyntaxFactory.Block(exprStatement)) + .WithDeclaration(variableDeclaration); + return root.ReplaceNode(statement, newUsingStatment); + } + + statement = (StatementSyntax)childOfArgumentNode.Ancestors().First(node => node is StatementSyntax); + var newStatement = statement.ReplaceNode(args, newArgs); + var statementsForUsing = new[] { newStatement }.Concat(GetChildStatementsAfter(statement)); + var usingBlock = SyntaxFactory.Block(statementsForUsing); + var usingStatement = CreateUsingStatement(newStatement, usingBlock) + .WithDeclaration(variableDeclaration); + var statementsToReplace = new List { statement }; + statementsToReplace.AddRange(statementsForUsing.Skip(1)); + return root.ReplaceNodes(statementsToReplace, (node, _) => node.Equals(statement) ? usingStatement : null); + } + private static string GetIdentifierName(ObjectCreationExpressionSyntax objectCreation, SemanticModel semanticModel) { var identifierName = "disposableObject"; @@ -108,12 +174,12 @@ private static string GetIdentifierName(ObjectCreationExpressionSyntax objectCre if (type.IsKind(SyntaxKind.QualifiedName)) { var name = (QualifiedNameSyntax)type; - identifierName = LowerCaseFirstLetter(name.Right.Identifier.ValueText); + identifierName = name.Right.Identifier.ValueText.ToLowerCaseFirstLetter(); } else if (type is SimpleNameSyntax) { var name = (SimpleNameSyntax)type; - identifierName = LowerCaseFirstLetter(name.Identifier.ValueText); + identifierName = name.Identifier.ValueText.ToLowerCaseFirstLetter(); } var confilctingNames = from symbol in semanticModel.LookupSymbols(objectCreation.SpanStart) @@ -126,8 +192,6 @@ private static string GetIdentifierName(ObjectCreationExpressionSyntax objectCre return identifierName + (identifierPostFix == 0 ? "" : identifierPostFix.ToString()); } - private static string LowerCaseFirstLetter(string name) => char.ToLowerInvariant(name[0]) + name.Substring(1); - private static SyntaxNode CreateRootAddingDisposeToEndOfMethod(SyntaxNode root, ExpressionStatementSyntax statement, ILocalSymbol identitySymbol) { var method = statement.FirstAncestorOrSelf(); diff --git a/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedFixAllProvider.cs b/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedFixAllProvider.cs index b52ca3681..d34c097b0 100644 --- a/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedFixAllProvider.cs +++ b/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedFixAllProvider.cs @@ -11,7 +11,7 @@ public sealed class DisposableVariableNotDisposedFixAllProvider : FixAllProvider { private static readonly SyntaxAnnotation disposeAnnotation = new SyntaxAnnotation(nameof(DisposableVariableNotDisposedFixAllProvider)); private DisposableVariableNotDisposedFixAllProvider() { } - public static DisposableVariableNotDisposedFixAllProvider Instance = new DisposableVariableNotDisposedFixAllProvider(); + public static readonly DisposableVariableNotDisposedFixAllProvider Instance = new DisposableVariableNotDisposedFixAllProvider(); public override Task GetFixAsync(FixAllContext fixAllContext) { switch (fixAllContext.Scope) @@ -25,8 +25,9 @@ public override Task GetFixAsync(FixAllContext fixAllContext) case FixAllScope.Solution: return Task.FromResult(CodeAction.Create(DisposableVariableNotDisposedCodeFixProvider.MessageFormat, ct => GetFixedSolutionAsync(fixAllContext))); + default: + return null; } - return null; } private async static Task GetFixedSolutionAsync(FixAllContext fixAllContext) diff --git a/src/CSharp/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeAnalyzer.cs b/src/CSharp/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeAnalyzer.cs index 15434698c..b70f8d735 100644 --- a/src/CSharp/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeAnalyzer.cs @@ -1,8 +1,10 @@ -using Microsoft.CodeAnalysis; +using System; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; using System.Linq; +using Microsoft.CodeAnalysis.CSharp; namespace CodeCracker.CSharp.Usage { @@ -15,7 +17,7 @@ public class DisposablesShouldCallSuppressFinalizeAnalyzer : DiagnosticAnalyzer const string Description = "Classes implementing IDisposable should call the GC.SuppressFinalize method in their " + "finalize method to avoid any finalizer from being called.\r\n" + "This rule should be followed even if the class doesn't have a finalizer as a derived class could have one."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.DisposablesShouldCallSuppressFinalize.ToDiagnosticId(), Title, MessageFormat, @@ -28,39 +30,51 @@ public class DisposablesShouldCallSuppressFinalizeAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) => - context.RegisterSymbolAction(Analyze, SymbolKind.NamedType); + context.RegisterSyntaxNodeAction(LanguageVersion.CSharp6, Analyze, SyntaxKind.MethodDeclaration); - private static void Analyze(SymbolAnalysisContext context) + private static void Analyze(SyntaxNodeAnalysisContext context) { if (context.IsGenerated()) return; - var symbol = (INamedTypeSymbol)context.Symbol; + + var semanticModel = context.SemanticModel; + var method = (MethodDeclarationSyntax)context.Node; + if (method.Body == null && method.ExpressionBody == null) return; + + var methodSymbol = semanticModel.GetDeclaredSymbol(method); + var isImplicitDispose = methodSymbol.ToString().Contains($"{methodSymbol.ContainingType.Name}.Dispose("); + var isExplicitDispose = + methodSymbol.ExplicitInterfaceImplementations.Any(i => i.ToString() == "System.IDisposable.Dispose()"); + + if (!isImplicitDispose && !isExplicitDispose) + return; + + if (methodSymbol.Parameters != null && methodSymbol.Parameters.Length > 0) + return; + + var symbol = methodSymbol.ContainingType; if (symbol.TypeKind != TypeKind.Class) return; if (!symbol.Interfaces.Any(i => i.SpecialType == SpecialType.System_IDisposable)) return; if (symbol.IsSealed && !ContainsUserDefinedFinalizer(symbol)) return; if (!ContainsNonPrivateConstructors(symbol)) return; - var disposeMethod = FindDisposeMethod(symbol); - if (disposeMethod == null) return; - var syntaxTree = disposeMethod.DeclaringSyntaxReferences[0]?.GetSyntax(); - var statements = ((MethodDeclarationSyntax)syntaxTree)?.Body?.Statements.OfType(); - if (statements != null) + var expressions = method.Body != null + ? method.Body?.DescendantNodes().OfType().Select(e => e.Expression) + : new[] { method.ExpressionBody.Expression }; + foreach (var expression in expressions) { - foreach (var statement in statements) - { - var invocation = statement.Expression as InvocationExpressionSyntax; - var method = invocation?.Expression as MemberAccessExpressionSyntax; - var identifierSyntax = method?.Expression as IdentifierNameSyntax; - if (identifierSyntax != null && identifierSyntax.Identifier.ToString() == "GC" && method.Name.ToString() == "SuppressFinalize") - return; - } - } - context.ReportDiagnostic(Diagnostic.Create(Rule, disposeMethod.Locations[0], symbol.Name)); - } + var suppress = (expression as InvocationExpressionSyntax)?.Expression as MemberAccessExpressionSyntax; - private static ISymbol FindDisposeMethod(INamedTypeSymbol symbol) - { - return symbol.GetMembers().Where(x => x.ToString().Contains($"{x.ContainingType.Name}.Dispose(")).Cast() - .FirstOrDefault(m => m.Parameters == null || m.Parameters.Length == 0); + if (suppress?.Name.ToString() != "SuppressFinalize") + continue; + + var containingType = semanticModel.GetSymbolInfo(suppress.Expression).Symbol as INamedTypeSymbol; + if (containingType?.ContainingNamespace.Name != "System") + continue; + + if (containingType.Name == "GC") + return; + } + context.ReportDiagnostic(Diagnostic.Create(Rule, methodSymbol.Locations[0], symbol.Name)); } public static bool ContainsUserDefinedFinalizer(INamedTypeSymbol symbol) @@ -78,7 +92,7 @@ public static bool ContainsNonPrivateConstructors(INamedTypeSymbol symbol) .Any(m => m.MetadataName == ".ctor" && m.DeclaredAccessibility != Accessibility.Private); } - private static bool IsNestedPrivateType(INamedTypeSymbol symbol) + private static bool IsNestedPrivateType(ISymbol symbol) { if (symbol == null) return false; @@ -89,4 +103,4 @@ private static bool IsNestedPrivateType(INamedTypeSymbol symbol) return IsNestedPrivateType(symbol.ContainingType); } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeCodeFixProvider.cs b/src/CSharp/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeCodeFixProvider.cs index e1c0fabab..19d0e7027 100644 --- a/src/CSharp/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeCodeFixProvider.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Simplification; namespace CodeCracker.CSharp.Usage { @@ -28,21 +29,57 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) return Task.FromResult(0); } - private async static Task AddSuppressFinalizeAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + private async static Task AddSuppressFinalizeAsync( + Document document, + Diagnostic diagnostic, + CancellationToken cancellationToken + ) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var method = root.FindToken(diagnostic.Location.SourceSpan.Start).Parent.AncestorsAndSelf().OfType().First(); + + var startLocation = diagnostic.Location.SourceSpan.Start; + var token = root.FindToken(startLocation); + var method = token.Parent.AncestorsAndSelf().OfType().First(); + + var systemGc = SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName("System"), + SyntaxFactory.IdentifierName("GC") + ); + + var suppressFinalize = SyntaxFactory.IdentifierName("SuppressFinalize"); + + var arguments = SyntaxFactory.ArgumentList() + .AddArguments(SyntaxFactory.Argument(SyntaxFactory.ThisExpression())); + + var suppressFinalizeCall = + SyntaxFactory.ExpressionStatement( + SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + systemGc, + suppressFinalize + ), + arguments + )); + + var modifiedMethod = method; + if (method.ExpressionBody != null) + { + var bodiedExpression = method.ExpressionBody; + var bodiedStatement = SyntaxFactory.ExpressionStatement(bodiedExpression.Expression); + modifiedMethod = method.RemoveNode(bodiedExpression, SyntaxRemoveOptions.KeepNoTrivia) + .AddBodyStatements(bodiedStatement) + .WithSemicolonToken(SyntaxFactory.MissingToken(SyntaxKind.SemicolonToken) + .WithLeadingTrivia(modifiedMethod.SemicolonToken.LeadingTrivia) + .WithTrailingTrivia(modifiedMethod.SemicolonToken.TrailingTrivia)); + } + modifiedMethod = modifiedMethod.AddBodyStatements(suppressFinalizeCall) + .WithAdditionalAnnotations(Simplifier.Annotation) + .WithAdditionalAnnotations(Formatter.Annotation); + return document - .WithSyntaxRoot(root - .ReplaceNode(method, method.AddBodyStatements( - SyntaxFactory.ExpressionStatement( - SyntaxFactory.InvocationExpression( - SyntaxFactory.MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - SyntaxFactory.IdentifierName("GC"), - SyntaxFactory.IdentifierName("SuppressFinalize")), - SyntaxFactory.ArgumentList().AddArguments(SyntaxFactory.Argument(SyntaxFactory.ThisExpression()))))) - .WithAdditionalAnnotations(Formatter.Annotation))); + .WithSyntaxRoot(root.ReplaceNode(method, modifiedMethod)); } } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Usage/IPAddressAnalyzer.cs b/src/CSharp/CodeCracker/Usage/IPAddressAnalyzer.cs index 5513b0b0f..ba2b3b03b 100644 --- a/src/CSharp/CodeCracker/Usage/IPAddressAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/IPAddressAnalyzer.cs @@ -11,15 +11,14 @@ namespace CodeCracker.CSharp.Usage [DiagnosticAnalyzer(LanguageNames.CSharp)] public class IPAddressAnalyzer : DiagnosticAnalyzer { - internal const string Title = "Your IP Address syntax is wrong."; + internal const string Title = "Your IP Address syntax is incorrect."; internal const string MessageFormat = "{0}"; internal const string Category = SupportedCategories.Usage; private const string Description = - "This diagnostic checks the IP Address string and triggers if the parsing fail " - + "by throwing an exception."; + "An error was found parsing the IP Address string."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.IPAddress.ToDiagnosticId(), Title, MessageFormat, @@ -41,9 +40,12 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) "System.Net.IPAddress.Parse(string)", args => { + if (!(args[0] is string)) { + return; + } parseMethodInfo.Value.Invoke(null, new[] { args[0].ToString() }); } - ); + ); var checker = new MethodChecker(context, Rule); checker.AnalyzeMethod(method); } @@ -53,4 +55,4 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) private static readonly Lazy parseMethodInfo = new Lazy(() => objectType.Value.GetRuntimeMethod("Parse", new[] { typeof(string) })); } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Usage/IfReturnTrueAnalyzer.cs b/src/CSharp/CodeCracker/Usage/IfReturnTrueAnalyzer.cs index 645073e91..c03121553 100644 --- a/src/CSharp/CodeCracker/Usage/IfReturnTrueAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/IfReturnTrueAnalyzer.cs @@ -12,10 +12,9 @@ public class IfReturnTrueAnalyzer : DiagnosticAnalyzer internal const string Title = "Return Condition directly"; internal const string Message = "{0}"; internal const string Category = SupportedCategories.Usage; - const string Description = "Using an if/else to return true/false depending on the condition isn't useful.\r\n" - + "As the condition is already a boolean it can be returned directly"; + const string Description = "Using an if/else statement to return a boolean can be replaced by directly returning a boolean."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.IfReturnTrue.ToDiagnosticId(), Title, Message, @@ -47,9 +46,10 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) (returnIf.Expression is LiteralExpressionSyntax && returnIf.Expression.IsKind(SyntaxKind.FalseLiteralExpression) && returnElse.Expression is LiteralExpressionSyntax && returnElse.Expression.IsKind(SyntaxKind.TrueLiteralExpression))) { - var diagnostic = Diagnostic.Create(Rule, ifStatement.IfKeyword.GetLocation(), "You should return directly."); + var diagnostic = Diagnostic.Create(Rule, ifStatement.IfKeyword.GetLocation(), + "You should return the boolean directly."); context.ReportDiagnostic(diagnostic); } } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Usage/JsonNetAnalyzer.cs b/src/CSharp/CodeCracker/Usage/JsonNetAnalyzer.cs index 88fe95647..d69c802dc 100644 --- a/src/CSharp/CodeCracker/Usage/JsonNetAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/JsonNetAnalyzer.cs @@ -17,7 +17,7 @@ public class JsonNetAnalyzer : DiagnosticAnalyzer const string Description = "This diagnostic checks the json string and triggers if the parsing fail " + "by throwing an exception."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.JsonNet.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Usage/NoPrivateReadonlyFieldAnalyzer.cs b/src/CSharp/CodeCracker/Usage/NoPrivateReadonlyFieldAnalyzer.cs index 31435b436..f87394915 100644 --- a/src/CSharp/CodeCracker/Usage/NoPrivateReadonlyFieldAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/NoPrivateReadonlyFieldAnalyzer.cs @@ -16,7 +16,7 @@ public class NoPrivateReadonlyFieldAnalyzer : DiagnosticAnalyzer internal const string Category = SupportedCategories.Usage; const string Description = "A field that is only assigned on the constructor can be made readonly."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.NoPrivateReadonlyField.ToDiagnosticId(), Title, Message, @@ -36,11 +36,19 @@ public override void Initialize(AnalysisContext analysisContext) var assignedFields = new List(); compilationStartContext.RegisterSyntaxNodeAction( - syntaxNodeAnalysisContext => CaptureCandidateFields(syntaxNodeAnalysisContext.Node as FieldDeclarationSyntax, syntaxNodeAnalysisContext.SemanticModel, candidateFields), + syntaxNodeAnalysisContext => + { + if (syntaxNodeAnalysisContext.IsGenerated()) return; + CaptureCandidateFields(syntaxNodeAnalysisContext.Node as FieldDeclarationSyntax, syntaxNodeAnalysisContext.SemanticModel, candidateFields); + }, SyntaxKind.FieldDeclaration); compilationStartContext.RegisterSyntaxNodeAction( - syntaxNodeAnalysisContext => CaptureAssignedFields(syntaxNodeAnalysisContext.Node as TypeDeclarationSyntax, syntaxNodeAnalysisContext.SemanticModel, assignedFields), + syntaxNodeAnalysisContext => + { + if (syntaxNodeAnalysisContext.IsGenerated()) return; + CaptureAssignedFields(syntaxNodeAnalysisContext.Node as TypeDeclarationSyntax, syntaxNodeAnalysisContext.SemanticModel, assignedFields); + }, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration); compilationStartContext.RegisterCompilationEndAction(compilationEndContext => diff --git a/src/CSharp/CodeCracker/Usage/ReadOnlyComplexTypesAnalyzer.cs b/src/CSharp/CodeCracker/Usage/ReadOnlyComplexTypesAnalyzer.cs new file mode 100644 index 000000000..089013936 --- /dev/null +++ b/src/CSharp/CodeCracker/Usage/ReadOnlyComplexTypesAnalyzer.cs @@ -0,0 +1,69 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; +using System.Linq; +using System.Collections.Generic; +using System; + +namespace CodeCracker.CSharp.Usage +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ReadOnlyComplexTypesAnalyzer : DiagnosticAnalyzer + { + internal const string Title = "Complex fields must be readonly"; + internal const string Message = "Make '{0}' readonly"; + internal const string Category = SupportedCategories.Usage; + const string Description = "Complex fields must be readonly"; + + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( + DiagnosticId.ReadOnlyComplexTypes.ToDiagnosticId(), + Title, + Message, + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: false, + description: Description, + helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.ReadOnlyComplexTypes)); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, + new[] { SyntaxKind.FieldDeclaration }); + + private static void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + if (context.IsGenerated()) return; + var fieldDeclaration = context.Node as FieldDeclarationSyntax; + var variable = fieldDeclaration?.Declaration.Variables.LastOrDefault(); + if (variable?.Initializer == null) return; + var semanticModel = context.SemanticModel; + var fieldSymbol = semanticModel.GetDeclaredSymbol(variable) as IFieldSymbol; + if (!IsComplexValueType(semanticModel, fieldDeclaration)) return; + if (!CanBeMadeReadonly(fieldSymbol)) return; + ReportDiagnostic(context, variable, variable.Initializer.Value); + } + private static bool IsComplexValueType(SemanticModel semanticModel, FieldDeclarationSyntax fieldDeclaration) + { + var fieldTypeName = fieldDeclaration.Declaration.Type; + var fieldType = semanticModel.GetTypeInfo(fieldTypeName).ConvertedType; + return fieldType.IsValueType && !(fieldType.TypeKind == TypeKind.Enum || fieldType.IsPrimitive()); + } + + private static bool CanBeMadeReadonly(IFieldSymbol fieldSymbol) + { + return (fieldSymbol.DeclaredAccessibility == Accessibility.NotApplicable + || fieldSymbol.DeclaredAccessibility == Accessibility.Private) + && !fieldSymbol.IsReadOnly + && !fieldSymbol.IsConst; + } + + private static void ReportDiagnostic(SyntaxNodeAnalysisContext context, VariableDeclaratorSyntax variable, ExpressionSyntax initializerValue) + { + var props = new Dictionary { { "identifier", variable.Identifier.Text } }.ToImmutableDictionary(); + var diag = Diagnostic.Create(Rule, variable.GetLocation(), props, initializerValue.ToString()); + context.ReportDiagnostic(diag); + } + } +} diff --git a/src/CSharp/CodeCracker/Usage/ReadonlyFieldAnalyzer.cs b/src/CSharp/CodeCracker/Usage/ReadonlyFieldAnalyzer.cs index 75a3f0ffe..1fa957957 100644 --- a/src/CSharp/CodeCracker/Usage/ReadonlyFieldAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/ReadonlyFieldAnalyzer.cs @@ -2,9 +2,9 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Collections.Generic; namespace CodeCracker.CSharp.Usage { @@ -16,7 +16,7 @@ public class ReadonlyFieldAnalyzer : DiagnosticAnalyzer internal const string Category = SupportedCategories.Usage; const string Description = "A field that is only assigned on the constructor can be made readonly."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.ReadonlyField.ToDiagnosticId(), Title, Message, @@ -36,6 +36,19 @@ private static void AnalyzeCompilation(CompilationStartAnalysisContext compilati compilationStartAnalysisContext.RegisterSyntaxTreeAction(context => AnalyzeTree(context, compilation)); } + private struct MethodKindComparer : IComparer + { + public int Compare(MethodKind x, MethodKind y) => + x - y == 0 + ? 0 + : (x == MethodKind.Constructor + ? 1 + : (y == MethodKind.Constructor + ? -1 + : x - y)); + } + private static readonly MethodKindComparer methodKindComparer = new MethodKindComparer(); + private static void AnalyzeTree(SyntaxTreeAnalysisContext context, Compilation compilation) { if (context.IsGenerated()) return; @@ -51,6 +64,7 @@ private static void AnalyzeTree(SyntaxTreeAnalysisContext context, Compilation c var typeSymbol = semanticModel.GetDeclaredSymbol(type); if (typeSymbol == null) continue; var methods = typeSymbol.GetAllMethodsIncludingFromInnerTypes(); + methods = methods.OrderByDescending(m => m.MethodKind, methodKindComparer).ToList(); foreach (var method in methods) { foreach (var syntaxReference in method.DeclaringSyntaxReferences) @@ -58,17 +72,35 @@ private static void AnalyzeTree(SyntaxTreeAnalysisContext context, Compilation c var syntaxRefSemanticModel = syntaxReference.SyntaxTree.Equals(context.Tree) ? semanticModel : compilation.GetSemanticModel(syntaxReference.SyntaxTree); - var assignments = syntaxReference.GetSyntax().DescendantNodes().OfType(); - foreach (var assignment in assignments) + var descendants = syntaxReference.GetSyntax().DescendantNodes().ToList(); + var argsWithRefOrOut = descendants.OfType().Where(a => a.RefOrOutKeyword != null); + foreach (var argWithRefOrOut in argsWithRefOrOut) { - var fieldSymbol = syntaxRefSemanticModel.GetSymbolInfo(assignment.Left).Symbol as IFieldSymbol; + var fieldSymbol = syntaxRefSemanticModel.GetSymbolInfo(argWithRefOrOut.Expression).Symbol as IFieldSymbol; if (fieldSymbol == null) continue; - if (method.MethodKind == MethodKind.StaticConstructor && fieldSymbol.IsStatic) - AddVariableThatWasSkippedBeforeBecauseItLackedAInitializer(variablesToMakeReadonly, fieldSymbol); - else if (method.MethodKind == MethodKind.Constructor && !fieldSymbol.IsStatic) - AddVariableThatWasSkippedBeforeBecauseItLackedAInitializer(variablesToMakeReadonly, fieldSymbol); - else - RemoveVariableThatHasAssignment(variablesToMakeReadonly, fieldSymbol); + variablesToMakeReadonly.Remove(fieldSymbol); + } + var assignments = descendants.OfKind(SyntaxKind.SimpleAssignmentExpression, + SyntaxKind.AddAssignmentExpression, SyntaxKind.AndAssignmentExpression, SyntaxKind.DivideAssignmentExpression, + SyntaxKind.ExclusiveOrAssignmentExpression, SyntaxKind.LeftShiftAssignmentExpression, SyntaxKind.ModuloAssignmentExpression, + SyntaxKind.MultiplyAssignmentExpression, SyntaxKind.OrAssignmentExpression, SyntaxKind.RightShiftAssignmentExpression, + SyntaxKind.SubtractAssignmentExpression); + foreach (AssignmentExpressionSyntax assignment in assignments) + { + var fieldSymbol = syntaxRefSemanticModel.GetSymbolInfo(assignment.Left).Symbol as IFieldSymbol; + VerifyVariable(variablesToMakeReadonly, method, syntaxRefSemanticModel, assignment, fieldSymbol); + } + var postFixUnaries = descendants.OfKind(SyntaxKind.PostIncrementExpression, SyntaxKind.PostDecrementExpression); + foreach (PostfixUnaryExpressionSyntax postFixUnary in postFixUnaries) + { + var fieldSymbol = syntaxRefSemanticModel.GetSymbolInfo(postFixUnary.Operand).Symbol as IFieldSymbol; + VerifyVariable(variablesToMakeReadonly, method, syntaxRefSemanticModel, postFixUnary, fieldSymbol); + } + var preFixUnaries = descendants.OfKind(SyntaxKind.PreDecrementExpression, SyntaxKind.PreIncrementExpression); + foreach (PrefixUnaryExpressionSyntax preFixUnary in preFixUnaries) + { + var fieldSymbol = syntaxRefSemanticModel.GetSymbolInfo(preFixUnary.Operand).Symbol as IFieldSymbol; + VerifyVariable(variablesToMakeReadonly, method, syntaxRefSemanticModel, preFixUnary, fieldSymbol); } } } @@ -81,11 +113,43 @@ private static void AnalyzeTree(SyntaxTreeAnalysisContext context, Compilation c } } - private static void AddVariableThatWasSkippedBeforeBecauseItLackedAInitializer(Dictionary variablesToMakeReadonly, IFieldSymbol fieldSymbol) + private static void VerifyVariable(Dictionary variablesToMakeReadonly, IMethodSymbol method, + SemanticModel syntaxRefSemanticModel, SyntaxNode node, IFieldSymbol fieldSymbol) + { + if (fieldSymbol == null) return; + if (!CanBeMadeReadonly(fieldSymbol)) return; + if (!HasAssignmentInLambda(node) + && ((method.MethodKind == MethodKind.StaticConstructor && fieldSymbol.IsStatic) + || (method.MethodKind == MethodKind.Constructor && !fieldSymbol.IsStatic))) + AddVariableThatWasSkippedBeforeBecauseItLackedAInitializer(variablesToMakeReadonly, fieldSymbol, node, syntaxRefSemanticModel); + else + RemoveVariableThatHasAssignment(variablesToMakeReadonly, fieldSymbol); + } + + private static bool HasAssignmentInLambda(SyntaxNode assignment) { - if (!fieldSymbol.IsReadOnly && !variablesToMakeReadonly.Keys.Contains(fieldSymbol)) + var parent = assignment.Parent; + while (parent != null) + { + if (parent is AnonymousFunctionExpressionSyntax) + return true; + parent = parent.Parent; + } + return false; + } + + private static void AddVariableThatWasSkippedBeforeBecauseItLackedAInitializer(Dictionary variablesToMakeReadonly, IFieldSymbol fieldSymbol, SyntaxNode assignment, SemanticModel semanticModel) + { + if (!fieldSymbol.IsReadOnly && !variablesToMakeReadonly.Keys.Contains(fieldSymbol) && !IsComplexValueType(fieldSymbol.Type)) + { + var containingType = assignment.FirstAncestorOfKind(SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration); + if (containingType == null) return; + var containingTypeSymbol = semanticModel.GetDeclaredSymbol(containingType) as INamedTypeSymbol; + if (containingTypeSymbol == null) return; + if (!fieldSymbol.ContainingType.Equals(containingTypeSymbol)) return; foreach (var variable in fieldSymbol.DeclaringSyntaxReferences) variablesToMakeReadonly.Add(fieldSymbol, (VariableDeclaratorSyntax)variable.GetSyntax()); + } } private static void RemoveVariableThatHasAssignment(Dictionary variablesToMakeReadonly, IFieldSymbol fieldSymbol) @@ -105,8 +169,12 @@ private static Dictionary GetCandidateVa private static Dictionary GetCandidateVariables(SemanticModel semanticModel, FieldDeclarationSyntax fieldDeclaration) { var variablesToMakeReadonly = new Dictionary(); - if (fieldDeclaration == null) return variablesToMakeReadonly; - if (!CanBeMadeReadonly(fieldDeclaration)) return variablesToMakeReadonly; + if (fieldDeclaration == null || + IsComplexValueType(semanticModel, fieldDeclaration) || + !CanBeMadeReadonly(fieldDeclaration)) + { + return variablesToMakeReadonly; + } foreach (var variable in fieldDeclaration.Declaration.Variables) { if (variable.Initializer == null) continue; @@ -127,6 +195,23 @@ private static bool CanBeMadeReadonly(FieldDeclarationSyntax fieldDeclaration) || m.IsKind(SyntaxKind.ConstKeyword)); } + private static bool IsComplexValueType(SemanticModel semanticModel, FieldDeclarationSyntax fieldDeclaration) + { + var fieldTypeName = fieldDeclaration.Declaration.Type; + var fieldType = semanticModel.GetTypeInfo(fieldTypeName).ConvertedType; + return IsComplexValueType(fieldType); + } + + private static bool IsComplexValueType(ITypeSymbol fieldType) => fieldType.IsValueType && !(fieldType.TypeKind == TypeKind.Enum || fieldType.IsPrimitive()); + + private static bool CanBeMadeReadonly(IFieldSymbol fieldSymbol) + { + return (fieldSymbol.DeclaredAccessibility == Accessibility.NotApplicable + || fieldSymbol.DeclaredAccessibility == Accessibility.Private) + && !fieldSymbol.IsReadOnly + && !fieldSymbol.IsConst; + } + private static List GetTypesInRoot(SyntaxNode root) { var types = new List(); @@ -137,4 +222,4 @@ private static List GetTypesInRoot(SyntaxNode root) return types; } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Usage/ReadonlyFieldCodeFixProvider.cs b/src/CSharp/CodeCracker/Usage/ReadonlyFieldCodeFixProvider.cs index 9b0d0be1a..1b9e85a58 100644 --- a/src/CSharp/CodeCracker/Usage/ReadonlyFieldCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Usage/ReadonlyFieldCodeFixProvider.cs @@ -16,7 +16,8 @@ namespace CodeCracker.CSharp.Usage public class ReadonlyFieldCodeFixProvider : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(DiagnosticId.ReadonlyField.ToDiagnosticId()); + ImmutableArray.Create(DiagnosticId.ReadonlyField.ToDiagnosticId(), + DiagnosticId.ReadOnlyComplexTypes.ToDiagnosticId()); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; diff --git a/src/CSharp/CodeCracker/Usage/RedundantFieldAssignmentAnalyzer.cs b/src/CSharp/CodeCracker/Usage/RedundantFieldAssignmentAnalyzer.cs index 2d52923fc..7791f70cb 100644 --- a/src/CSharp/CodeCracker/Usage/RedundantFieldAssignmentAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/RedundantFieldAssignmentAnalyzer.cs @@ -16,7 +16,7 @@ public class RedundantFieldAssignmentAnalyzer : DiagnosticAnalyzer internal const string Category = SupportedCategories.Usage; const string Description = "It's recommend not to assign the default value to a field as a performance optimization."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Usage/RegexAnalyzer.cs b/src/CSharp/CodeCracker/Usage/RegexAnalyzer.cs index 99769d481..41393ac96 100644 --- a/src/CSharp/CodeCracker/Usage/RegexAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/RegexAnalyzer.cs @@ -10,13 +10,12 @@ namespace CodeCracker.CSharp.Usage [DiagnosticAnalyzer(LanguageNames.CSharp)] public class RegexAnalyzer : DiagnosticAnalyzer { - internal const string Title = "Your Regex expression is wrong"; + internal const string Title = "Your regex expression is incorrect"; internal const string MessageFormat = "{0}"; internal const string Category = SupportedCategories.Naming; - const string Description = "This diagnostic compile the Regex expression and trigger if the compilation fail " - + "by throwing an exception."; + const string Description = "There is an error in your regex expression."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.Regex.ToDiagnosticId(), Title, MessageFormat, @@ -63,4 +62,4 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) } } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Usage/RemovePrivateMethodNeverUsedAnalyzer.cs b/src/CSharp/CodeCracker/Usage/RemovePrivateMethodNeverUsedAnalyzer.cs index ef357a17e..26b80458c 100644 --- a/src/CSharp/CodeCracker/Usage/RemovePrivateMethodNeverUsedAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/RemovePrivateMethodNeverUsedAnalyzer.cs @@ -16,10 +16,10 @@ public class RemovePrivateMethodNeverUsedAnalyzer : DiagnosticAnalyzer internal const string Title = "Unused Method"; internal const string Message = "Method is not used."; internal const string Category = SupportedCategories.Usage; - const string Description = "When a private method declared does not used might bring incorrect conclusions."; + const string Description = "Unused private methods can be safely removed as they are unnecessary."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( - DiagnosticId.RemovePrivateMethodNeverUsed.ToDiagnosticId(), + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( + DiagnosticId.RemovePrivateMethodNeverUsed.ToDiagnosticId(), Title, Message, Category, @@ -40,14 +40,49 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) if (methodDeclaration.ExplicitInterfaceSpecifier != null) return; var methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodDeclaration); if (methodSymbol.DeclaredAccessibility != Accessibility.Private) return; + if (IsMethodAttributeAnException(methodDeclaration)) return; if (IsMethodUsed(methodDeclaration, context.SemanticModel)) return; if (IsMainMethodEntryPoint(methodDeclaration, context.SemanticModel)) return; if (methodDeclaration.Modifiers.Any(SyntaxKind.ExternKeyword)) return; + if (IsWinformsPropertyDefaultValueDefinitionMethod(methodDeclaration, context.SemanticModel)) return; var props = new Dictionary { { "identifier", methodDeclaration.Identifier.Text } }.ToImmutableDictionary(); var diagnostic = Diagnostic.Create(Rule, methodDeclaration.GetLocation(), props); context.ReportDiagnostic(diagnostic); } + private static bool IsMethodAttributeAnException(MethodDeclarationSyntax methodDeclaration) + { + if (methodDeclaration == null) return false; + + foreach (var attributeList in methodDeclaration.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + var identifierName = attribute.Name as IdentifierNameSyntax; + string nameText = null; + if (identifierName != null) + { + nameText = identifierName?.Identifier.Text; + } + else + { + var qualifiedName = attribute.Name as QualifiedNameSyntax; + if (qualifiedName != null) + nameText = qualifiedName.Right?.Identifier.Text; + } + if (nameText == null) continue; + if (IsExcludedAttributeName(nameText)) return true; + } + } + return false; + } + + // Some Attributes make it valid to have an unused private Method, this is a list of them + private static readonly string[] excludedAttributeNames = { "Fact", "ContractInvariantMethod", "DataMember" }; + + private static bool IsExcludedAttributeName(string attributeName) => + excludedAttributeNames.Contains(attributeName); + private static bool IsMethodUsed(MethodDeclarationSyntax methodTarget, SemanticModel semanticModel) { var typeDeclaration = methodTarget.Parent as TypeDeclarationSyntax; @@ -91,5 +126,39 @@ private static bool IsMainMethodEntryPoint(MethodDeclarationSyntax methodTarget, if (!parameterType.OriginalDefinition.ToString().Equals("String[]", StringComparison.OrdinalIgnoreCase)) return false; return true; } + + // see https://msdn.microsoft.com/en-us/library/53b8022e(v=vs.110).aspx + private static bool IsWinformsPropertyDefaultValueDefinitionMethod(MethodDeclarationSyntax methodTarget, SemanticModel semanticModel) + { + var propertyName = GetPropertyNameForWinformDefaultValueMethods(methodTarget, semanticModel); + if (string.IsNullOrWhiteSpace(propertyName)) return false; + if (!ExistsProperty(propertyName, methodTarget, semanticModel)) return false; + return true; + } + + private static string GetPropertyNameForWinformDefaultValueMethods(MethodDeclarationSyntax methodTarget, SemanticModel semanticModel) => + GetPropertyNameForMethodWithSignature(methodTarget, semanticModel, "Reset", "Void") ?? + GetPropertyNameForMethodWithSignature(methodTarget, semanticModel, "ShouldSerialize", "Boolean"); + + private static string GetPropertyNameForMethodWithSignature(MethodDeclarationSyntax methodTarget, SemanticModel semanticModel, string startsWith, string returnType) + { + var methodName = methodTarget.Identifier.Text; + if (methodName.StartsWith(startsWith)) + if (methodTarget.ParameterList.Parameters.Count == 0) + { + var returnTypeInfo = semanticModel.GetTypeInfo(methodTarget.ReturnType).Type; + if (returnTypeInfo.Name.Equals(returnType, StringComparison.OrdinalIgnoreCase)) + return methodName.Substring(startsWith.Length); ; + } + return null; + } + + private static bool ExistsProperty(string propertyName, SyntaxNode nodeInType, SemanticModel semanticModel) + { + var typeDeclaration = nodeInType.AncestorsAndSelf().OfType().FirstOrDefault(); + if (typeDeclaration == null) return false; + var propertyDeclarations = typeDeclaration.DescendantNodes().OfType(); + return propertyDeclarations.Any(pd => pd.Identifier.Text == propertyName); + } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Usage/RemoveRedundantElseClauseAnalyzer.cs b/src/CSharp/CodeCracker/Usage/RemoveRedundantElseClauseAnalyzer.cs index 3921eb943..03294eb66 100644 --- a/src/CSharp/CodeCracker/Usage/RemoveRedundantElseClauseAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/RemoveRedundantElseClauseAnalyzer.cs @@ -14,7 +14,7 @@ public class RemoveRedundantElseClauseAnalyzer : DiagnosticAnalyzer internal const string Category = SupportedCategories.Usage; const string Description = "An empty else clause only adds complexity. You may safely remove it."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.RemoveRedundantElseClause.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Usage/RemoveUnreachableCodeFixAllProvider.cs b/src/CSharp/CodeCracker/Usage/RemoveUnreachableCodeFixAllProvider.cs index 0276ea344..499cbd6b7 100644 --- a/src/CSharp/CodeCracker/Usage/RemoveUnreachableCodeFixAllProvider.cs +++ b/src/CSharp/CodeCracker/Usage/RemoveUnreachableCodeFixAllProvider.cs @@ -11,7 +11,7 @@ public sealed class RemoveUnreachableCodeFixAllProvider : FixAllProvider { private static readonly SyntaxAnnotation removeUnreachableCodeAnnotation = new SyntaxAnnotation(nameof(RemoveUnreachableCodeFixAllProvider)); private RemoveUnreachableCodeFixAllProvider() { } - public static RemoveUnreachableCodeFixAllProvider Instance = new RemoveUnreachableCodeFixAllProvider(); + public static readonly RemoveUnreachableCodeFixAllProvider Instance = new RemoveUnreachableCodeFixAllProvider(); public override Task GetFixAsync(FixAllContext fixAllContext) { switch (fixAllContext.Scope) @@ -25,8 +25,9 @@ public override Task GetFixAsync(FixAllContext fixAllContext) case FixAllScope.Solution: return Task.FromResult(CodeAction.Create(RemoveUnreachableCodeCodeFixProvider.Message, ct => GetFixedSolutionAsync(fixAllContext))); + default: + return null; } - return null; } private async static Task GetFixedSolutionAsync(FixAllContext fixAllContext) diff --git a/src/CSharp/CodeCracker/Usage/RethrowExceptionAnalyzer.cs b/src/CSharp/CodeCracker/Usage/RethrowExceptionAnalyzer.cs index a649c00b6..cf2376d77 100644 --- a/src/CSharp/CodeCracker/Usage/RethrowExceptionAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/RethrowExceptionAnalyzer.cs @@ -13,11 +13,10 @@ public class RethrowExceptionAnalyzer : DiagnosticAnalyzer internal const string Title = "Your throw does nothing"; internal const string MessageFormat = "{0}"; internal const string Category = SupportedCategories.Naming; - const string Description = "Throwing the same exception as passed to the 'catch' block lose the original " - + "stack trace and will make debugging this exception a lot more difficult.\r\n" - + "The correct way to rethrow an exception without changing it is by using 'throw' without any parameter."; + const string Description = "If a exception is caught and then thrown again the original stack trace will be lost. " + + "Instead it is best to throw the exception without using any parameters."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.RethrowException.ToDiagnosticId(), Title, MessageFormat, @@ -44,8 +43,8 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) if (catchClause == null) return; var catchExSymbol = context.SemanticModel.GetDeclaredSymbol(catchClause.Declaration); if (!catchExSymbol.Equals(exSymbol)) return; - var diagnostic = Diagnostic.Create(Rule, throwStatement.GetLocation(), "Don't throw the same exception you caught, you lose the original stack trace."); + var diagnostic = Diagnostic.Create(Rule, throwStatement.GetLocation(), "Throwing the same exception that was caught will lose the original stack trace."); context.ReportDiagnostic(diagnostic); } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Usage/SimplifyRedundantBooleanComparisonsAnalyzer.cs b/src/CSharp/CodeCracker/Usage/SimplifyRedundantBooleanComparisonsAnalyzer.cs index 07972fa73..0a149d2b5 100644 --- a/src/CSharp/CodeCracker/Usage/SimplifyRedundantBooleanComparisonsAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/SimplifyRedundantBooleanComparisonsAnalyzer.cs @@ -13,7 +13,7 @@ public class SimplifyRedundantBooleanComparisonsAnalyzer : DiagnosticAnalyzer internal const string MessageFormat = "You can remove this comparison."; internal const string Category = SupportedCategories.Usage; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.SimplifyRedundantBooleanComparisons.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Usage/SimplifyRedundantBooleanComparisonsCodeFixProvider.cs b/src/CSharp/CodeCracker/Usage/SimplifyRedundantBooleanComparisonsCodeFixProvider.cs index 68a402570..8c9ba5209 100644 --- a/src/CSharp/CodeCracker/Usage/SimplifyRedundantBooleanComparisonsCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Usage/SimplifyRedundantBooleanComparisonsCodeFixProvider.cs @@ -1,14 +1,14 @@ -using Microsoft.CodeAnalysis; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; namespace CodeCracker.CSharp.Usage { @@ -31,7 +31,11 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task RemoveRedundantComparisonAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var comparison = root.FindToken(diagnostic.Location.SourceSpan.Start).Parent.AncestorsAndSelf().OfType().First(); + var comparison = root.FindToken(diagnostic.Location.SourceSpan.Start) + .Parent.AncestorsAndSelf() + .OfType() + .First(bes => !bes.IsKind(SyntaxKind.IsExpression)); + var semanticModel = await document.GetSemanticModelAsync(cancellationToken); bool constValue; ExpressionSyntax replacer; @@ -49,9 +53,19 @@ private static async Task RemoveRedundantComparisonAsync(Document docu } - if ((!constValue && comparison.IsKind(SyntaxKind.EqualsExpression)) || (constValue && comparison.IsKind(SyntaxKind.NotEqualsExpression))) + if ((!constValue && comparison.IsKind(SyntaxKind.EqualsExpression)) || + (constValue && comparison.IsKind(SyntaxKind.NotEqualsExpression))) + { + if (comparison.Left is BinaryExpressionSyntax) + { + replacer = SyntaxFactory.ParenthesizedExpression(replacer); + } replacer = SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, replacer); - replacer = replacer.WithAdditionalAnnotations(Formatter.Annotation); + } + + replacer = replacer + .WithAdditionalAnnotations(Formatter.Annotation); + var newRoot = root.ReplaceNode(comparison, replacer); var newDocument = document.WithSyntaxRoot(newRoot); diff --git a/src/CSharp/CodeCracker/Usage/StringFormatArgsAnalyzer.cs b/src/CSharp/CodeCracker/Usage/StringFormatArgsAnalyzer.cs index 9705738a8..3213dfe6c 100644 --- a/src/CSharp/CodeCracker/Usage/StringFormatArgsAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/StringFormatArgsAnalyzer.cs @@ -15,29 +15,30 @@ public class StringFormatArgsAnalyzer : DiagnosticAnalyzer internal const string IncorrectNumberOfArgsMessage = "The number of arguments in String.Format is incorrect."; internal const string InvalidArgsReferenceMessage = "Invalid argument reference in String.Format."; internal const string Category = SupportedCategories.Usage; - const string Description = "The format argument in String.Format determines the number of argument, considering the {} inside. You should pass the correct number of arguments."; + const string Description = "The format argument in String.Format determines the number of other arguments that need to be " + + "passed into the method based on the number of curly braces {} used. The incorrect number of arguments are being passed."; - internal static DiagnosticDescriptor IncorrectNumberOfArgs = new DiagnosticDescriptor( - DiagnosticId.StringFormatArgs.ToDiagnosticId(), + internal static readonly DiagnosticDescriptor ExtraArgs = new DiagnosticDescriptor( + DiagnosticId.StringFormatArgs_ExtraArgs.ToDiagnosticId(), Title, IncorrectNumberOfArgsMessage, Category, - DiagnosticSeverity.Error, + DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description, - helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.StringFormatArgs)); + helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.StringFormatArgs_ExtraArgs)); - internal static DiagnosticDescriptor InvalidArgsReference = new DiagnosticDescriptor( - DiagnosticId.StringFormatArgs.ToDiagnosticId(), + internal static readonly DiagnosticDescriptor InvalidArgs = new DiagnosticDescriptor( + DiagnosticId.StringFormatArgs_InvalidArgs.ToDiagnosticId(), Title, InvalidArgsReferenceMessage, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description, - helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.StringFormatArgs)); + helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.StringFormatArgs_InvalidArgs)); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(IncorrectNumberOfArgs, InvalidArgsReference); + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(ExtraArgs, InvalidArgs); public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(Analyzer, SyntaxKind.InvocationExpression); @@ -49,18 +50,20 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) if (memberExpresion?.Name?.ToString() != "Format") return; var memberSymbol = context.SemanticModel.GetSymbolInfo(memberExpresion).Symbol; if (memberSymbol == null) return; - if (!memberSymbol.ToString().StartsWith("string.Format(string, ")) return; + var memberSignature = memberSymbol.ToString(); + if (!memberSignature.StartsWith("string.Format(string, ")) return; var argumentList = invocationExpression.ArgumentList as ArgumentListSyntax; - if (argumentList?.Arguments.Count < 2) return; - if (!argumentList.Arguments[0]?.Expression?.IsKind(SyntaxKind.StringLiteralExpression) ?? false) return; - if (memberSymbol.ToString() == "string.Format(string, params object[])" && argumentList.Arguments.Skip(1).Any(a => context.SemanticModel.GetTypeInfo(a.Expression).Type.TypeKind == TypeKind.Array)) return; - var formatLiteral = (LiteralExpressionSyntax)argumentList.Arguments[0].Expression; + if (argumentList == null) return; + var arguments = argumentList.Arguments; + if (!arguments[0]?.Expression?.IsKind(SyntaxKind.StringLiteralExpression) ?? false) return; + if (memberSignature == "string.Format(string, params object[])" && arguments.Count == 2 && context.SemanticModel.GetTypeInfo(arguments[1].Expression).Type.TypeKind == TypeKind.Array) return; + var formatLiteral = (LiteralExpressionSyntax)arguments[0].Expression; var analyzingInterpolation = (InterpolatedStringExpressionSyntax)SyntaxFactory.ParseExpression($"${formatLiteral.Token.Text}"); var allInterpolations = analyzingInterpolation.Contents.Where(c => c.IsKind(SyntaxKind.Interpolation)).Select(c => (InterpolationSyntax)c); var distinctInterpolations = allInterpolations.Select(c => c.Expression.ToString()).Distinct(); - if (distinctInterpolations.Count() != argumentList.Arguments.Count - 1) + if (distinctInterpolations.Count() < arguments.Count - 1) { - var diag = Diagnostic.Create(IncorrectNumberOfArgs, invocationExpression.GetLocation()); + var diag = Diagnostic.Create(ExtraArgs, invocationExpression.GetLocation()); context.ReportDiagnostic(diag); return; } @@ -70,14 +73,15 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) int argIndexReference; if (int.TryParse(interpolation, out argIndexReference)) { - validIndexReference = argIndexReference >= 0 && argIndexReference < argumentList.Arguments.Count - 1; + validIndexReference = argIndexReference >= 0 && argIndexReference < arguments.Count - 1; } if (!validIndexReference) { - var diag = Diagnostic.Create(InvalidArgsReference, invocationExpression.GetLocation()); + var diag = Diagnostic.Create(InvalidArgs, invocationExpression.GetLocation()); context.ReportDiagnostic(diag); + return; } } } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Usage/UnusedParametersAnalyzer.cs b/src/CSharp/CodeCracker/Usage/UnusedParametersAnalyzer.cs index 5ba4d88ae..db1cbdf65 100644 --- a/src/CSharp/CodeCracker/Usage/UnusedParametersAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/UnusedParametersAnalyzer.cs @@ -14,10 +14,9 @@ public class UnusedParametersAnalyzer : DiagnosticAnalyzer internal const string Title = "Unused parameters"; internal const string Message = "Parameter '{0}' is not used."; internal const string Category = SupportedCategories.Usage; - const string Description = "When a method declares a parameter and does not use it might bring incorrect conclusions for anyone reading the code and also demands the parameter when the method is called, unnecessarily.\r\n" - + "You should delete the parameter in such cases."; + const string Description = "A method with an unused parameter creates unnecessary confusion and should be deleted."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.UnusedParameters.ToDiagnosticId(), Title, Message, @@ -42,6 +41,7 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) if (!IsCandidateForRemoval(methodOrConstructor, semanticModel)) return; var parameters = methodOrConstructor.ParameterList.Parameters.ToDictionary(p => p, p => semanticModel.GetDeclaredSymbol(p)); var ctor = methodOrConstructor as ConstructorDeclarationSyntax; + if (ctor?.Initializer != null) { var symbolsTouched = new List(); @@ -56,30 +56,81 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) foreach (var parameter in parametersToRemove) parameters.Remove(parameter.Key); } - if (methodOrConstructor.Body.Statements.Any()) + + var method = methodOrConstructor as MethodDeclarationSyntax; + + // It is legit for virtual methods to have parameters that aren't used in the default + // base class implementation, but are only provided for sub classes instead. + // See https://github.com/code-cracker/code-cracker/issues/872 + if (method?.Modifiers.Any(SyntaxKind.VirtualKeyword) == true) return; + + IEnumerable methodChildren = methodOrConstructor.Body?.Statements; + var expressionBody = (methodOrConstructor as MethodDeclarationSyntax)?.ExpressionBody; + if (methodChildren == null && expressionBody != null) + methodChildren = new[] { expressionBody }; + + if (methodChildren?.Any() ?? false) { - var dataFlowAnalysis = semanticModel.AnalyzeDataFlow(methodOrConstructor.Body.Statements.First(), methodOrConstructor.Body.Statements.Last()); - if (!dataFlowAnalysis.Succeeded) return; + var identifiers = methodChildren + .SelectMany(s => s.DescendantNodesAndSelf()) + .OfType() + .ToList(); foreach (var parameter in parameters) { - var parameterSymbol = parameter.Value; - if (parameterSymbol == null) continue; - if (!dataFlowAnalysis.ReadInside.Contains(parameterSymbol) && !dataFlowAnalysis.WrittenInside.Contains(parameterSymbol)) - context = ReportDiagnostic(context, parameter.Key); + var used = identifiers + .Any(iName => IdentifierRefersToParam(iName, parameter.Key)); + + if (!used) + { + ReportDiagnostic(context, parameter.Key); + } } + // + // THIS IS THE RIGHT WAY TO DO THIS VERIFICATION. + // BUT, WE HAVE TO WAIT FOR A "BUGFIX" FROM ROSLYN TEAM + // IN DataFlowAnalysis + // + // https://github.com/dotnet/roslyn/issues/6967 + // + //var dataFlowAnalysis = semanticModel.AnalyzeDataFlow(methodOrConstructor.Body); + //if (!dataFlowAnalysis.Succeeded) return; + //foreach (var parameter in parameters) + //{ + + // var parameterSymbol = parameter.Value; + // if (parameterSymbol == null) continue; + // if (!dataFlowAnalysis.ReadInside.Contains(parameterSymbol) && + // !dataFlowAnalysis.WrittenInside.Contains(parameterSymbol)) + // { + // ReportDiagnostic(context, parameter.Key); + // } + //} } else { foreach (var parameter in parameters.Keys) - context = ReportDiagnostic(context, parameter); + ReportDiagnostic(context, parameter); } } + private static bool IdentifierRefersToParam(IdentifierNameSyntax iName, ParameterSyntax param) + { + var identifierName = iName.Identifier.ToString(); + if (!identifierName.StartsWith("@")) identifierName = $"@{identifierName}"; + var parameterName = param.Identifier.ToString(); + if (!parameterName.StartsWith("@")) parameterName = $"@{parameterName}"; + if (identifierName != parameterName) + return false; + var mae = iName.Parent as MemberAccessExpressionSyntax; + if (mae == null) + return true; + return mae.DescendantNodes().FirstOrDefault() == iName; + } + private static bool IsCandidateForRemoval(BaseMethodDeclarationSyntax methodOrConstructor, SemanticModel semanticModel) { - if (methodOrConstructor.Modifiers.Any(m => m.ValueText == "partial" || m.ValueText == "override") - || !methodOrConstructor.ParameterList.Parameters.Any() - || methodOrConstructor.Body == null) + if (methodOrConstructor.Modifiers.Any(m => m.ValueText == "partial" || m.ValueText == "override" || m.ValueText == "abstract" || m.ValueText == "extern") + || !methodOrConstructor.ParameterList.Parameters.Any()) return false; var method = methodOrConstructor as MethodDeclarationSyntax; if (method != null) @@ -92,6 +143,8 @@ private static bool IsCandidateForRemoval(BaseMethodDeclarationSyntax methodOrCo .Any(member => methodSymbol.Equals(typeSymbol.FindImplementationForInterfaceMember(member)))) return false; if (IsEventHandlerLike(method, semanticModel)) return false; + if (IsPrivateAndUsedAsMethodGroup(method, methodSymbol, semanticModel)) return false; + if (method.Parent is InterfaceDeclarationSyntax) return false; } else { @@ -108,6 +161,23 @@ private static bool IsCandidateForRemoval(BaseMethodDeclarationSyntax methodOrCo return true; } + private static bool IsPrivateAndUsedAsMethodGroup(MethodDeclarationSyntax method, IMethodSymbol methodSymbol, SemanticModel semanticModel) + { + if (methodSymbol.DeclaredAccessibility != Accessibility.Private) return false; + var parentType = method.Parent; + var allTokens = parentType.DescendantTokens(); + var tokensThatMatch = from t in allTokens + let text = t.Text + where text == method.Identifier.Text + select t; + foreach (var token in tokensThatMatch) + { + var nodeSymbol = semanticModel.GetSymbolInfo(token.Parent).Symbol; + if (methodSymbol.Equals(nodeSymbol)) return true; + } + return false; + } + private static bool IsSerializationConstructor(ConstructorDeclarationSyntax constructor, SemanticModel semanticModel) { if (constructor.ParameterList.Parameters.Count != 2) return false; @@ -144,4 +214,4 @@ private static SyntaxNodeAnalysisContext ReportDiagnostic(SyntaxNodeAnalysisCont return context; } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Usage/UnusedParametersCodeFixAllProvider.cs b/src/CSharp/CodeCracker/Usage/UnusedParametersCodeFixAllProvider.cs new file mode 100644 index 000000000..74650b882 --- /dev/null +++ b/src/CSharp/CodeCracker/Usage/UnusedParametersCodeFixAllProvider.cs @@ -0,0 +1,133 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CodeCracker.CSharp.Usage +{ + public sealed class UnusedParametersCodeFixAllProvider : FixAllProvider + { + private UnusedParametersCodeFixAllProvider() { } + + private const string message = "Remove unused parameter"; + public static readonly UnusedParametersCodeFixAllProvider Instance = new UnusedParametersCodeFixAllProvider(); + public override Task GetFixAsync(FixAllContext fixAllContext) + { + switch (fixAllContext.Scope) + { + case FixAllScope.Document: + return Task.FromResult(CodeAction.Create(message, + async ct => await GetFixedSolutionAsync(fixAllContext, await GetSolutionWithDocsAsync(fixAllContext, fixAllContext.Document)))); + case FixAllScope.Project: + return Task.FromResult(CodeAction.Create(message, + async ct => await GetFixedSolutionAsync(fixAllContext, await GetSolutionWithDocsAsync(fixAllContext, fixAllContext.Project)))); + case FixAllScope.Solution: + return Task.FromResult(CodeAction.Create(message, + async ct => await GetFixedSolutionAsync(fixAllContext, await GetSolutionWithDocsAsync(fixAllContext, fixAllContext.Solution)))); + default: + return null; + } + } + + private async static Task GetSolutionWithDocsAsync(FixAllContext fixAllContext, Solution solution) + { + var docs = new List(); + var sol = new SolutionWithDocs { Docs = docs, Solution = solution }; + foreach (var pId in solution.Projects.Select(p => p.Id)) + { + var project = sol.Solution.GetProject(pId); + var newSol = await GetSolutionWithDocsAsync(fixAllContext, project).ConfigureAwait(false); + sol.Merge(newSol); + } + return sol; + } + + private async static Task GetSolutionWithDocsAsync(FixAllContext fixAllContext, Project project) + { + var docs = new List(); + var newSolution = project.Solution; + foreach (var document in project.Documents) + { + var doc = await GetDiagnosticsInDocAsync(fixAllContext, document); + if (doc.Equals(DiagnosticsInDoc.Empty)) continue; + docs.Add(doc); + newSolution = newSolution.WithDocumentSyntaxRoot(document.Id, doc.TrackedRoot); + } + var sol = new SolutionWithDocs { Docs = docs, Solution = newSolution }; + return sol; + } + + private async static Task GetSolutionWithDocsAsync(FixAllContext fixAllContext, Document document) + { + var docs = new List(); + var doc = await GetDiagnosticsInDocAsync(fixAllContext, document); + docs.Add(doc); + var newSolution = document.Project.Solution.WithDocumentSyntaxRoot(document.Id, doc.TrackedRoot); + var sol = new SolutionWithDocs { Docs = docs, Solution = newSolution }; + return sol; + } + + private static async Task GetDiagnosticsInDocAsync(FixAllContext fixAllContext, Document document) + { + var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false); + if (!diagnostics.Any()) return DiagnosticsInDoc.Empty; + var root = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); + var doc = DiagnosticsInDoc.Create(document.Id, diagnostics, root); + return doc; + } + + private async static Task GetFixedSolutionAsync(FixAllContext fixAllContext, SolutionWithDocs sol) + { + var newSolution = sol.Solution; + foreach (var doc in sol.Docs) + { + foreach (var node in doc.Nodes) + { + var document = newSolution.GetDocument(doc.DocumentId); + var root = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); + var trackedNode = root.GetCurrentNode(node); + var parameter = trackedNode.AncestorsAndSelf().OfType().First(); + var docResults = await UnusedParametersCodeFixProvider.RemoveParameterAsync(document, parameter, root, fixAllContext.CancellationToken); + foreach (var docResult in docResults) + newSolution = newSolution.WithDocumentSyntaxRoot(docResult.DocumentId, docResult.Root); + } + } + return newSolution; + } + + private struct DiagnosticsInDoc + { + public static DiagnosticsInDoc Create(DocumentId documentId, IList diagnostics, SyntaxNode root) + { + var nodes = diagnostics.Select(d => root.FindNode(d.Location.SourceSpan)).Where(n => !n.IsMissing).ToList(); + var diagnosticsInDoc = new DiagnosticsInDoc + { + DocumentId = documentId, + TrackedRoot = root.TrackNodes(nodes), + Nodes = nodes + }; + return diagnosticsInDoc; + } + public DocumentId DocumentId; + public List Nodes; + public SyntaxNode TrackedRoot; + + private static readonly DiagnosticsInDoc empty = new DiagnosticsInDoc(); + public static DiagnosticsInDoc Empty => empty; + } + + private struct SolutionWithDocs + { + public Solution Solution; + public List Docs; + public void Merge(SolutionWithDocs sol) + { + Solution = sol.Solution; + Docs.AddRange(sol.Docs); + } + } + } +} \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Usage/UnusedParametersCodeFixProvider.cs b/src/CSharp/CodeCracker/Usage/UnusedParametersCodeFixProvider.cs index 3b53b735b..49914e370 100644 --- a/src/CSharp/CodeCracker/Usage/UnusedParametersCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Usage/UnusedParametersCodeFixProvider.cs @@ -19,7 +19,7 @@ public class UnusedParametersCodeFixProvider : CodeFixProvider public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.UnusedParameters.ToDiagnosticId()); - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + public sealed override FixAllProvider GetFixAllProvider() => UnusedParametersCodeFixAllProvider.Instance; public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) { @@ -29,21 +29,32 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) return Task.FromResult(0); } - private async static Task RemoveParameterAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + public async static Task RemoveParameterAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { + var solution = document.Project.Solution; + var newSolution = solution; var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var parameter = root.FindToken(diagnostic.Location.SourceSpan.Start).Parent.AncestorsAndSelf().OfType().First(); + var docs = await RemoveParameterAsync(document, parameter, root, cancellationToken); + foreach (var doc in docs) + newSolution = newSolution.WithDocumentSyntaxRoot(doc.DocumentId, doc.Root); + return newSolution; + } + + public async static Task> RemoveParameterAsync(Document document, ParameterSyntax parameter, SyntaxNode root, CancellationToken cancellationToken) + { var solution = document.Project.Solution; var parameterList = (ParameterListSyntax)parameter.Parent; var parameterPosition = parameterList.Parameters.IndexOf(parameter); + if (parameterList.Parameters.First().ToString().Contains("this")) parameterPosition--; var newParameterList = parameterList.WithParameters(parameterList.Parameters.Remove(parameter)); - var newSolution = solution; var foundDocument = false; - var semanticModel = await document.GetSemanticModelAsync(cancellationToken); + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var method = (BaseMethodDeclarationSyntax)parameter.Parent.Parent; var methodSymbol = semanticModel.GetDeclaredSymbol(method); - var references = await SymbolFinder.FindReferencesAsync(methodSymbol, solution, cancellationToken).ConfigureAwait(false); + var references = await SymbolFinder.FindReferencesAsync(methodSymbol, solution, cancellationToken); var documentGroups = references.SelectMany(r => r.Locations).GroupBy(loc => loc.Document); + var docs = new List(); foreach (var documentGroup in documentGroups) { var referencingDocument = documentGroup.Key; @@ -69,19 +80,44 @@ private async static Task RemoveParameterAsync(Document document, Diag var arguments = objectCreation != null ? objectCreation.ArgumentList : methodIdentifier.FirstAncestorOfType().ArgumentList; - var newArguments = arguments.WithArguments(arguments.Arguments.RemoveAt(parameterPosition)); - replacingArgs.Add(arguments, newArguments); + + // Attempt to find the parameter as a named argument. Named arguments can only appear once in the argument list. + var namedArg = arguments.Arguments.SingleOrDefault(arg => arg.NameColon != null && arg.NameColon.Name.Identifier.Text == parameter.Identifier.Text); + if (namedArg != null) + { + var newArguments = arguments.WithArguments(arguments.Arguments.Remove(namedArg)); + replacingArgs.Add(arguments, newArguments); + } + else if (parameter.Modifiers.Any(m => m.IsKind(SyntaxKind.ParamsKeyword))) + { + var newArguments = arguments; + while (newArguments.Arguments.Count > parameterPosition) + { + newArguments = newArguments.WithArguments(newArguments.Arguments.RemoveAt(parameterPosition)); + } + replacingArgs.Add(arguments, newArguments); + } + else + { + var newArguments = arguments.WithArguments(arguments.Arguments.RemoveAt(parameterPosition)); + replacingArgs.Add(arguments, newArguments); + } } var newLocRoot = locRoot.ReplaceNodes(replacingArgs.Keys, (original, rewritten) => replacingArgs[original]); - newSolution = newSolution.WithDocumentSyntaxRoot(referencingDocument.Id, newLocRoot); + docs.Add(new DocumentIdAndRoot { DocumentId = referencingDocument.Id, Root = newLocRoot }); } if (!foundDocument) { var newRoot = root.ReplaceNode(parameterList, newParameterList); var newDocument = document.WithSyntaxRoot(newRoot); - newSolution = newSolution.WithDocumentSyntaxRoot(document.Id, newRoot); + docs.Add(new DocumentIdAndRoot { DocumentId = document.Id, Root = newRoot }); } - return newSolution; + return docs; + } + public struct DocumentIdAndRoot + { + internal DocumentId DocumentId; + internal SyntaxNode Root; } } } \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Usage/UriAnalyzer.cs b/src/CSharp/CodeCracker/Usage/UriAnalyzer.cs index 4cdee1157..464976212 100644 --- a/src/CSharp/CodeCracker/Usage/UriAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/UriAnalyzer.cs @@ -17,7 +17,7 @@ public class UriAnalyzer : DiagnosticAnalyzer private const string Description = "This diagnostic checks the Uri string and triggers if the parsing fail " + "by throwing an exception."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.Uri.ToDiagnosticId(), Title, MessageFormat, diff --git a/src/CSharp/CodeCracker/Usage/VirtualMethodOnConstructorAnalyzer.cs b/src/CSharp/CodeCracker/Usage/VirtualMethodOnConstructorAnalyzer.cs index f0b2c179c..f0d2becef 100644 --- a/src/CSharp/CodeCracker/Usage/VirtualMethodOnConstructorAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/VirtualMethodOnConstructorAnalyzer.cs @@ -18,7 +18,7 @@ public class VirtualMethodOnConstructorAnalyzer : DiagnosticAnalyzer "it is possible that the constructor for the instance that invokes the method " + "has not executed."; - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.VirtualMethodOnConstructor.ToDiagnosticId(), Title, Message, @@ -40,13 +40,21 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) var ctor = (ConstructorDeclarationSyntax)context.Node; if (ctor.Body == null) return; var methodInvocations = ctor.Body.DescendantNodes().OfType(); - foreach (var method in methodInvocations) + var semanticModel = context.SemanticModel; + foreach (var invocation in methodInvocations) { - var identifier = method.Expression as IdentifierNameSyntax; - if (identifier == null && !method.ToString().StartsWith("this")) return; - var methodDeclaration = context.SemanticModel.GetSymbolInfo(method).Symbol; - if (methodDeclaration == null || !methodDeclaration.IsVirtual) return; - var diagnostic = Diagnostic.Create(Rule, method.GetLocation()); + var identifier = invocation.Expression as IdentifierNameSyntax; + if (identifier == null) + { + if (!invocation.ToString().StartsWith("this.")) return; + } + else + { + if (semanticModel.GetSymbolInfo(identifier).Symbol is IParameterSymbol) return; + } + var methodSymbol = semanticModel.GetSymbolInfo(invocation).Symbol; + if (methodSymbol == null || !methodSymbol.IsVirtual) return; + var diagnostic = Diagnostic.Create(Rule, invocation.GetLocation()); context.ReportDiagnostic(diagnostic); } } diff --git a/src/CSharp/CodeCracker/packages.config b/src/CSharp/CodeCracker/packages.config deleted file mode 100644 index 67601ec6f..000000000 --- a/src/CSharp/CodeCracker/packages.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/src/CodeCracker.nuspec b/src/CodeCracker.nuspec deleted file mode 100644 index 8a93b003f..000000000 --- a/src/CodeCracker.nuspec +++ /dev/null @@ -1,30 +0,0 @@ - - - - codecracker - 1.0.0-rc3 - CodeCracker - giggio,elemarjr,carloscds - giggio,elemarjr,carloscds - https://github.com/code-cracker/code-cracker/blob/master/LICENSE.txt - http://code-cracker.github.io/ - https://avatars1.githubusercontent.com/u/9695920?v=3&s=200 - true - A analyzer library for C# and VB that uses Roslyn to produce refactorings, code analysis, and other niceties. - -You probably don't want this package directly, search for the C# or Visual Basic specific packages (codecracker.CSharp and codecracker.VisualBasic). This will install both. - -This is a community project, free and open source. Everyone is invited to contribute, fork, share and use the code. No money shall be charged by this software, nor it will be. Ever. - First alpha release - Copyright CodeCracker 2014 - roslyn, analyzers - - - - - - - - - - diff --git a/src/Common/CodeCracker.Common/CodeCracker.Common.csproj b/src/Common/CodeCracker.Common/CodeCracker.Common.csproj index 0a0fc5a52..8f502cca1 100644 --- a/src/Common/CodeCracker.Common/CodeCracker.Common.csproj +++ b/src/Common/CodeCracker.Common/CodeCracker.Common.csproj @@ -1,5 +1,5 @@  - + 11.0 @@ -15,6 +15,7 @@ {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Profile7 v4.5 + AD0001 true @@ -34,11 +35,22 @@ prompt 4 + + win + true + + + + + + + + @@ -46,69 +58,22 @@ True Resources.resx + + True + True + Resources.fr.resx + - - Designer - - - + + ResXFileCodeGenerator + Resources.fr.Designer.cs + PublicResXFileCodeGenerator Resources.Designer.cs - - - - - - - - - ..\..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll - False - - - ..\..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.Workspaces.dll - False - - - ..\..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll - False - - - ..\..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll - False - - - \ No newline at end of file diff --git a/src/Common/CodeCracker.Common/DiagnosticId.cs b/src/Common/CodeCracker.Common/DiagnosticId.cs index 788601342..4dfc7ecb6 100644 --- a/src/Common/CodeCracker.Common/DiagnosticId.cs +++ b/src/Common/CodeCracker.Common/DiagnosticId.cs @@ -18,7 +18,6 @@ public enum DiagnosticId TernaryOperator_Return = 13, TernaryOperator_Assignment = 14, UnnecessaryParenthesis = 15, - CopyEventToVariableBeforeFire = 16, SwitchToAutoProp = 17, ExistenceOperator = 18, ConvertToSwitch = 19, @@ -49,7 +48,7 @@ public enum DiagnosticId SimplifyRedundantBooleanComparisons = 49, ReadonlyField = 52, JsonNet = 54, - StringFormatArgs = 56, + StringFormatArgs_InvalidArgs = 56, UnusedParameters = 57, AbstractClassShouldNotHavePublicCtors = 60, TaskNameAsync = 61, @@ -73,9 +72,18 @@ public enum DiagnosticId UseStringEmpty = 84, UseEmptyString = 88, RemoveRedundantElseClause = 89, - XmlDocumentation = 90, + XmlDocumentation_MissingInCSharp = 90, MakeMethodStatic = 91, ChangeAllToAny = 92, ConsoleWriteLine = 95, + XmlDocumentation_MissingInXml = 97, + NameOf_External = 108, + StringFormatArgs_ExtraArgs = 111, + AlwaysUseVarOnPrimitives = 105, + PropertyChangedEventArgsUnnecessaryAllocation = 106, + UnnecessaryToStringInStringConcatenation = 118, + SwitchCaseWithoutDefault = 120, + ReadOnlyComplexTypes = 121, + ReplaceWithGetterOnlyAutoProperty = 125, } -} +} \ No newline at end of file diff --git a/src/Common/CodeCracker.Common/Extensions/AnalyzerExtensions.cs b/src/Common/CodeCracker.Common/Extensions/AnalyzerExtensions.cs index 2e8a22cbf..cc3802231 100644 --- a/src/Common/CodeCracker.Common/Extensions/AnalyzerExtensions.cs +++ b/src/Common/CodeCracker.Common/Extensions/AnalyzerExtensions.cs @@ -52,8 +52,19 @@ public static SyntaxNode FirstAncestorOrSelfOfType(this SyntaxNode node, params return null; } - public static T FirstAncestorOfType(this SyntaxNode node) where T : SyntaxNode => - (T)node.FirstAncestorOfType(typeof(T)); + public static T FirstAncestorOfType(this SyntaxNode node) where T : SyntaxNode + { + var currentNode = node; + while (true) + { + var parent = currentNode.Parent; + if (parent == null) break; + var tParent = parent as T; + if (tParent != null) return tParent; + currentNode = parent; + } + return null; + } public static SyntaxNode FirstAncestorOfType(this SyntaxNode node, params Type[] types) { @@ -111,5 +122,75 @@ public static string GetLastIdentifierIfQualiedTypeName(this string typeName) return result; } public static IEnumerable EnsureProtectedBeforeInternal(this IEnumerable modifiers) => modifiers.OrderByDescending(token => token.RawKind); + + public static string GetFullName(this ISymbol symbol, bool addGlobal = true) + { + if (symbol.Kind == SymbolKind.TypeParameter) + return symbol.ToString(); + var fullName = symbol.Name; + var containingSymbol = symbol.ContainingSymbol; + while (!(containingSymbol is INamespaceSymbol)) + { + fullName = $"{containingSymbol.Name}.{fullName}"; + containingSymbol = containingSymbol.ContainingSymbol; + } + if (!((INamespaceSymbol)containingSymbol).IsGlobalNamespace) + fullName = $"{containingSymbol.ToString()}.{fullName}"; + if (addGlobal) + fullName = $"global::{fullName}"; + return fullName; + } + + public static IEnumerable GetAllContainingTypes(this ISymbol symbol) + { + while (symbol.ContainingType != null) + { + yield return symbol.ContainingType; + symbol = symbol.ContainingType; + } + } + + public static Accessibility GetMinimumCommonAccessibility(this Accessibility accessibility, Accessibility otherAccessibility) + { + if (accessibility == otherAccessibility || otherAccessibility == Accessibility.Private) return accessibility; + if (otherAccessibility == Accessibility.Public) return Accessibility.Public; + switch (accessibility) + { + case Accessibility.Private: + return otherAccessibility; + case Accessibility.ProtectedAndInternal: + case Accessibility.Protected: + case Accessibility.Internal: + return Accessibility.ProtectedAndInternal; + case Accessibility.Public: + return Accessibility.Public; + default: + throw new NotSupportedException(); + } + } + + public static bool IsPrimitive(this ITypeSymbol typeSymbol) + { + switch (typeSymbol.SpecialType) + { + case SpecialType.System_Boolean: + case SpecialType.System_Byte: + case SpecialType.System_SByte: + case SpecialType.System_Int16: + case SpecialType.System_UInt16: + case SpecialType.System_Int32: + case SpecialType.System_UInt32: + case SpecialType.System_Int64: + case SpecialType.System_UInt64: + case SpecialType.System_IntPtr: + case SpecialType.System_UIntPtr: + case SpecialType.System_Char: + case SpecialType.System_Double: + case SpecialType.System_Single: + return true; + default: + return false; + } + } } } \ No newline at end of file diff --git a/src/Common/CodeCracker.Common/Extensions/Extensions.cs b/src/Common/CodeCracker.Common/Extensions/Extensions.cs index 11a56a012..5ef4f95ca 100644 --- a/src/Common/CodeCracker.Common/Extensions/Extensions.cs +++ b/src/Common/CodeCracker.Common/Extensions/Extensions.cs @@ -23,5 +23,12 @@ public static bool EndsWithAny(this string text, StringComparison comparisonType if (text.EndsWith(value, comparisonType)) return true; return false; } + + public static string ToLowerCaseFirstLetter(this string text) + { + if (string.IsNullOrWhiteSpace(text)) return text; + if (text.Length == 1) return text.ToLower(); + return char.ToLowerInvariant(text[0]) + text.Substring(1); + } } } diff --git a/src/Common/CodeCracker.Common/FixAllProviders/DocumentCodeFixProviderAll.cs b/src/Common/CodeCracker.Common/FixAllProviders/DocumentCodeFixProviderAll.cs new file mode 100644 index 000000000..c74833924 --- /dev/null +++ b/src/Common/CodeCracker.Common/FixAllProviders/DocumentCodeFixProviderAll.cs @@ -0,0 +1,99 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace CodeCracker.FixAllProviders +{ + + public sealed class DocumentCodeFixProviderAll : FixAllProvider + { + private const string SyntaxAnnotationKey = "DocumentCodeFixProviderAllSyntaxAnnotation"; + + public DocumentCodeFixProviderAll(string codeFixTitle) + { + CodeFixTitle = codeFixTitle; + } + + private string CodeFixTitle { get; } + + public override Task GetFixAsync(FixAllContext fixAllContext) + { + switch (fixAllContext.Scope) + { + case FixAllScope.Document: + return Task.FromResult(CodeAction.Create(CodeFixTitle, + ct => GetFixedDocumentsAsync(fixAllContext, Enumerable.Repeat(fixAllContext.Document, 1)))); + case FixAllScope.Project: + return Task.FromResult(CodeAction.Create(CodeFixTitle, + ct => GetFixedDocumentsAsync(fixAllContext, fixAllContext.Project.Documents))); + case FixAllScope.Solution: + return Task.FromResult(CodeAction.Create(CodeFixTitle, + ct => GetFixedDocumentsAsync(fixAllContext, fixAllContext.Solution.Projects.SelectMany(p => p.Documents)))); + default: + return null; + } + } + + private async static Task GetFixedDocumentsAsync(FixAllContext fixAllContext, IEnumerable documents) + { + var solution = fixAllContext.Solution; + var newDocuments = documents.ToDictionary(d => d.Id, d => GetFixedDocumentAsync(fixAllContext, d)); + await Task.WhenAll(newDocuments.Values).ConfigureAwait(false); + var changedDocuments = from kvp in newDocuments + where kvp.Value.Result != null + select new { DocumentId = kvp.Key, Document = kvp.Value.Result }; + foreach (var newDocument in changedDocuments) + solution = solution.WithDocumentSyntaxRoot(newDocument.DocumentId, await newDocument.Document.GetSyntaxRootAsync().ConfigureAwait(false)); + return solution; + } + + private async static Task GetFixedDocumentAsync(FixAllContext fixAllContext, Document document) + { + var codeFixer = fixAllContext.CodeFixProvider as IFixDocumentInternalsOnly; + if (codeFixer == null) throw new ArgumentException("This CodeFixAllProvider requires that your CodeFixProvider implements the IFixDocumentInternalsOnly."); + var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false); + if (diagnostics.Length == 0) return null; + var root = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); + var nodes = diagnostics.Select(d => root.FindNode(d.Location.SourceSpan)).Where(n => !n.IsMissing); + var annotations = new List(); + var newRoot = root.ReplaceNodes(nodes, (original, rewritten) => + { + var annotation = new SyntaxAnnotation(SyntaxAnnotationKey); + annotations.Add(annotation); + var newNode = original.WithAdditionalAnnotations(annotation); + return newNode; + }); + var newDocument = document.WithSyntaxRoot(newRoot); + newDocument = await FixCodeForAnnotatedNodesAsync(newDocument, codeFixer, annotations, fixAllContext.CancellationToken).ConfigureAwait(false); + newDocument = await RemoveAnnotationsAsync(newDocument, annotations).ConfigureAwait(false); + return newDocument; + } + + private static async Task FixCodeForAnnotatedNodesAsync(Document document, IFixDocumentInternalsOnly codeFixer, IEnumerable annotations, CancellationToken cancellationToken) + { + foreach (var annotation in annotations) + { + var newRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var annotatedNodes = newRoot.GetAnnotatedNodes(annotation); + var node = annotatedNodes.FirstOrDefault(); + if (node == null) continue; + document = await codeFixer.FixDocumentAsync(node, document, cancellationToken).ConfigureAwait(false); + } + return document; + } + + private static async Task RemoveAnnotationsAsync(Document document, IEnumerable annotations) + { + var root = await document.GetSyntaxRootAsync().ConfigureAwait(false); + var nodes = annotations.SelectMany(annotation => root.GetAnnotatedNodes(annotation)); + root = root.ReplaceNodes(nodes, (original, rewritten) => original.WithoutAnnotations(annotations)); + var newDocument = document.WithSyntaxRoot(root); + return newDocument; + } + } +} \ No newline at end of file diff --git a/src/Common/CodeCracker.Common/FixAllProviders/IFixDocumentInternalsOnly.cs b/src/Common/CodeCracker.Common/FixAllProviders/IFixDocumentInternalsOnly.cs new file mode 100644 index 000000000..901942669 --- /dev/null +++ b/src/Common/CodeCracker.Common/FixAllProviders/IFixDocumentInternalsOnly.cs @@ -0,0 +1,16 @@ +using Microsoft.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace CodeCracker.FixAllProviders +{ + /// + /// This interface must be implemented by the associated CodeFixProvider. The CodeFixProvider must operate on a single document and + /// should only change the document. This limits the possible operations of the CodeFixProvider to change only document internals without + /// effecting other parts of the solution. + /// + public interface IFixDocumentInternalsOnly + { + Task FixDocumentAsync(SyntaxNode nodeWithDiagnostic, Document document, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Common/CodeCracker.Common/Properties/AssemblyInfo.cs b/src/Common/CodeCracker.Common/Properties/AssemblyInfo.cs index bee783931..ff625b1e5 100644 --- a/src/Common/CodeCracker.Common/Properties/AssemblyInfo.cs +++ b/src/Common/CodeCracker.Common/Properties/AssemblyInfo.cs @@ -8,12 +8,12 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CodeCracker")] -[assembly: AssemblyCopyright("Copyright © 2014-2015")] +[assembly: AssemblyCopyright("Copyright © 2014-2018")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: NeutralResourcesLanguage("en")] [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("1.0.0.10")] +[assembly: AssemblyFileVersion("1.1.0.0")] [assembly: InternalsVisibleTo("CodeCracker.Test.CSharp")] -[assembly: InternalsVisibleTo("CodeCracker.Test.VisualBasic")] \ No newline at end of file +[assembly: InternalsVisibleTo("CodeCracker.Test.VisualBasic")] diff --git a/src/Common/CodeCracker.Common/Properties/Resources.Designer.cs b/src/Common/CodeCracker.Common/Properties/Resources.Designer.cs index 5648e72dc..af567f32d 100644 --- a/src/Common/CodeCracker.Common/Properties/Resources.Designer.cs +++ b/src/Common/CodeCracker.Common/Properties/Resources.Designer.cs @@ -97,6 +97,69 @@ public static string ConsoleWriteLineCodeFixProvider_Title { } } + /// + /// Looks up a localized string similar to An empty catch block suppress all errors and shouldn't be used.\r\nIf the error is expected consider logging it or changing the control flow such that it is explicit.. + /// + public static string EmptyCatchBlockAnalyzer_Description { + get { + return ResourceManager.GetString("EmptyCatchBlockAnalyzer_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Empty Catch Block.. + /// + public static string EmptyCatchBlockAnalyzer_Message { + get { + return ResourceManager.GetString("EmptyCatchBlockAnalyzer_Message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Catch block cannot be empty. + /// + public static string EmptyCatchBlockAnalyzer_Title { + get { + return ResourceManager.GetString("EmptyCatchBlockAnalyzer_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Insert Exception class to Catch. + /// + public static string EmptyCatchBlockCodeFixProvider_InsertException { + get { + return ResourceManager.GetString("EmptyCatchBlockCodeFixProvider_InsertException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove Empty Catch Block. + /// + public static string EmptyCatchBlockCodeFixProvider_Remove { + get { + return ResourceManager.GetString("EmptyCatchBlockCodeFixProvider_Remove", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove Empty Catch Block and Put a Documentation Link about Try...Catch use. + /// + public static string EmptyCatchBlockCodeFixProvider_RemoveAndDocumentation { + get { + return ResourceManager.GetString("EmptyCatchBlockCodeFixProvider_RemoveAndDocumentation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove wrapping Try Block. + /// + public static string EmptyCatchBlockCodeFixProvider_RemoveTry { + get { + return ResourceManager.GetString("EmptyCatchBlockCodeFixProvider_RemoveTry", resourceCulture); + } + } + /// /// Looks up a localized string similar to Change field type '{0}' accessibility to be as accessible as field '{1}'. /// @@ -151,6 +214,51 @@ public static string InconsistentAccessibilityInPropertyType_Title { } } + /// + /// Looks up a localized string similar to Consider introduce field for constructor parameters.. + /// + public static string IntroduceFieldFromConstructorAnalyzer_Description { + get { + return ResourceManager.GetString("IntroduceFieldFromConstructorAnalyzer_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Introduce a field for parameter: {0}. + /// + public static string IntroduceFieldFromConstructorAnalyzer_MessageFormat { + get { + return ResourceManager.GetString("IntroduceFieldFromConstructorAnalyzer_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Consider introduce field for constructor parameters.. + /// + public static string IntroduceFieldFromConstructorAnalyzer_Title { + get { + return ResourceManager.GetString("IntroduceFieldFromConstructorAnalyzer_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Introduce field: {0} from constructor.. + /// + public static string IntroduceFieldFromConstructorCodeFixProvider_MessageFormat { + get { + return ResourceManager.GetString("IntroduceFieldFromConstructorCodeFixProvider_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Introduce fields for constructor parameters.. + /// + public static string IntroduceFieldFromConstructorCodeFixProvider_Title { + get { + return ResourceManager.GetString("IntroduceFieldFromConstructorCodeFixProvider_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to Make method non async. /// @@ -196,6 +304,78 @@ public static string NameOfCodeFixProvider_Title { } } + /// + /// Looks up a localized string similar to Create static PropertyChangedEventArgs instance and reuse. + /// + public static string PropertyChangedEventArgsUnnecessaryAllocation_CodeActionTitle { + get { + return ResourceManager.GetString("PropertyChangedEventArgsUnnecessaryAllocation_CodeActionTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Creating every time an instance of PropertyChangedEventArgs class causes unnecessary memory allocation. Instance can be created once and reused.. + /// + public static string PropertyChangedEventArgsUnnecessaryAllocation_Description { + get { + return ResourceManager.GetString("PropertyChangedEventArgsUnnecessaryAllocation_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Create PropertyChangedEventArgs static instance and reuse it to avoid unecessary memory allocation.. + /// + public static string PropertyChangedEventArgsUnnecessaryAllocation_MessageFormat { + get { + return ResourceManager.GetString("PropertyChangedEventArgsUnnecessaryAllocation_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to PropertyChangedEventArgs unnecessary allocation. + /// + public static string PropertyChangedEventArgsUnnecessaryAllocation_Title { + get { + return ResourceManager.GetString("PropertyChangedEventArgsUnnecessaryAllocation_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Getter only properties with backing read-only field can be converted to getter-only auto-properties.. + /// + public static string ReplaceWithGetterOnlyAutoPropertyAnalyzer_Description { + get { + return ResourceManager.GetString("ReplaceWithGetterOnlyAutoPropertyAnalyzer_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Property {0} can be converted to an getter-only auto-property.. + /// + public static string ReplaceWithGetterOnlyAutoPropertyAnalyzer_MessageFormat { + get { + return ResourceManager.GetString("ReplaceWithGetterOnlyAutoPropertyAnalyzer_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Property can be simplified by using an getter-only auto-property.. + /// + public static string ReplaceWithGetterOnlyAutoPropertyAnalyzer_Title { + get { + return ResourceManager.GetString("ReplaceWithGetterOnlyAutoPropertyAnalyzer_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Simplify by using an getter-only auto-property. + /// + public static string ReplaceWithGetterOnlyAutoPropertyCodeFixProvider_Title { + get { + return ResourceManager.GetString("ReplaceWithGetterOnlyAutoPropertyCodeFixProvider_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to String interpolation allows for better reading of the resulting string when compared to String.Format. You should use String.Format only when another method is supplying the format string.. /// diff --git a/src/Common/CodeCracker.Common/Properties/Resources.fr.Designer.cs b/src/Common/CodeCracker.Common/Properties/Resources.fr.Designer.cs new file mode 100644 index 000000000..e69de29bb diff --git a/src/Common/CodeCracker.Common/Properties/Resources.fr.resx b/src/Common/CodeCracker.Common/Properties/Resources.fr.resx new file mode 100644 index 000000000..630db6544 --- /dev/null +++ b/src/Common/CodeCracker.Common/Properties/Resources.fr.resx @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Change field type '{0}' accessibility to be as accessible as field '{1}' + + + Change parameter type '{0}' accessibility to be as accessible as indexer 'this[{1}]' + + + Change indexer return type '{0}' accessibility to be as accessible as indexer 'this[{1}]' + + + Change parameter type '{0}' accessibility to be as accessible as method '{1}' + + + Change return type '{0}' accessibility to be as accessible as method '{1}' + + + Change property type '{0}' accessibility to be as accessible as property '{1}' + + + String interpolation allows for better reading of the resulting string when compared to Console.WriteLine arguments. You should use Console.WriteLine with arguments only when another method is supplying the format string. + + + Use string interpolation + + + Use string interpolation instead of arguments on Console.WriteLine + + + Change to string interpolation + + + In C#6 the nameof() operator should be used to specify the name of a program element instead of a string literal as it produce code that is easier to refactor. + + + Use 'nameof({0})' instead of specifying the program element name. + + + Use nameof + + + Use nameof() + + + String interpolation allows for better reading of the resulting string when compared to String.Format. You should use String.Format only when another method is supplying the format string. + + + Use string interpolation + + + Use string interpolation instead of String.Format + + + Change to string interpolation + + + Make method non async + + + You have missing/unexistent parameters in Xml Docs + + + Create missing parameters in xml docs + + + Remove unexistent parameters in xml docs + + + Auto properties offer a more concise way of defining a property. If you are using simple getters and setters you are able to simplify your code with autoproperties. + + + Change {0} to an auto property + + + Use auto property + + + Change to auto property + + + Un block Catch vide avale toutes les erreurs et devrait être évité. +Si l'erreur est attendu considérer ajouter du logging ou modifier le flow de contrôle pour gérer l'erreur explicitement. + + + Block Catch Vide. + + + Un block catch ne peut etre vide + + + Insérer la classe Exception à la clause Catch + + + Enlever le block Catch vide + + + Enlever le block Catch vide et ajouter un lien vers la documentation des bonnes pratiques de Try...Catch + + + Creating every time an instance of PropertyChangedEventArgs class causes unnecessary memory allocation. Instance can be created once and reused. + + + Create PropertyChangedEventArgs static instance and reuse it to avoid unecessary memory allocation. + + + PropertyChangedEventArgs unnecessary allocation + + + Create static PropertyChangedEventArgs instance and reuse + + \ No newline at end of file diff --git a/src/Common/CodeCracker.Common/Properties/Resources.resx b/src/Common/CodeCracker.Common/Properties/Resources.resx index eb9bfeda3..944e7b07e 100644 --- a/src/Common/CodeCracker.Common/Properties/Resources.resx +++ b/src/Common/CodeCracker.Common/Properties/Resources.resx @@ -195,4 +195,64 @@ Change to auto property + + An empty catch block suppress all errors and shouldn't be used.\r\nIf the error is expected consider logging it or changing the control flow such that it is explicit. + + + Empty Catch Block. + + + Catch block cannot be empty + + + Insert Exception class to Catch + + + Remove Empty Catch Block + + + Remove Empty Catch Block and Put a Documentation Link about Try...Catch use + + + Remove wrapping Try Block + + + Getter only properties with backing read-only field can be converted to getter-only auto-properties. + + + Property {0} can be converted to an getter-only auto-property. + + + Property can be simplified by using an getter-only auto-property. + + + Simplify by using an getter-only auto-property + + + Consider introduce field for constructor parameters. + + + Introduce a field for parameter: {0} + + + Consider introduce field for constructor parameters. + + + Introduce field: {0} from constructor. + + + Introduce fields for constructor parameters. + + + Creating every time an instance of PropertyChangedEventArgs class causes unnecessary memory allocation. Instance can be created once and reused. + + + Create PropertyChangedEventArgs static instance and reuse it to avoid unecessary memory allocation. + + + PropertyChangedEventArgs unnecessary allocation + + + Create static PropertyChangedEventArgs instance and reuse + \ No newline at end of file diff --git a/src/Common/CodeCracker.Common/packages.config b/src/Common/CodeCracker.Common/packages.config deleted file mode 100644 index af80b93b5..000000000 --- a/src/Common/CodeCracker.Common/packages.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/VisualBasic/CodeCracker.Vsix/CodeCracker.Vsix.Debug.csproj b/src/VisualBasic/CodeCracker.Vsix/CodeCracker.Vsix.Debug.csproj new file mode 100644 index 000000000..55bfcbb15 --- /dev/null +++ b/src/VisualBasic/CodeCracker.Vsix/CodeCracker.Vsix.Debug.csproj @@ -0,0 +1,88 @@ + + + + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + Debug + AnyCPU + 2.0 + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {7F08D429-91E1-4B47-B3B2-A98754C8DFA7} + Library + Properties + CodeCracker + CodeCracker.VisualBasic + v4.5.2 + false + false + false + false + false + false + Roslyn + + + true + full + false + debug\bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + debug\bin\Release\ + TRACE + prompt + 4 + + + Program + $(DevEnvDir)devenv.exe + /rootsuffix Roslyn + + + + Designer + + + + + {753d4757-fcba-43ba-b1be-89201acda192} + CodeCracker.Common + + + {41fa4971-d354-4647-a269-4a886da2ef4c} + CodeCracker + + + + + Always + true + + + Always + true + + + Always + true + + + + + + \ No newline at end of file diff --git a/src/VisualBasic/CodeCracker.Vsix/CodeCracker.Vsix.csproj b/src/VisualBasic/CodeCracker.Vsix/CodeCracker.Vsix.csproj index dcdad50cf..0e643652c 100644 --- a/src/VisualBasic/CodeCracker.Vsix/CodeCracker.Vsix.csproj +++ b/src/VisualBasic/CodeCracker.Vsix/CodeCracker.Vsix.csproj @@ -1,7 +1,7 @@  - + - 14.0 + 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) @@ -81,4 +81,4 @@ --> - \ No newline at end of file + diff --git a/src/VisualBasic/CodeCracker.Vsix/LICENSE.txt b/src/VisualBasic/CodeCracker.Vsix/LICENSE.txt index 9d626bb93..2717e6ad3 100644 --- a/src/VisualBasic/CodeCracker.Vsix/LICENSE.txt +++ b/src/VisualBasic/CodeCracker.Vsix/LICENSE.txt @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2014-2015 Giovanni Bassi and Elemar Jr. + Copyright 2014-2018 Giovanni Bassi and Elemar Jr. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/VisualBasic/CodeCracker.Vsix/debug/source.extension.vsixmanifest b/src/VisualBasic/CodeCracker.Vsix/debug/source.extension.vsixmanifest new file mode 100644 index 000000000..0595693d0 --- /dev/null +++ b/src/VisualBasic/CodeCracker.Vsix/debug/source.extension.vsixmanifest @@ -0,0 +1,34 @@ + + + + + Code Cracker for Visual Basic + An analyzer library for VB that uses Roslyn to produce refactorings, code analysis, and other niceties. +Check the official project site on code-cracker.github.io. +Build status Nuget count Nuget downloads Issues open +This is a community project, free and open source. Everyone is invited to contribute, fork, share and use the code. No money shall be charged by this software, nor it will be. Ever. + http://code-cracker.github.io/ + LICENSE.txt + http://code-cracker.github.io/changelog.html + codecrackerlogo.png + ccexample.png + ReSharper, Refactoring, code analysis, analyzer, CodeRush, roslyn, roslyn c#, Refactoring c# Roslyn + + + + + + + + + + + + + + + + + + + diff --git a/src/VisualBasic/CodeCracker.Vsix/source.extension.vsixmanifest b/src/VisualBasic/CodeCracker.Vsix/source.extension.vsixmanifest index 787120813..01fa41c09 100644 --- a/src/VisualBasic/CodeCracker.Vsix/source.extension.vsixmanifest +++ b/src/VisualBasic/CodeCracker.Vsix/source.extension.vsixmanifest @@ -1,7 +1,7 @@  - + Code Cracker for Visual Basic An analyzer library for VB that uses Roslyn to produce refactorings, code analysis, and other niceties. Check the official project site on code-cracker.github.io. @@ -9,6 +9,7 @@ Build status Nuget count Nuget downloads Issues open This is a community project, free and open source. Everyone is invited to contribute, fork, share and use the code. No money shall be charged by this software, nor it will be. Ever. http://code-cracker.github.io/ LICENSE.txt + http://code-cracker.github.io/changelog.html codecrackerlogo.png ccexample.png ReSharper, Refactoring, code analysis, analyzer, CodeRush, roslyn, roslyn c#, Refactoring c# Roslyn @@ -25,4 +26,8 @@ This is a community project, free and open source. Everyone is invited to contri + + + + diff --git a/src/VisualBasic/CodeCracker/CodeCracker.nuspec b/src/VisualBasic/CodeCracker/CodeCracker.nuspec index 8a9f9a7be..8c35dc85b 100644 --- a/src/VisualBasic/CodeCracker/CodeCracker.nuspec +++ b/src/VisualBasic/CodeCracker/CodeCracker.nuspec @@ -2,8 +2,8 @@ codecracker.VisualBasic - 1.0.0-rc3 - CodeCracker + 1.1.0 + CodeCracker for Visual Basic giggio,elemarjr,carloscds giggio,elemarjr,carloscds https://github.com/code-cracker/code-cracker/blob/master/LICENSE.txt @@ -13,15 +13,15 @@ A analyzer library for Visual Basic that uses Roslyn to produce refactorings, code analysis, and other niceties. This is a community project, free and open source. Everyone is invited to contribute, fork, share and use the code. No money shall be charged by this software, nor it will be. Ever. - Third alpha release - Copyright CodeCracker 2014-2015 + See https://github.com/code-cracker/code-cracker/blob/master/CHANGELOG.md + Copyright CodeCracker 2014-2016 roslyn, analyzers - + diff --git a/src/VisualBasic/CodeCracker/CodeCracker.vbproj b/src/VisualBasic/CodeCracker/CodeCracker.vbproj index ee0f6d02e..877bee09b 100644 --- a/src/VisualBasic/CodeCracker/CodeCracker.vbproj +++ b/src/VisualBasic/CodeCracker/CodeCracker.vbproj @@ -1,5 +1,5 @@  - + 11.0 @@ -9,10 +9,12 @@ Library CodeCracker.VisualBasic CodeCracker.VisualBasic + CodeCracker.VisualBasic.NewIdRequiredDueToNuGetBug en-US {14182A97-F7F0-4C62-8B27-98AA8AE2109A};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} Profile7 v4.5 + AD0001,RS1010,RS1016,RS1017,RS1022 true @@ -53,10 +55,18 @@ On + + win + true + + + + + + - PreserveNewest @@ -139,6 +149,7 @@ + @@ -157,64 +168,5 @@ CodeCracker.Common - - - - - - - - - ..\..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll - False - - - ..\..\..\packages\Microsoft.CodeAnalysis.VisualBasic.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.VisualBasic.dll - False - - - ..\..\..\packages\Microsoft.CodeAnalysis.VisualBasic.Workspaces.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll - False - - - ..\..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.Workspaces.dll - False - - - ..\..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll - False - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll - False - - - ..\..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll - False - - - \ No newline at end of file diff --git a/src/VisualBasic/CodeCracker/Design/CatchEmptyAnalyzer.vb b/src/VisualBasic/CodeCracker/Design/CatchEmptyAnalyzer.vb index 132345083..e4f1b7c9c 100644 --- a/src/VisualBasic/CodeCracker/Design/CatchEmptyAnalyzer.vb +++ b/src/VisualBasic/CodeCracker/Design/CatchEmptyAnalyzer.vb @@ -9,7 +9,7 @@ Namespace Design Inherits DiagnosticAnalyzer Public Shared ReadOnly Id As String = DiagnosticId.CatchEmpty.ToDiagnosticId() - Public Const Title As String = "Your catch may includes some Exception" + Public Const Title As String = "Your catch should include an Exception" Public Const MessageFormat As String = "{0}" Public Const Category As String = SupportedCategories.Design Protected Shared Rule As DiagnosticDescriptor = New DiagnosticDescriptor( @@ -33,7 +33,7 @@ Namespace Design If catchStatement Is Nothing Then Exit Sub If catchStatement.IdentifierName Is Nothing Then - Dim diag = Diagnostic.Create(Rule, catchStatement.GetLocation(), "Consider including an Exception Class in catch.") + Dim diag = Diagnostic.Create(Rule, catchStatement.GetLocation(), "Consider adding an Exception to the catch.") context.ReportDiagnostic(diag) End If End Sub diff --git a/src/VisualBasic/CodeCracker/Design/CatchEmptyCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Design/CatchEmptyCodeFixProvider.vb index 909a8adb9..ecbb7a6ca 100644 --- a/src/VisualBasic/CodeCracker/Design/CatchEmptyCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Design/CatchEmptyCodeFixProvider.vb @@ -18,15 +18,16 @@ Namespace Design Return WellKnownFixAllProviders.BatchFixer End Function - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diag = context.Diagnostics.First() - Dim diagSpan = diag.Location.SourceSpan - Dim declaration = root.FindToken(diagSpan.Start).Parent.AncestorsAndSelf.OfType(Of CatchBlockSyntax).First() - context.RegisterCodeFix(CodeAction.Create("Add an Exception class", Function(c) MakeCatchEmptyAsync(context.Document, declaration, c), NameOf(CatchEmptyCodeFixProvider)), diag) + context.RegisterCodeFix(CodeAction.Create("Add an Exception class", Function(c) MakeCatchEmptyAsync(context.Document, diag, c), NameOf(CatchEmptyCodeFixProvider)), diag) + Return Task.FromResult(0) End Function - Private Async Function MakeCatchEmptyAsync(document As Document, catchStatement As CatchBlockSyntax, cancellationtoken As CancellationToken) As Task(Of Document) + Private Async Function MakeCatchEmptyAsync(document As Document, diag As Diagnostic, cancellationtoken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationtoken).ConfigureAwait(False) + Dim diagSpan = diag.Location.SourceSpan + Dim catchStatement = root.FindToken(diagSpan.Start).Parent.AncestorsAndSelf.OfType(Of CatchBlockSyntax).First() Dim semanticModel = Await document.GetSemanticModelAsync(cancellationtoken) Dim newCatch = SyntaxFactory.CatchBlock( @@ -39,7 +40,6 @@ Namespace Design WithTrailingTrivia(catchStatement.GetTrailingTrivia). WithAdditionalAnnotations(Formatter.Annotation) - Dim root = Await document.GetSyntaxRootAsync() Dim newRoot = root.ReplaceNode(catchStatement, newCatch) Dim newDoc = document.WithSyntaxRoot(newRoot) Return newDoc diff --git a/src/VisualBasic/CodeCracker/Design/EmptyCatchBlockCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Design/EmptyCatchBlockCodeFixProvider.vb index 150d6bd0c..628eb1962 100644 --- a/src/VisualBasic/CodeCracker/Design/EmptyCatchBlockCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Design/EmptyCatchBlockCodeFixProvider.vb @@ -6,13 +6,18 @@ Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports System.Collections.Immutable Imports System.Threading +Imports CCProp = CodeCracker.Properties Namespace Design Public Class EmptyCatchBlockCodeFixProvider Inherits CodeFixProvider - Public Overrides NotOverridable ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(DiagnosticId.EmptyCatchBlock.ToDiagnosticId()) + Friend Shared ReadOnly FixRemoveEmptyCatchBlock As New LocalizableResourceString(NameOf(CCProp.Resources.EmptyCatchBlockCodeFixProvider_Remove), CCProp.Resources.ResourceManager, GetType(CCProp.Resources)) + Friend Shared ReadOnly FixInsertExceptionClass As New LocalizableResourceString(NameOf(CCProp.Resources.EmptyCatchBlockCodeFixProvider_InsertException), CCProp.Resources.ResourceManager, GetType(CCProp.Resources)) + Friend Shared ReadOnly FixRemoveTry As New LocalizableResourceString(NameOf(CCProp.Resources.EmptyCatchBlockCodeFixProvider_RemoveTry), CCProp.Resources.ResourceManager, GetType(CCProp.Resources)) + + Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(DiagnosticId.EmptyCatchBlock.ToDiagnosticId()) Public Overrides Function GetFixAllProvider() As FixAllProvider Return WellKnownFixAllProviders.BatchFixer @@ -23,8 +28,23 @@ Namespace Design Dim diag = context.Diagnostics.First Dim diagSpan = diag.Location.SourceSpan Dim declaration = root.FindToken(diagSpan.Start).Parent.AncestorsAndSelf.OfType(Of CatchBlockSyntax).First - context.RegisterCodeFix(CodeAction.Create("Remove Empty Catch Block", Function(c) RemoveTry(context.Document, declaration, c), NameOf(EmptyCatchBlockCodeFixProvider) & NameOf(RemoveTry)), diag) - context.RegisterCodeFix(CodeAction.Create("Insert Exception class to Catch", Function(c) InsertExceptionClassCommentAsync(context.Document, declaration, c), NameOf(EmptyCatchBlockCodeFixProvider) & NameOf(InsertExceptionClassCommentAsync)), diag) + + Dim tryBlock = DirectCast(declaration.Parent, TryBlockSyntax) + If tryBlock.CatchBlocks.Count > 1 Then + context.RegisterCodeFix(CodeAction.Create(FixRemoveEmptyCatchBlock.ToString(), Function(c) RemoveCatch(context.Document, declaration, c), NameOf(EmptyCatchBlockCodeFixProvider) & NameOf(RemoveTry)), diag) + Else + context.RegisterCodeFix(CodeAction.Create(FixRemoveTry.ToString(), Function(c) RemoveTry(context.Document, declaration, c), NameOf(EmptyCatchBlockCodeFixProvider) & NameOf(RemoveTry)), diag) + End If + context.RegisterCodeFix(CodeAction.Create(FixInsertExceptionClass.ToString(), Function(c) InsertExceptionClassCommentAsync(context.Document, declaration, c), NameOf(EmptyCatchBlockCodeFixProvider) & NameOf(InsertExceptionClassCommentAsync)), diag) + End Function + + Private Async Function RemoveCatch(document As Document, catchBlock As CatchBlockSyntax, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + + Dim newRoot = root.RemoveNode(catchBlock, SyntaxRemoveOptions.KeepNoTrivia) + + Dim newDocument = document.WithSyntaxRoot(newRoot) + Return newDocument End Function Private Async Function RemoveTry(document As Document, catchBlock As CatchBlockSyntax, cancellationToken As CancellationToken) As Task(Of Document) diff --git a/src/VisualBasic/CodeCracker/Design/NameOfAnalyzer.vb b/src/VisualBasic/CodeCracker/Design/NameOfAnalyzer.vb index 6467ecbf7..482eceea5 100644 --- a/src/VisualBasic/CodeCracker/Design/NameOfAnalyzer.vb +++ b/src/VisualBasic/CodeCracker/Design/NameOfAnalyzer.vb @@ -24,8 +24,17 @@ Namespace Design isEnabledByDefault:=True, description:=Description, helpLinkUri:=HelpLink.ForDiagnostic(DiagnosticId.NameOf)) + Protected Shared RuleExtenal As DiagnosticDescriptor = New DiagnosticDescriptor( + DiagnosticId.NameOf_External.ToDiagnosticId(), + Title, + MessageFormat, + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault:=True, + description:=Description, + helpLinkUri:=HelpLink.ForDiagnostic(DiagnosticId.NameOf_External)) - Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) = ImmutableArray.Create(Rule) + Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) = ImmutableArray.Create(Rule, RuleExtenal) Public Overrides Sub Initialize(context As AnalysisContext) context.RegisterSyntaxNodeAction(LanguageVersion.VisualBasic14, AddressOf Analyzer, SyntaxKind.StringLiteralExpression) @@ -36,32 +45,35 @@ Namespace Design Dim stringLiteral = DirectCast(context.Node, LiteralExpressionSyntax) If String.IsNullOrWhiteSpace(stringLiteral?.Token.ValueText) Then Return - Dim programElementName = GetProgramElementNameThatMatchesStringLiteral(stringLiteral, context.SemanticModel) + Dim externalSymbol = False + Dim programElementName = GetProgramElementNameThatMatchesStringLiteral(stringLiteral, context.SemanticModel, externalSymbol) If (Found(programElementName)) Then - Dim diag = Diagnostic.Create(Rule, stringLiteral.GetLocation(), programElementName) + Dim diag = Diagnostic.Create(If(externalSymbol, RuleExtenal, Rule), stringLiteral.GetLocation(), programElementName) context.ReportDiagnostic(diag) End If End Sub - Private Shared Function GetProgramElementNameThatMatchesStringLiteral(stringLiteral As LiteralExpressionSyntax, model As SemanticModel) As String + Private Shared Function GetProgramElementNameThatMatchesStringLiteral(stringLiteral As LiteralExpressionSyntax, model As SemanticModel, ByRef externalSymbol As Boolean) As String Dim programElementName = GetParameterNameThatMatchesStringLiteral(stringLiteral) If Not Found(programElementName) Then Dim literalValueText = stringLiteral.Token.ValueText Dim symbol = model.LookupSymbols(stringLiteral.Token.SpanStart, Nothing, literalValueText).FirstOrDefault() - If symbol?.Kind = SymbolKind.Local Then + If symbol Is Nothing Then Return Nothing + externalSymbol = symbol.Locations.Any(Function(l) l.IsInSource) = False + If symbol.Kind = SymbolKind.Local Then ' Only register if local variable is declared before it is used. ' Don't recommend if variable is declared after string literal is used. Dim symbolSpan = symbol.Locations.Min(Function(i) i.SourceSpan) If symbolSpan.CompareTo(stringLiteral.Token.Span) > 0 Then - Return String.Empty + Return Nothing End If End If programElementName = symbol?.ToDisplayParts(). - Where(AddressOf IncludeOnlyPartsThatAreName). - LastOrDefault(Function(displayPart) displayPart.ToString() = literalValueText). - ToString() - End If - Return programElementName + Where(AddressOf IncludeOnlyPartsThatAreName). + LastOrDefault(Function(displayPart) displayPart.ToString() = literalValueText). + ToString() + End If + Return programElementName End Function Private Shared Function GetParameterNameThatMatchesStringLiteral(stringLiteral As LiteralExpressionSyntax) As String diff --git a/src/VisualBasic/CodeCracker/Design/NameOfCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Design/NameOfCodeFixProvider.vb index 706d77a42..41d633b51 100644 --- a/src/VisualBasic/CodeCracker/Design/NameOfCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Design/NameOfCodeFixProvider.vb @@ -1,4 +1,5 @@ -Imports System.Collections.Immutable +Imports CodeCracker.Properties +Imports System.Collections.Immutable Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeActions @@ -12,30 +13,30 @@ Namespace Design Public Class NameOfCodeFixProvider Inherits CodeFixProvider - Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(NameOfAnalyzer.Id) + Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(NameOfAnalyzer.Id, DiagnosticId.NameOf_External.ToDiagnosticId()) Public Overrides Function GetFixAllProvider() As FixAllProvider Return WellKnownFixAllProviders.BatchFixer End Function - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First + context.RegisterCodeFix(CodeAction.Create(Resources.NameOfAnalyzer_Title, Function(c) MakeNameOf(context.Document, diagnostic, c), NameOf(NameOfCodeFixProvider)), diagnostic) + Return Task.FromResult(0) + End Function + + Private Async Function MakeNameOf(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) Dim diagnosticspan = diagnostic.Location.SourceSpan Dim stringLiteral = root.FindToken(diagnosticspan.Start).Parent.AncestorsAndSelf.OfType(Of LiteralExpressionSyntax).FirstOrDefault - If stringLiteral IsNot Nothing Then - context.RegisterCodeFix(CodeAction.Create("use NameOf()", Function(c) MakeNameOf(context.Document, stringLiteral, root, diagnostic, c), NameOf(NameOfCodeFixProvider)), diagnostic) - End If - End Function - Private Function MakeNameOf(document As Document, stringLiteral As LiteralExpressionSyntax, root As SyntaxNode, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) Dim newNameof = SyntaxFactory.ParseExpression($"NameOf({stringLiteral.Token.ToString().Replace("""", "")})"). WithLeadingTrivia(stringLiteral.GetLeadingTrivia). WithTrailingTrivia(stringLiteral.GetTrailingTrivia). WithAdditionalAnnotations(Formatter.Annotation) Dim newRoot = root.ReplaceNode(stringLiteral, newNameof) - Return Task.FromResult(document.WithSyntaxRoot(newRoot)) + Return document.WithSyntaxRoot(newRoot) End Function End Class diff --git a/src/VisualBasic/CodeCracker/Design/StaticConstructorExceptionAnalyzer.vb b/src/VisualBasic/CodeCracker/Design/StaticConstructorExceptionAnalyzer.vb index 72799cbd9..3cfaab1f3 100644 --- a/src/VisualBasic/CodeCracker/Design/StaticConstructorExceptionAnalyzer.vb +++ b/src/VisualBasic/CodeCracker/Design/StaticConstructorExceptionAnalyzer.vb @@ -9,11 +9,11 @@ Namespace Design Inherits DiagnosticAnalyzer Public Shared ReadOnly Id As String = DiagnosticId.StaticConstructorException.ToDiagnosticId() - Public Const Title As String = "Don't throw exception inside static constructors." + Public Const Title As String = "Don't throw exceptions inside static constructors." Public Const MessageFormat As String = "Don't throw exceptions inside static constructors." Public Const Category As String = SupportedCategories.Design - Public Const Description As String = "Static constructor are called before the first time a class is used but the caller doesn't control when exactly. -Exception thrown in this context forces callers to use 'try' block around any useage of the class and should be avoided." + Public Const Description As String = "Static constructors are called before a class is used for the first time. Exceptions thrown + in static constructors force the use of a try block and should be avoided." Protected Shared Rule As DiagnosticDescriptor = New DiagnosticDescriptor( Id, Title, diff --git a/src/VisualBasic/CodeCracker/Design/StaticConstructorExceptionCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Design/StaticConstructorExceptionCodeFixProvider.vb index 8feb23984..5293269e7 100644 --- a/src/VisualBasic/CodeCracker/Design/StaticConstructorExceptionCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Design/StaticConstructorExceptionCodeFixProvider.vb @@ -16,15 +16,17 @@ Namespace Design Return WellKnownFixAllProviders.BatchFixer End Function - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First + context.RegisterCodeFix(CodeAction.Create("Remove this exception", Function(ct) RemoveThrow(context.Document, diagnostic, ct), NameOf(StaticConstructorExceptionCodeFixProvider)), diagnostic) + Return Task.FromResult(0) + End Function + + Private Async Function RemoveThrow(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) Dim sourceSpan = diagnostic.Location.SourceSpan Dim throwBlock = root.FindToken(sourceSpan.Start).Parent.AncestorsAndSelf.OfType(Of ThrowStatementSyntax).First - context.RegisterCodeFix(CodeAction.Create("Remove this exception", Function(ct) RemoveThrow(context.Document, throwBlock, ct), NameOf(StaticConstructorExceptionCodeFixProvider)), diagnostic) - End Function - Private Async Function RemoveThrow(document As Document, throwBlock As ThrowStatementSyntax, cancellationToken As CancellationToken) As Task(Of Document) Return document.WithSyntaxRoot((Await document.GetSyntaxRootAsync(cancellationToken)).RemoveNode(throwBlock, SyntaxRemoveOptions.KeepNoTrivia)) End Function End Class diff --git a/src/VisualBasic/CodeCracker/Extensions/VBAnalyzerExtensions.vb b/src/VisualBasic/CodeCracker/Extensions/VBAnalyzerExtensions.vb index f7db5bcb3..d7fe23787 100644 --- a/src/VisualBasic/CodeCracker/Extensions/VBAnalyzerExtensions.vb +++ b/src/VisualBasic/CodeCracker/Extensions/VBAnalyzerExtensions.vb @@ -5,6 +5,103 @@ Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Public Module VBAnalyzerExtensions + + + Public Function GetCommonBaseType(source As ITypeSymbol, other As ITypeSymbol) As ITypeSymbol + If source Is Nothing AndAlso other IsNot Nothing Then + Return other + End If + If source IsNot Nothing AndAlso other Is Nothing Then + Return source + End If + + Dim baseType = source + While baseType IsNot Nothing + Dim otherBaseType = other + While otherBaseType IsNot Nothing + If baseType.Equals(otherBaseType) Then Return baseType + otherBaseType = otherBaseType.BaseType + End While + baseType = baseType.BaseType + End While + Return Nothing + End Function + + + Public Function CanBeAssignedTo(source As ITypeSymbol, targetType As ITypeSymbol) As Boolean + If source Is Nothing OrElse targetType Is Nothing Then Return True + If source.Kind = SymbolKind.ErrorType OrElse targetType.Kind = SymbolKind.ErrorType Then Return True + + Dim baseType = source + While baseType IsNot Nothing AndAlso baseType.SpecialType <> SpecialType.System_Object + If baseType.Equals(targetType) Then Return True + baseType = baseType.BaseType + End While + Return False + End Function + + + Public Function ConvertToBaseType(source As ExpressionSyntax, sourceType As ITypeSymbol, targetType As ITypeSymbol) As ExpressionSyntax + If (sourceType?.IsNumeric() AndAlso targetType?.IsNumeric()) OrElse + (sourceType?.BaseType?.SpecialType = SpecialType.System_Enum AndAlso targetType?.IsNumeric()) OrElse + (targetType?.OriginalDefinition.SpecialType = SpecialType.System_Nullable_T) Then Return source + Return If(sourceType IsNot Nothing AndAlso sourceType.Name = targetType.Name, source, SyntaxFactory.DirectCastExpression(source.WithoutTrailingTrivia, SyntaxFactory.ParseTypeName(targetType.Name))).WithTrailingTrivia(source.GetTrailingTrivia()) + End Function + + + Public Function IsNumeric(typeSymbol As ITypeSymbol) As Boolean + Return typeSymbol.SpecialType = SpecialType.System_Byte OrElse + typeSymbol.SpecialType = SpecialType.System_SByte OrElse + typeSymbol.SpecialType = SpecialType.System_Int16 OrElse + typeSymbol.SpecialType = SpecialType.System_UInt16 OrElse + typeSymbol.SpecialType = SpecialType.System_Int16 OrElse + typeSymbol.SpecialType = SpecialType.System_UInt32 OrElse + typeSymbol.SpecialType = SpecialType.System_Int32 OrElse + typeSymbol.SpecialType = SpecialType.System_UInt64 OrElse + typeSymbol.SpecialType = SpecialType.System_Int64 OrElse + typeSymbol.SpecialType = SpecialType.System_Decimal OrElse + typeSymbol.SpecialType = SpecialType.System_Single OrElse + typeSymbol.SpecialType = SpecialType.System_Double + End Function + + + Public Function EnsureNothingAsType(expression As ExpressionSyntax, semanticModel As SemanticModel, type As ITypeSymbol, typeSyntax As TypeSyntax) As ExpressionSyntax + If type?.OriginalDefinition.SpecialType = SpecialType.System_Nullable_T Then + Dim constValue = semanticModel.GetConstantValue(expression) + If constValue.HasValue AndAlso constValue.Value Is Nothing Then + Return SyntaxFactory.DirectCastExpression(expression.WithoutTrailingTrivia(), typeSyntax) + End If + End If + + Return expression + End Function + + + Public Function ExtractAssignmentAsExpressionSyntax(expression As AssignmentStatementSyntax) As ExpressionSyntax + Select Case expression.Kind + Case SyntaxKind.AddAssignmentStatement + Return SyntaxFactory.AddExpression(expression.Left, expression.Right) + Case SyntaxKind.SubtractAssignmentStatement + Return SyntaxFactory.SubtractExpression(expression.Left, expression.Right) + Case SyntaxKind.ConcatenateAssignmentStatement + Return SyntaxFactory.ConcatenateExpression(expression.Left, expression.Right) + Case SyntaxKind.DivideAssignmentStatement + Return SyntaxFactory.DivideExpression(expression.Left, expression.Right) + Case SyntaxKind.ExponentiateAssignmentStatement + Return SyntaxFactory.ExponentiateExpression(expression.Left, expression.Right) + Case SyntaxKind.IntegerDivideAssignmentStatement + Return SyntaxFactory.IntegerDivideExpression(expression.Left, expression.Right) + Case SyntaxKind.LeftShiftAssignmentStatement + Return SyntaxFactory.LeftShiftExpression(expression.Left, expression.Right) + Case SyntaxKind.MultiplyAssignmentStatement + Return SyntaxFactory.MultiplyExpression(expression.Left, expression.Right) + Case SyntaxKind.RightShiftAssignmentStatement + Return SyntaxFactory.RightShiftExpression(expression.Left, expression.Right) + Case Else + Return expression.Right + End Select + End Function + Public Sub RegisterSyntaxNodeAction(Of TLanguageKindEnum As Structure)(context As AnalysisContext, languageVersion As LanguageVersion, action As Action(Of SyntaxNodeAnalysisContext), ParamArray syntaxKinds As TLanguageKindEnum()) context.RegisterCompilationStartAction(languageVersion, Sub(compilationContext) compilationContext.RegisterSyntaxNodeAction(action, syntaxKinds)) End Sub @@ -36,7 +133,7 @@ Public Module VBAnalyzerExtensions End Sub Public Sub RunWithVB14OrGreater(languageVersion As LanguageVersion, action As Action) - languageVersion.RunWithVBVersionOrGreater(action, languageVersion.VisualBasic14) + languageVersion.RunWithVBVersionOrGreater(action, LanguageVersion.VisualBasic14) End Sub Public Sub RunWithVBVersionOrGreater(languageVersion As LanguageVersion, action As Action, greaterOrEqualThanLanguageVersion As LanguageVersion) diff --git a/src/VisualBasic/CodeCracker/My Project/AssemblyInfo.vb b/src/VisualBasic/CodeCracker/My Project/AssemblyInfo.vb index 6360f51c1..89f5f1675 100644 --- a/src/VisualBasic/CodeCracker/My Project/AssemblyInfo.vb +++ b/src/VisualBasic/CodeCracker/My Project/AssemblyInfo.vb @@ -8,11 +8,11 @@ Imports System.Runtime.InteropServices - + - - \ No newline at end of file + + diff --git a/src/VisualBasic/CodeCracker/Performance/MakeLocalVariableConstWhenPossibleCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Performance/MakeLocalVariableConstWhenPossibleCodeFixProvider.vb index 31ced6241..4d2d4e094 100644 --- a/src/VisualBasic/CodeCracker/Performance/MakeLocalVariableConstWhenPossibleCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Performance/MakeLocalVariableConstWhenPossibleCodeFixProvider.vb @@ -18,17 +18,18 @@ Namespace Performance Return WellKnownFixAllProviders.BatchFixer End Function - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First() - Dim diagnosticSpan = diagnostic.Location.SourceSpan - Dim localDeclaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType(Of LocalDeclarationStatementSyntax).First() Const message = "Make constant" - context.RegisterCodeFix(CodeAction.Create(message, Function(c) MakeConstantAsync(context.Document, localDeclaration, c), NameOf(MakeLocalVariableConstWhenPossibleCodeFixProvider)), diagnostic) - + context.RegisterCodeFix(CodeAction.Create(message, Function(c) MakeConstantAsync(context.Document, diagnostic, c), NameOf(MakeLocalVariableConstWhenPossibleCodeFixProvider)), diagnostic) + Return Task.FromResult(0) End Function - Public Async Function MakeConstantAsync(document As Document, localDeclaration As LocalDeclarationStatementSyntax, cancellationToken As CancellationToken) As Task(Of Document) + Public Async Function MakeConstantAsync(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim diagnosticSpan = diagnostic.Location.SourceSpan + Dim localDeclaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType(Of LocalDeclarationStatementSyntax).First() + Dim declaration = localDeclaration.Declarators.First Dim dimModifier = localDeclaration.Modifiers.First() @@ -46,7 +47,6 @@ Namespace Performance WithTrailingTrivia(localDeclaration.GetTrailingTrivia()). WithAdditionalAnnotations(Formatter.Annotation) - Dim root = Await document.GetSyntaxRootAsync(cancellationToken) Dim newRoot = root.ReplaceNode(localDeclaration, newLocalDeclaration) Return document.WithSyntaxRoot(newRoot) End Function diff --git a/src/VisualBasic/CodeCracker/Performance/RemoveWhereWhenItIsPossibleAnalyzer.vb b/src/VisualBasic/CodeCracker/Performance/RemoveWhereWhenItIsPossibleAnalyzer.vb index 0da0cf67c..584604eb5 100644 --- a/src/VisualBasic/CodeCracker/Performance/RemoveWhereWhenItIsPossibleAnalyzer.vb +++ b/src/VisualBasic/CodeCracker/Performance/RemoveWhereWhenItIsPossibleAnalyzer.vb @@ -43,8 +43,8 @@ Namespace Performance If Not supportedMethods.Contains(candidate) Then Exit Sub If nextMethodInvoke.ArgumentList.Arguments.Any Then Return - - Dim diag = Diagnostic.Create(Rule, GetNameExpressionOfTheInvokedMethod(whereInvoke).GetLocation(), candidate) + Dim props = New Dictionary(Of String, String) From {{"methodName", candidate}}.ToImmutableDictionary() + Dim diag = Diagnostic.Create(Rule, GetNameExpressionOfTheInvokedMethod(whereInvoke).GetLocation(), props, candidate) context.ReportDiagnostic(diag) End Sub diff --git a/src/VisualBasic/CodeCracker/Performance/RemoveWhereWhenItIsPossibleCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Performance/RemoveWhereWhenItIsPossibleCodeFixProvider.vb index 0887c0dbb..dc5eb5fd3 100644 --- a/src/VisualBasic/CodeCracker/Performance/RemoveWhereWhenItIsPossibleCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Performance/RemoveWhereWhenItIsPossibleCodeFixProvider.vb @@ -17,18 +17,20 @@ Namespace Performance Return WellKnownFixAllProviders.BatchFixer End Function - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First + Dim name = diagnostic.Properties!methodName + Dim message = $"Remove 'Where' moving predicate to '{name}'" + context.RegisterCodeFix(CodeAction.Create(message, Function(c) RemoveWhere(context.Document, diagnostic, c), NameOf(RemoveWhereWhenItIsPossibleCodeFixProvider)), diagnostic) + Return Task.FromResult(0) + End Function + + Private Async Function RemoveWhere(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) Dim diagnosticSpan = diagnostic.Location.SourceSpan Dim whereInvoke = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType(Of InvocationExpressionSyntax)().First() Dim nextMethodInvoke = whereInvoke.Parent.FirstAncestorOrSelf(Of InvocationExpressionSyntax)() - Dim message = "Remove 'Where' moving predicate to '" + RemoveWhereWhenItIsPossibleAnalyzer.GetNameOfTheInvokeMethod(nextMethodInvoke) + "'" - context.RegisterCodeFix(CodeAction.Create(message, Function(c) RemoveWhere(context.Document, whereInvoke, nextMethodInvoke, c), NameOf(RemoveWhereWhenItIsPossibleCodeFixProvider)), diagnostic) - End Function - Private Async Function RemoveWhere(document As Document, whereInvoke As InvocationExpressionSyntax, nextMethodInvoke As InvocationExpressionSyntax, cancellationToken As CancellationToken) As Task(Of Document) - Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) Dim whereMemberAccess = whereInvoke.ChildNodes.OfType(Of MemberAccessExpressionSyntax)().FirstOrDefault() Dim nextMethodMemberAccess = nextMethodInvoke.ChildNodes.OfType(Of MemberAccessExpressionSyntax)().FirstOrDefault() diff --git a/src/VisualBasic/CodeCracker/Performance/SealedAttributeCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Performance/SealedAttributeCodeFixProvider.vb index 7f312ee93..966b28bca 100644 --- a/src/VisualBasic/CodeCracker/Performance/SealedAttributeCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Performance/SealedAttributeCodeFixProvider.vb @@ -4,6 +4,7 @@ Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.CodeActions Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports System.Threading Namespace Performance @@ -21,10 +22,14 @@ Namespace Performance Dim diag = context.Diagnostics.First() Dim sourceSpan = diag.Location.SourceSpan Dim type = root.FindToken(sourceSpan.Start).Parent.AncestorsAndSelf().OfType(Of Microsoft.CodeAnalysis.VisualBasic.Syntax.ClassStatementSyntax)().First() - context.RegisterCodeFix(CodeAction.Create("Mark as NotInheritable", Function(ct) MarkClassAsSealed(context.Document, type, ct), NameOf(SealedAttributeCodeFixProvider)), diag) + context.RegisterCodeFix(CodeAction.Create("Mark as NotInheritable", Function(ct) MarkClassAsSealed(context.Document, diag, ct), NameOf(SealedAttributeCodeFixProvider)), diag) End Function - Private Async Function MarkClassAsSealed(document As Document, type As ClassStatementSyntax, cancellationToken As Threading.CancellationToken) As Task(Of Document) + Private Async Function MarkClassAsSealed(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim sourceSpan = diagnostic.Location.SourceSpan + Dim type = root.FindToken(sourceSpan.Start).Parent.AncestorsAndSelf().OfType(Of Microsoft.CodeAnalysis.VisualBasic.Syntax.ClassStatementSyntax)().First() + Return document. WithSyntaxRoot((Await document.GetSyntaxRootAsync(cancellationToken)). ReplaceNode(type, diff --git a/src/VisualBasic/CodeCracker/Performance/StringBuilderInLoopAnalyzer.vb b/src/VisualBasic/CodeCracker/Performance/StringBuilderInLoopAnalyzer.vb index 054feb557..36c1a1ac1 100644 --- a/src/VisualBasic/CodeCracker/Performance/StringBuilderInLoopAnalyzer.vb +++ b/src/VisualBasic/CodeCracker/Performance/StringBuilderInLoopAnalyzer.vb @@ -13,7 +13,7 @@ Namespace Performance Public Const Title As String = "Don't concatenate strings in loops" Public Const MessageFormat As String = "Don't concatenate '{0}' in a loop." Public Const Category As String = SupportedCategories.Performance - Public Const Description As String = "Do not concatenate a string in a loop. It will allocate a lot of memory. Use a StringBuilder instead. It will require less allocation, less garbage collection work, less CPU cycles, and less overall time." + Public Const Description As String = "Don't concatenate strings in a loop. Using a StringBuilder will require less memory and time." Protected Shared Rule As DiagnosticDescriptor = New DiagnosticDescriptor( Id, Title, @@ -65,8 +65,10 @@ Namespace Performance Exit Sub End If - Dim diag = Diagnostic.Create(Rule, assignmentExpression.GetLocation(), assignmentExpression.Left.ToString()) + Dim assignmentExpressionLeft = assignmentExpression.Left.ToString() + Dim props = New Dictionary(Of String, String) From {{NameOf(assignmentExpressionLeft), assignmentExpressionLeft}}.ToImmutableDictionary() + Dim diag = Diagnostic.Create(Rule, assignmentExpression.GetLocation(), props, assignmentExpression.Left.ToString()) context.ReportDiagnostic(diag) End Sub End Class -End Namespace \ No newline at end of file +End Namespace diff --git a/src/VisualBasic/CodeCracker/Performance/StringBuilderInLoopCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Performance/StringBuilderInLoopCodeFixProvider.vb index 47317fbcd..7475456dd 100644 --- a/src/VisualBasic/CodeCracker/Performance/StringBuilderInLoopCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Performance/StringBuilderInLoopCodeFixProvider.vb @@ -19,27 +19,28 @@ Namespace Performance Return Nothing End Function - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First + context.RegisterCodeFix(CodeAction.Create($"Use StringBuilder to create a value for '{diagnostic.Properties!assignmentExpressionLeft}'", Function(c) UseStringBuilder(context.Document, diagnostic, c), NameOf(StringBuilderInLoopCodeFixProvider)), diagnostic) + Return Task.FromResult(0) + End Function + + Private Async Function UseStringBuilder(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) Dim diagosticSpan = diagnostic.Location.SourceSpan - Dim assignmentExpression = root.FindToken(diagosticSpan.Start).Parent.AncestorsAndSelf.OfType(Of AssignmentStatementSyntax).First - context.RegisterCodeFix(CodeAction.Create("Use StringBuilder to create a value for " & assignmentExpression.Left.ToString(), Function(c) UseStringBuilder(context.Document, assignmentExpression, c), NameOf(StringBuilderInLoopCodeFixProvider)), diagnostic) + Dim expressionStatement = root.FindToken(diagosticSpan.Start).Parent.AncestorsAndSelf.OfType(Of AssignmentStatementSyntax).First - End Function - Private Async Function UseStringBuilder(document As Document, assignmentStatement As AssignmentStatementSyntax, cancellationToken As CancellationToken) As Task(Of Document) - Dim expressionStatement = assignmentStatement Dim expressionStatementParent = expressionStatement.Parent Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken) - Dim builderName = FindAvailableStringBuilderVariableName(assignmentStatement, semanticModel) + Dim builderName = FindAvailableStringBuilderVariableName(expressionStatement, semanticModel) Dim loopStatement = expressionStatement.FirstAncestorOrSelfOfType( GetType(WhileBlockSyntax), GetType(ForBlockSyntax), GetType(ForEachBlockSyntax), GetType(DoLoopBlockSyntax)) - Dim newExpressionStatementParent = ReplaceAddExpressionByStringBuilderAppendExpression(assignmentStatement, expressionStatement, expressionStatementParent, builderName) + Dim newExpressionStatementParent = ReplaceAddExpressionByStringBuilderAppendExpression(expressionStatement, expressionStatement, expressionStatementParent, builderName) Dim newLoopStatement = loopStatement.ReplaceNode(expressionStatementParent, newExpressionStatementParent) Dim stringBuilderType = SyntaxFactory.ParseTypeName("System.Text.StringBuilder").WithAdditionalAnnotations(Simplifier.Annotation) @@ -51,14 +52,13 @@ Namespace Performance Dim stringBuilderDeclaration = SyntaxFactory.LocalDeclarationStatement(SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.DimKeyword)), declarators).NormalizeWhitespace(" ").WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed) - Dim appendExpressionOnInitialization = SyntaxFactory.ParseExecutableStatement(builderName & ".Append(" & assignmentStatement.Left.ToString() & ")").WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed) '.WithLeadingTrivia(assignmentStatement.GetLeadingTrivia()).WithTrailingTrivia(assignmentStatement.GetTrailingTrivia()) - Dim stringBuilderToString = SyntaxFactory.ParseExecutableStatement(assignmentStatement.Left.ToString() & " = " & builderName & ".ToString()").WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed) '.WithLeadingTrivia(assignmentStatement.GetLeadingTrivia()).WithTrailingTrivia(assignmentStatement.GetTrailingTrivia()) + Dim appendExpressionOnInitialization = SyntaxFactory.ParseExecutableStatement(builderName & ".Append(" & expressionStatement.Left.ToString() & ")").WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed) + Dim stringBuilderToString = SyntaxFactory.ParseExecutableStatement(expressionStatement.Left.ToString() & " = " & builderName & ".ToString()").WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed) Dim loopParent = loopStatement.Parent Dim newLoopParent = loopParent.ReplaceNode(loopStatement, {stringBuilderDeclaration, appendExpressionOnInitialization, newLoopStatement, stringBuilderToString}). WithAdditionalAnnotations(Formatter.Annotation) - Dim root = Await document.GetSyntaxRootAsync() Dim newroot = root.ReplaceNode(loopParent, newLoopParent) Dim newDocument = document.WithSyntaxRoot(newroot) Return newDocument diff --git a/src/VisualBasic/CodeCracker/Properties/AssemblyInfo.cs b/src/VisualBasic/CodeCracker/Properties/AssemblyInfo.cs index 68af67824..47c3f28ae 100644 --- a/src/VisualBasic/CodeCracker/Properties/AssemblyInfo.cs +++ b/src/VisualBasic/CodeCracker/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CodeCracker")] -[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyCopyright("Copyright © 2014-2018")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/VisualBasic/CodeCracker/Refactoring/ChangeAnyToAllCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Refactoring/ChangeAnyToAllCodeFixProvider.vb index a347d977a..9f9f70a8b 100644 --- a/src/VisualBasic/CodeCracker/Refactoring/ChangeAnyToAllCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Refactoring/ChangeAnyToAllCodeFixProvider.vb @@ -34,7 +34,7 @@ Namespace Refactoring Private Shared Async Function ConvertAsync(Document As Document, diagnosticLocation As Location, cancellationToken As CancellationToken) As Task(Of Document) Dim root = Await Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) Dim invocation = root.FindNode(diagnosticLocation.SourceSpan).FirstAncestorOfType(Of InvocationExpressionSyntax) - Dim newInvocation = createNewInvocation(invocation, root). + Dim newInvocation = CreateNewInvocation(invocation). WithAdditionalAnnotations(Formatter.Annotation) Dim newRoot = ReplaceInvocation(invocation, newInvocation, root) Dim newDocument = Document.WithSyntaxRoot(newRoot) @@ -50,9 +50,9 @@ Namespace Refactoring Return newRoot End Function - Friend Shared Function CreateNewInvocation(invocation As InvocationExpressionSyntax, root As SyntaxNode) As ExpressionSyntax + Friend Shared Function CreateNewInvocation(invocation As InvocationExpressionSyntax) As ExpressionSyntax Dim methodName = DirectCast(invocation.Expression, MemberAccessExpressionSyntax).Name.ToString - Dim nameToCheck = If(methodName = NameOf(System.Linq.Enumerable.Any), ChangeAnyToAllAnalyzer.allName, ChangeAnyToAllAnalyzer.anyName) + Dim nameToCheck = If(methodName = NameOf(Enumerable.Any), ChangeAnyToAllAnalyzer.allName, ChangeAnyToAllAnalyzer.anyName) Dim newInvocation = invocation.WithExpression(DirectCast(invocation.Expression, MemberAccessExpressionSyntax).WithName(nameToCheck)) Dim comparisonExpression = DirectCast(DirectCast(newInvocation.ArgumentList.Arguments.First().GetExpression(), SingleLineLambdaExpressionSyntax).Body, ExpressionSyntax) Dim newComparisonExpression = CreateNewComparison(comparisonExpression) diff --git a/src/VisualBasic/CodeCracker/Refactoring/ParameterRefactoryCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Refactoring/ParameterRefactoryCodeFixProvider.vb index 90b83d036..dd259cc03 100644 --- a/src/VisualBasic/CodeCracker/Refactoring/ParameterRefactoryCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Refactoring/ParameterRefactoryCodeFixProvider.vb @@ -12,15 +12,10 @@ Namespace Refactoring Public Class ParameterRefactoryCodeFixProvider Inherits CodeFixProvider - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) - + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First() - Dim diagnosticSpan = diagnostic.Location.SourceSpan - Dim declarationClass = root.FindToken(diagnosticSpan.Start).Parent.FirstAncestorOfType(Of ClassBlockSyntax) - Dim declarationNamespace = root.FindToken(diagnosticSpan.Start).Parent.FirstAncestorOfType(Of NamespaceBlockSyntax) - Dim declarationMethod = root.FindToken(diagnosticSpan.Start).Parent.FirstAncestorOfType(Of MethodBlockSyntax) - context.RegisterCodeFix(CodeAction.Create("Change to new Class", Function(c) NewClassAsync(context.Document, declarationNamespace, declarationClass, declarationMethod, c), NameOf(ParameterRefactoryCodeFixProvider)), diagnostic) + context.RegisterCodeFix(CodeAction.Create("Change to new Class", Function(c) NewClassAsync(context.Document, diagnostic, c), NameOf(ParameterRefactoryCodeFixProvider)), diagnostic) + Return Task.FromResult(0) End Function Public Overrides NotOverridable ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(DiagnosticId.ParameterRefactory.ToDiagnosticId()) @@ -29,16 +24,21 @@ Namespace Refactoring Return WellKnownFixAllProviders.BatchFixer End Function - Private Async Function NewClassAsync(document As Document, oldNamespace As NamespaceBlockSyntax, oldClass As ClassBlockSyntax, oldMethod As MethodBlockSyntax, cancellationToken As CancellationToken) As Task(Of Document) + Private Async Function NewClassAsync(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim diagnosticSpan = diagnostic.Location.SourceSpan + Dim declarationClass = root.FindToken(diagnosticSpan.Start).Parent.FirstAncestorOfType(Of ClassBlockSyntax) + Dim declarationNamespace = root.FindToken(diagnosticSpan.Start).Parent.FirstAncestorOfType(Of NamespaceBlockSyntax) + Dim declarationMethod = root.FindToken(diagnosticSpan.Start).Parent.FirstAncestorOfType(Of MethodBlockSyntax) + Dim newRootParameter As SyntaxNode - If oldNamespace Is Nothing Then - Dim newCompilation = NewCompilationFactory(DirectCast(oldClass.Parent, CompilationUnitSyntax), oldClass, oldMethod) - newRootParameter = root.ReplaceNode(oldClass.Parent, newCompilation) + If declarationNamespace Is Nothing Then + Dim newCompilation = NewCompilationFactory(DirectCast(declarationClass.Parent, CompilationUnitSyntax), declarationClass, declarationMethod) + newRootParameter = root.ReplaceNode(declarationClass.Parent, newCompilation) Return document.WithSyntaxRoot(newRootParameter) End If - Dim newNamespace = NewNamespaceFactory(oldNamespace, oldClass, oldMethod) - newRootParameter = root.ReplaceNode(oldNamespace, newNamespace) + Dim newNamespace = NewNamespaceFactory(declarationNamespace, declarationClass, declarationMethod) + newRootParameter = root.ReplaceNode(declarationNamespace, newNamespace) Return document.WithSyntaxRoot(newRootParameter) End Function diff --git a/src/VisualBasic/CodeCracker/Reliability/UseConfigureAwaitFalseCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Reliability/UseConfigureAwaitFalseCodeFixProvider.vb index f2480f0dc..4dbe74275 100644 --- a/src/VisualBasic/CodeCracker/Reliability/UseConfigureAwaitFalseCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Reliability/UseConfigureAwaitFalseCodeFixProvider.vb @@ -1,4 +1,5 @@ Imports System.Collections.Immutable +Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeActions Imports Microsoft.CodeAnalysis.CodeFixes @@ -17,11 +18,15 @@ Namespace Reliability Return WellKnownFixAllProviders.BatchFixer End Function - Public NotOverridable Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task + Public NotOverridable Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First() - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + context.RegisterCodeFix(CodeAction.Create("Use ConfigureAwait(False)", Function(c) CreateUseConfigureAwaitAsync(context.Document, diagnostic, c), NameOf(UseConfigureAwaitFalseCodeFixProvider)), diagnostic) + Return Task.FromResult(0) + End Function + Private Shared Async Function CreateUseConfigureAwaitAsync(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) Dim awaitExpression = root.FindNode(diagnostic.Location.SourceSpan).ChildNodes.OfType(Of AwaitExpressionSyntax).FirstOrDefault() - If awaitExpression Is Nothing Then Exit Function + If awaitExpression Is Nothing Then Return document Dim newExpression = SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( @@ -35,12 +40,8 @@ Namespace Reliability WithTrailingTrivia(awaitExpression.Expression.GetTrailingTrivia()). WithAdditionalAnnotations(Formatter.Annotation) Dim newRoot = root.ReplaceNode(awaitExpression.Expression, newExpression) - Dim newDocument = context.Document.WithSyntaxRoot(newRoot) - context.RegisterCodeFix(CodeAction.Create("Use ConfigureAwait(False)", - Function(ct) - Return Task.FromResult(newDocument) - End Function, - NameOf(UseConfigureAwaitFalseCodeFixProvider)), diagnostic) + Dim newDocument = document.WithSyntaxRoot(newRoot) + Return newDocument End Function End Class End Namespace \ No newline at end of file diff --git a/src/VisualBasic/CodeCracker/Style/InterfaceNameCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Style/InterfaceNameCodeFixProvider.vb index b975cd1a8..a7be8807b 100644 --- a/src/VisualBasic/CodeCracker/Style/InterfaceNameCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Style/InterfaceNameCodeFixProvider.vb @@ -18,17 +18,19 @@ Namespace Style Return WellKnownFixAllProviders.BatchFixer End Function - Public NotOverridable Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public NotOverridable Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First() - Dim diagnosticSpan = diagnostic.Location.SourceSpan - Dim declaration = root.FindToken(diagnosticSpan.Start).Parent.FirstAncestorOrSelfOfType(GetType(InterfaceStatementSyntax)) context.RegisterCodeFix(CodeAction.Create("Consider start Interface name with letter 'I'.", - Function(c) ChangeInterfaceNameAsync(context.Document, DirectCast(declaration, InterfaceStatementSyntax), c), NameOf(InterfaceNameCodeFixProvider)), diagnostic) - + Function(c) ChangeInterfaceNameAsync(context.Document, diagnostic, c), NameOf(InterfaceNameCodeFixProvider)), diagnostic) + Return Task.FromResult(0) End Function - Private Async Function ChangeInterfaceNameAsync(document As Document, interfaceStatement As InterfaceStatementSyntax, cancellationToken As CancellationToken) As Task(Of Solution) + Private Async Function ChangeInterfaceNameAsync(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Solution) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim diagnosticSpan = diagnostic.Location.SourceSpan + Dim declaration = root.FindToken(diagnosticSpan.Start).Parent.FirstAncestorOrSelfOfType(GetType(InterfaceStatementSyntax)) + Dim interfaceStatement = DirectCast(declaration, InterfaceStatementSyntax) + Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken) Dim newName = "I" & interfaceStatement.Identifier.Text diff --git a/src/VisualBasic/CodeCracker/Style/TernaryOperatorAnalyzer.vb b/src/VisualBasic/CodeCracker/Style/TernaryOperatorAnalyzer.vb index 1f1aa5575..81602adea 100644 --- a/src/VisualBasic/CodeCracker/Style/TernaryOperatorAnalyzer.vb +++ b/src/VisualBasic/CodeCracker/Style/TernaryOperatorAnalyzer.vb @@ -55,6 +55,10 @@ Namespace Style If ifStatement Is Nothing Then Exit Sub Dim ifBlock = TryCast(ifStatement.Parent, MultiLineIfBlockSyntax) If ifBlock Is Nothing Then Exit Sub + + ' Can't handle elseif clauses in ternary conditional + If ifBlock.ElseIfBlocks.Any() Then Exit Sub + If ifBlock.ElseBlock Is Nothing Then Exit Sub If ifBlock.Statements.Count <> 1 OrElse ifBlock.ElseBlock.Statements.Count <> 1 Then Exit Sub @@ -63,7 +67,6 @@ Namespace Style If TypeOf (ifClauseStatement) Is ReturnStatementSyntax AndAlso TypeOf (elseStatement) Is ReturnStatementSyntax Then - Dim diag = Diagnostic.Create(RuleForIfWithReturn, ifStatement.IfKeyword.GetLocation, "You can use a ternary operator.") context.ReportDiagnostic(diag) Exit Sub @@ -72,7 +75,10 @@ Namespace Style Dim ifAssignment = TryCast(ifClauseStatement, AssignmentStatementSyntax) Dim elseAssignment = TryCast(elseStatement, AssignmentStatementSyntax) If ifAssignment Is Nothing OrElse elseAssignment Is Nothing Then Exit Sub - If Not ifAssignment?.Left.IsEquivalentTo(elseAssignment?.Left) Then Exit Sub + Dim semanticModel = context.SemanticModel + Dim ifSymbol = semanticModel.GetSymbolInfo(ifAssignment.Left).Symbol + Dim elseSymbol = semanticModel.GetSymbolInfo(elseAssignment.Left).Symbol + If ifSymbol Is Nothing OrElse elseSymbol Is Nothing OrElse ifSymbol.Equals(elseSymbol) = False Then Exit Sub Dim assignDiag = Diagnostic.Create(RuleForIfWithAssignment, ifStatement.IfKeyword.GetLocation(), "You can use a ternary operator.") context.ReportDiagnostic(assignDiag) End Sub @@ -91,4 +97,4 @@ Namespace Style End If End Sub End Class -End Namespace +End Namespace \ No newline at end of file diff --git a/src/VisualBasic/CodeCracker/Style/TernaryOperatorCodeFixProviders.vb b/src/VisualBasic/CodeCracker/Style/TernaryOperatorCodeFixProviders.vb index 407372a5e..9ef918ddf 100644 --- a/src/VisualBasic/CodeCracker/Style/TernaryOperatorCodeFixProviders.vb +++ b/src/VisualBasic/CodeCracker/Style/TernaryOperatorCodeFixProviders.vb @@ -8,17 +8,15 @@ Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Style + Public Class TernaryOperatorWithReturnCodeFixProvider Inherits CodeFixProvider - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First - Dim span = diagnostic.Location.SourceSpan - Dim declaration = root.FindToken(span.Start).Parent.FirstAncestorOrSelf(Of MultiLineIfBlockSyntax) - If declaration Is Nothing Then Exit Function - context.RegisterCodeFix(CodeAction.Create("Change to ternary operator", Function(c) MakeTernaryAsync(context.Document, declaration, c), NameOf(TernaryOperatorWithReturnCodeFixProvider)), diagnostic) + context.RegisterCodeFix(CodeAction.Create("Change to ternary operator", Function(c) MakeTernaryAsync(context.Document, diagnostic, c), NameOf(TernaryOperatorWithReturnCodeFixProvider)), diagnostic) + Return Task.FromResult(0) End Function Public Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = @@ -28,20 +26,48 @@ Namespace Style Return WellKnownFixAllProviders.BatchFixer End Function - Private Async Function MakeTernaryAsync(document As Document, ifBlock As MultiLineIfBlockSyntax, cancellationToken As CancellationToken) As Task(Of Document) + Private Async Function MakeTernaryAsync(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim span = diagnostic.Location.SourceSpan + Dim ifBlock = root.FindToken(span.Start).Parent.FirstAncestorOrSelfOfType(Of MultiLineIfBlockSyntax) + Dim ifReturn = TryCast(ifBlock.Statements.FirstOrDefault(), ReturnStatementSyntax) Dim elseReturn = TryCast(ifBlock.ElseBlock?.Statements.FirstOrDefault(), ReturnStatementSyntax) + Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken) + Dim type = GetCommonBaseType(semanticModel.GetTypeInfo(ifReturn.Expression).ConvertedType, semanticModel.GetTypeInfo(elseReturn.Expression).ConvertedType) + + Dim ifType = semanticModel.GetTypeInfo(ifReturn.Expression).Type + Dim elseType = semanticModel.GetTypeInfo(elseReturn.Expression).Type + + Dim typeSyntax = SyntaxFactory.IdentifierName(type.ToMinimalDisplayString(semanticModel, ifReturn.SpanStart)) + Dim trueExpression = ifReturn.Expression. + ConvertToBaseType(ifType, type). + EnsureNothingAsType(semanticModel, type, typeSyntax) + + Dim falseExpression = elseReturn.Expression. + ConvertToBaseType(elseType, type). + EnsureNothingAsType(semanticModel, type, typeSyntax) + + Dim leadingTrivia = ifBlock.GetLeadingTrivia() + leadingTrivia = leadingTrivia.InsertRange(leadingTrivia.Count - 1, ifReturn.GetLeadingTrivia()) + leadingTrivia = leadingTrivia.InsertRange(leadingTrivia.Count - 1, elseReturn.GetLeadingTrivia()) + + Dim trailingTrivia = ifBlock.GetTrailingTrivia. + InsertRange(0, elseReturn.GetTrailingTrivia().Where(Function(trivia) Not trivia.IsKind(SyntaxKind.EndOfLineTrivia))). + InsertRange(0, ifReturn.GetTrailingTrivia().Where(Function(trivia) Not trivia.IsKind(SyntaxKind.EndOfLineTrivia))) + Dim ternary = SyntaxFactory.TernaryConditionalExpression(ifBlock.IfStatement.Condition.WithoutTrailingTrivia(), - ifReturn.Expression.WithoutTrailingTrivia(), - elseReturn.Expression.WithoutTrailingTrivia()). - WithLeadingTrivia(ifBlock.GetLeadingTrivia()). - WithTrailingTrivia(ifBlock.GetTrailingTrivia()). + trueExpression.WithoutTrailingTrivia(), + falseExpression.WithoutTrailingTrivia()) + + Dim returnStatement = SyntaxFactory.ReturnStatement(ternary). + WithLeadingTrivia(leadingTrivia). + WithTrailingTrivia(trailingTrivia). WithAdditionalAnnotations(Formatter.Annotation) - Dim returnStatement = SyntaxFactory.ReturnStatement(ternary) - Dim root = Await document.GetSyntaxRootAsync(cancellationToken) Dim newRoot = root.ReplaceNode(ifBlock, returnStatement) Dim newDocument = document.WithSyntaxRoot(newRoot) + Return newDocument End Function End Class @@ -50,13 +76,10 @@ Namespace Style Public Class TernaryOperatorWithAssignmentCodeFixProvider Inherits CodeFixProvider - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First - Dim span = diagnostic.Location.SourceSpan - Dim declaration = root.FindToken(span.Start).Parent.FirstAncestorOrSelf(Of MultiLineIfBlockSyntax) - If declaration Is Nothing Then Exit Function - context.RegisterCodeFix(CodeAction.Create("Change to ternary operator", Function(c) MakeTernaryAsync(context.Document, declaration, c), NameOf(TernaryOperatorWithAssignmentCodeFixProvider)), diagnostic) + context.RegisterCodeFix(CodeAction.Create("Change to ternary operator", Function(c) MakeTernaryAsync(context.Document, diagnostic, c), NameOf(TernaryOperatorWithAssignmentCodeFixProvider)), diagnostic) + Return Task.FromResult(0) End Function Public Overrides ReadOnly Property FixableDiagnosticIds() As ImmutableArray(Of String) = @@ -66,22 +89,61 @@ Namespace Style Return WellKnownFixAllProviders.BatchFixer End Function - Private Async Function MakeTernaryAsync(document As Document, ifBlock As MultiLineIfBlockSyntax, cancellationToken As CancellationToken) As Task(Of Document) + Private Async Function MakeTernaryAsync(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim ifBlock = root.FindToken(diagnostic.Location.SourceSpan.Start).Parent.FirstAncestorOrSelf(Of MultiLineIfBlockSyntax) + Dim ifAssign = TryCast(ifBlock.Statements.FirstOrDefault(), AssignmentStatementSyntax) Dim elseAssign = TryCast(ifBlock.ElseBlock?.Statements.FirstOrDefault(), AssignmentStatementSyntax) - Dim variableIdentifier = TryCast(ifAssign.Left, IdentifierNameSyntax) + Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken) + Dim type = GetCommonBaseType(semanticModel.GetTypeInfo(ifAssign.Left).ConvertedType, semanticModel.GetTypeInfo(elseAssign.Left).ConvertedType) + Dim typeSyntax = SyntaxFactory.IdentifierName(type.ToMinimalDisplayString(semanticModel, ifAssign.SpanStart)) + + Dim ifType = semanticModel.GetTypeInfo(ifAssign.Right).Type + Dim elseType = semanticModel.GetTypeInfo(elseAssign.Right).Type + + Dim trueExpression = ifAssign. + ExtractAssignmentAsExpressionSyntax(). + EnsureNothingAsType(semanticModel, type, typeSyntax). + ConvertToBaseType(ifType, type) + + Dim falseExpression = elseAssign. + ExtractAssignmentAsExpressionSyntax(). + EnsureNothingAsType(semanticModel, type, typeSyntax). + ConvertToBaseType(elseType, type) + + If ifAssign.OperatorToken.Text <> "=" AndAlso ifAssign.OperatorToken.Text = elseAssign.OperatorToken.Text Then + trueExpression = ifAssign.Right. + EnsureNothingAsType(semanticModel, type, typeSyntax). + ConvertToBaseType(ifType, type) + + falseExpression = elseAssign.Right. + EnsureNothingAsType(semanticModel, type, typeSyntax). + ConvertToBaseType(elseType, type) + End If + + Dim leadingTrivia = ifBlock.GetLeadingTrivia. + AddRange(ifAssign.GetLeadingTrivia()). + AddRange(trueExpression.GetLeadingTrivia()). + AddRange(elseAssign.GetLeadingTrivia()). + AddRange(falseExpression.GetLeadingTrivia()) + + Dim trailingTrivia = ifBlock.GetTrailingTrivia. + InsertRange(0, elseAssign.GetTrailingTrivia().Where(Function(trivia) Not trivia.IsKind(SyntaxKind.EndOfLineTrivia))). + InsertRange(0, ifAssign.GetTrailingTrivia().Where(Function(trivia) Not trivia.IsKind(SyntaxKind.EndOfLineTrivia))) - Dim ternary = SyntaxFactory.TernaryConditionalExpression( - ifBlock.IfStatement.Condition, - ifAssign.Right.WithoutTrailingTrivia(), - elseAssign.Right.WithoutTrailingTrivia()) + Dim ternary = SyntaxFactory.TernaryConditionalExpression(ifBlock.IfStatement.Condition.WithoutTrailingTrivia(), + trueExpression.WithoutTrailingTrivia(), + falseExpression.WithoutTrailingTrivia()) + + Dim ternaryOperatorToken As SyntaxToken = If((ifAssign.OperatorToken.Text <> "=" OrElse elseAssign.OperatorToken.Text <> "=") AndAlso ifAssign.OperatorToken.Text <> elseAssign.OperatorToken.Text, + SyntaxFactory.Token(SyntaxKind.EqualsToken), + ifAssign.OperatorToken) - Dim assignment = SyntaxFactory.SimpleAssignmentStatement(variableIdentifier, ternary). - WithLeadingTrivia(ifBlock.GetLeadingTrivia()). - WithTrailingTrivia(ifBlock.GetTrailingTrivia()). + Dim assignment = SyntaxFactory.SimpleAssignmentStatement(ifAssign.Left.WithLeadingTrivia(leadingTrivia), ternaryOperatorToken, ternary). + WithTrailingTrivia(trailingTrivia). WithAdditionalAnnotations(Formatter.Annotation) - Dim root = Await document.GetSyntaxRootAsync(cancellationToken) Dim newRoot = root.ReplaceNode(ifBlock, assignment) Dim newDocument = document.WithSyntaxRoot(newRoot) Return newDocument @@ -92,13 +154,10 @@ Namespace Style Public Class TernaryOperatorFromIifCodeFixProvider Inherits CodeFixProvider - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First - Dim span = diagnostic.Location.SourceSpan - Dim declaration = root.FindToken(span.Start).Parent.FirstAncestorOrSelf(Of InvocationExpressionSyntax) - If declaration Is Nothing Then Exit Function - context.RegisterCodeFix(CodeAction.Create("Change IIF to If to short circuit evaulations", Function(c) MakeTernaryAsync(context.Document, declaration, c), NameOf(TernaryOperatorFromIifCodeFixProvider)), diagnostic) + context.RegisterCodeFix(CodeAction.Create("Change IIF to If to short circuit evaulations", Function(c) MakeTernaryAsync(context.Document, diagnostic, c), NameOf(TernaryOperatorFromIifCodeFixProvider)), diagnostic) + Return Task.FromResult(0) End Function Public Overrides ReadOnly Property FixableDiagnosticIds() As ImmutableArray(Of String) = @@ -108,7 +167,10 @@ Namespace Style Return WellKnownFixAllProviders.BatchFixer End Function - Private Async Function MakeTernaryAsync(document As Document, iifAssignment As InvocationExpressionSyntax, cancellationToken As CancellationToken) As Task(Of Document) + Private Async Function MakeTernaryAsync(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim iifAssignment = root.FindToken(diagnostic.Location.SourceSpan.Start).Parent.FirstAncestorOrSelf(Of InvocationExpressionSyntax) + Dim ternary = SyntaxFactory.TernaryConditionalExpression( iifAssignment.ArgumentList.Arguments(0).GetExpression(), iifAssignment.ArgumentList.Arguments(1).GetExpression(), @@ -117,7 +179,6 @@ Namespace Style WithTrailingTrivia(iifAssignment.GetTrailingTrivia()). WithAdditionalAnnotations(Formatter.Annotation) - Dim root = Await document.GetSyntaxRootAsync(cancellationToken) Dim newRoot = root.ReplaceNode(iifAssignment, ternary) Dim newDocument = document.WithSyntaxRoot(newRoot) Return newDocument diff --git a/src/VisualBasic/CodeCracker/Usage/ArgumentExceptionAnalyzer.vb b/src/VisualBasic/CodeCracker/Usage/ArgumentExceptionAnalyzer.vb index 30d8022ef..5f44756e4 100644 --- a/src/VisualBasic/CodeCracker/Usage/ArgumentExceptionAnalyzer.vb +++ b/src/VisualBasic/CodeCracker/Usage/ArgumentExceptionAnalyzer.vb @@ -50,14 +50,16 @@ It can be either specified directly or using nameof() (VB 14 and above only)." If Not paramNameOpt.HasValue Then Exit Sub Dim paramName = paramNameOpt.Value.ToString() - If IsParamNameCompatibleWithCreatingContext(objectCreationExpression, paramName) Then Exit Sub - Dim diag = Diagnostic.Create(Rule, paramNameLiteral.GetLocation, paramName) + Dim parameters As IEnumerable(Of String) = Nothing + If IsParamNameCompatibleWithCreatingContext(objectCreationExpression, paramName, parameters) Then Exit Sub + Dim props = parameters.ToImmutableDictionary(Function(p) $"param{p}", Function(p) p) + Dim diag = Diagnostic.Create(Rule, paramNameLiteral.GetLocation, props.ToImmutableDictionary(), paramName) context.ReportDiagnostic(diag) End Sub - Private Function IsParamNameCompatibleWithCreatingContext(node As SyntaxNode, paramName As String) As Boolean - Dim parameters = GetParameterNamesFromCreationContext(node) + Private Function IsParamNameCompatibleWithCreatingContext(node As SyntaxNode, paramName As String, ByRef parameters As IEnumerable(Of String)) As Boolean + parameters = GetParameterNamesFromCreationContext(node) If parameters Is Nothing Then Return True Return parameters.Contains(paramName) End Function diff --git a/src/VisualBasic/CodeCracker/Usage/ArgumentExceptionCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Usage/ArgumentExceptionCodeFixProvider.vb index 730fa4d5e..be77760c7 100644 --- a/src/VisualBasic/CodeCracker/Usage/ArgumentExceptionCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Usage/ArgumentExceptionCodeFixProvider.vb @@ -18,20 +18,22 @@ Namespace Usage Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(DiagnosticId.ArgumentException.ToDiagnosticId()) - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First() - Dim span = diagnostic.Location.SourceSpan - Dim objectCreation = root.FindToken(span.Start).Parent.FirstAncestorOrSelf(Of ObjectCreationExpressionSyntax) - Dim parameters = ArgumentExceptionAnalyzer.GetParameterNamesFromCreationContext(objectCreation) + Dim parameters = diagnostic.Properties.Where(Function(p) p.Key.StartsWith("param")) For Each param In parameters - Dim message = "Use '" & param & "'" - context.RegisterCodeFix(CodeAction.Create(message, Function(c) FixParamAsync(context.Document, objectCreation, param, c), NameOf(ArgumentExceptionCodeFixProvider)), diagnostic) + Dim message = $"Use '{param}'" + context.RegisterCodeFix(CodeAction.Create(message, Function(c) FixParamAsync(context.Document, diagnostic, param.Value, c), NameOf(ArgumentExceptionCodeFixProvider)), diagnostic) Next + Return Task.FromResult(0) End Function - Private Async Function FixParamAsync(document As Document, objectCreation As ObjectCreationExpressionSyntax, newParamName As String, cancellationToken As CancellationToken) As Task(Of Document) + Private Async Function FixParamAsync(document As Document, diagnostic As Diagnostic, newParamName As String, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim span = diagnostic.Location.SourceSpan + Dim objectCreation = root.FindToken(span.Start).Parent.FirstAncestorOrSelf(Of ObjectCreationExpressionSyntax) + Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken) Dim argumentList = objectCreation.ArgumentList @@ -40,7 +42,6 @@ Namespace Usage Dim currentParamName = paramNameOpt.Value.ToString() Dim newLiteral = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(newParamName)) - Dim root = Await document.GetSyntaxRootAsync() Dim newRoot = root.ReplaceNode(paramNameLiteral, newLiteral) Dim newDocument = document.WithSyntaxRoot(newRoot) Return newDocument diff --git a/src/VisualBasic/CodeCracker/Usage/DisposableFieldNotDisposedAnalyzer.vb b/src/VisualBasic/CodeCracker/Usage/DisposableFieldNotDisposedAnalyzer.vb index 5de9041ac..16ed817be 100644 --- a/src/VisualBasic/CodeCracker/Usage/DisposableFieldNotDisposedAnalyzer.vb +++ b/src/VisualBasic/CodeCracker/Usage/DisposableFieldNotDisposedAnalyzer.vb @@ -53,12 +53,15 @@ Namespace Usage Dim variableDeclarator = TryCast(fieldSyntaxRef.GetSyntax().Parent, VariableDeclaratorSyntax) If variableDeclarator Is Nothing Then Exit Sub If ContainingTypeImplementsIDisposableAndCallsItOnTheField(context, fieldSymbol, fieldSymbol.ContainingType) Then Exit Sub + + Dim props = New Dictionary(Of String, String) From {{"variableIdentifier", variableDeclarator.Names.First().Identifier.ValueText}}.ToImmutableDictionary() + If variableDeclarator.AsClause.Kind = SyntaxKind.AsNewClause Then - context.ReportDiagnostic(Diagnostic.Create(RuleForCreated, variableDeclarator.GetLocation(), fieldSymbol.Name)) + context.ReportDiagnostic(Diagnostic.Create(RuleForCreated, variableDeclarator.GetLocation(), props, fieldSymbol.Name)) ElseIf TypeOf (variableDeclarator.Initializer?.Value) Is InvocationExpressionSyntax Then - context.ReportDiagnostic(Diagnostic.Create(RuleForReturned, variableDeclarator.GetLocation(), fieldSymbol.Name)) + context.ReportDiagnostic(Diagnostic.Create(RuleForReturned, variableDeclarator.GetLocation(), props, fieldSymbol.Name)) ElseIf TypeOf (variableDeclarator.Initializer?.Value) Is ObjectCreationExpressionSyntax Then - context.ReportDiagnostic(Diagnostic.Create(RuleForCreated, variableDeclarator.GetLocation(), fieldSymbol.Name)) + context.ReportDiagnostic(Diagnostic.Create(RuleForCreated, variableDeclarator.GetLocation(), props, fieldSymbol.Name)) End If End Sub diff --git a/src/VisualBasic/CodeCracker/Usage/DisposableFieldNotDisposedCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Usage/DisposableFieldNotDisposedCodeFixProvider.vb index 2205dca82..dd9932546 100644 --- a/src/VisualBasic/CodeCracker/Usage/DisposableFieldNotDisposedCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Usage/DisposableFieldNotDisposedCodeFixProvider.vb @@ -14,16 +14,13 @@ Namespace Usage Public Class DisposableFieldNotDisposedCodeFixProvider Inherits CodeFixProvider - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First - Dim span = diagnostic.Location.SourceSpan - Dim variableDeclarator = root.FindToken(span.Start).Parent.FirstAncestorOrSelf(Of VariableDeclaratorSyntax)() - context.RegisterCodeFix(CodeAction.Create("Dispose field '" & variableDeclarator.Names.First().ToString(), - Function(c) DisposeField(context.Document, variableDeclarator, c), + context.RegisterCodeFix(CodeAction.Create($"Dispose field '{diagnostic.Properties!variableIdentifier}", + Function(c) DisposeField(context.Document, diagnostic, c), NameOf(DisposableFieldNotDisposedCodeFixProvider)), diagnostic) - + Return Task.FromResult(0) End Function Public Overrides NotOverridable ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId()) @@ -32,13 +29,16 @@ Namespace Usage Return WellKnownFixAllProviders.BatchFixer End Function - Private Async Function DisposeField(document As Document, variableDeclarator As VariableDeclaratorSyntax, cancellationToken As CancellationToken) As Task(Of Document) + Private Async Function DisposeField(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim span = diagnostic.Location.SourceSpan + Dim variableDeclarator = root.FindToken(span.Start).Parent.FirstAncestorOrSelf(Of VariableDeclaratorSyntax)() + Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken) Dim type = variableDeclarator.FirstAncestorOrSelf(Of ClassBlockSyntax) Dim typeSymbol = semanticModel.GetDeclaredSymbol(type) Dim newTypeImplementingIDisposable = AddIDisposableImplementationToType(type, typeSymbol) Dim newTypeWithDisposeMethod = AddDisposeDeclarationToDisposeMethod(variableDeclarator, newTypeImplementingIDisposable, typeSymbol) - Dim root = Await document.GetSyntaxRootAsync() Dim newRoot = root.ReplaceNode(type, newTypeWithDisposeMethod) Dim newDocument = document.WithSyntaxRoot(newRoot) Return newDocument diff --git a/src/VisualBasic/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeAnalyzer.vb b/src/VisualBasic/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeAnalyzer.vb index c7ee0b6e2..2b80fa72d 100644 --- a/src/VisualBasic/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeAnalyzer.vb +++ b/src/VisualBasic/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeAnalyzer.vb @@ -35,7 +35,7 @@ This rule should be followed even if the class doesn't have a finalizer in a der context.RegisterSymbolAction(AddressOf AnalyzeAsync, SymbolKind.NamedType) End Sub - Public Async Sub AnalyzeAsync(context As SymbolAnalysisContext) + Public Sub AnalyzeAsync(context As SymbolAnalysisContext) If (context.IsGenerated()) Then Return Dim symbol = DirectCast(context.Symbol, INamedTypeSymbol) If symbol.TypeKind <> TypeKind.Class Then Exit Sub @@ -48,7 +48,7 @@ This rule should be followed even if the class doesn't have a finalizer in a der Dim disposeMethod = FindDisposeMethod(symbol) If disposeMethod Is Nothing Then Exit Sub - Dim syntaxTree = Await disposeMethod.DeclaringSyntaxReferences(0)?.GetSyntaxAsync(context.CancellationToken) + Dim syntaxTree = disposeMethod.DeclaringSyntaxReferences(0)?.GetSyntax(context.CancellationToken) Dim methodBlock = TryCast(TryCast(syntaxTree, MethodStatementSyntax)?.Parent, MethodBlockSyntax) Dim statements = methodBlock?.Statements.OfType(Of ExpressionStatementSyntax) diff --git a/src/VisualBasic/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeCodeFixProvider.vb index 94c6470c0..1d9cdb587 100644 --- a/src/VisualBasic/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeCodeFixProvider.vb @@ -12,12 +12,10 @@ Namespace Usage Public Class DisposablesShouldCallSuppressFinalizeCodeFixProvider Inherits CodeFixProvider - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First - Dim span = diagnostic.Location.SourceSpan - Dim method = root.FindToken(span.Start).Parent.FirstAncestorOrSelf(Of MethodBlockSyntax)() - context.RegisterCodeFix(CodeAction.Create("Call GC.SuppressFinalize", Function(ct) AddSuppressFinalizeAsync(context.Document, method, ct), NameOf(DisposablesShouldCallSuppressFinalizeCodeFixProvider)), diagnostic) + context.RegisterCodeFix(CodeAction.Create("Call GC.SuppressFinalize", Function(ct) AddSuppressFinalizeAsync(context.Document, diagnostic, ct), NameOf(DisposablesShouldCallSuppressFinalizeCodeFixProvider)), diagnostic) + Return Task.FromResult(0) End Function Public NotOverridable Overrides Function GetFixAllProvider() As FixAllProvider @@ -26,7 +24,11 @@ Namespace Usage Public Overrides NotOverridable ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(DiagnosticId.DisposablesShouldCallSuppressFinalize.ToDiagnosticId()) - Public Async Function AddSuppressFinalizeAsync(document As Document, method As MethodBlockSyntax, cancellationToken As CancellationToken) As Task(Of Document) + Public Async Function AddSuppressFinalizeAsync(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim span = diagnostic.Location.SourceSpan + Dim method = root.FindToken(span.Start).Parent.FirstAncestorOrSelf(Of MethodBlockSyntax)() + Dim suppressInvocation = SyntaxFactory.ExpressionStatement( SyntaxFactory.InvocationExpression( diff --git a/src/VisualBasic/CodeCracker/Usage/IPAddressAnalyzer.vb b/src/VisualBasic/CodeCracker/Usage/IPAddressAnalyzer.vb index 38aadd11a..49b2acf07 100644 --- a/src/VisualBasic/CodeCracker/Usage/IPAddressAnalyzer.vb +++ b/src/VisualBasic/CodeCracker/Usage/IPAddressAnalyzer.vb @@ -9,9 +9,9 @@ Imports Microsoft.CodeAnalysis.VisualBasic Public Class IPAddressAnalyzer Inherits DiagnosticAnalyzer - Friend Const Title = "Your IP Address syntax is wrong." + Friend Const Title = "Your IP Address syntax is incorrect." Friend Const MessageFormat = "{0}" - Private Const Description = "This diagnostic checks the IP Address string and triggers if the parsing will fail by throwing an exception." + Private Const Description = "An error was found parsing the IP Address string." Friend Shared Rule As New DiagnosticDescriptor( DiagnosticId.IPAddress.ToDiagnosticId(), diff --git a/src/VisualBasic/CodeCracker/Usage/MustInheritClassShouldNotHavePublicConstructorsCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Usage/MustInheritClassShouldNotHavePublicConstructorsCodeFixProvider.vb index 16ff5569c..f91d7255f 100644 --- a/src/VisualBasic/CodeCracker/Usage/MustInheritClassShouldNotHavePublicConstructorsCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Usage/MustInheritClassShouldNotHavePublicConstructorsCodeFixProvider.vb @@ -11,13 +11,10 @@ Namespace Usage Public Class MustInheritClassShouldNotHavePublicConstructorsCodeFixProvider Inherits CodeFixProvider - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diag = context.Diagnostics.First() - Dim span = diag.Location.SourceSpan - - Dim constructor = root.FindToken(span.Start).Parent.FirstAncestorOrSelf(Of SubNewStatementSyntax) - context.RegisterCodeFix(CodeAction.Create("Use 'Protected' in stead of 'Public'", Function(c) ReplacePublicWithProtectedAsync(context.Document, constructor, c), NameOf(MustInheritClassShouldNotHavePublicConstructorsCodeFixProvider)), diag) + context.RegisterCodeFix(CodeAction.Create("Use 'Friend' instead of 'Public'", Function(c) ReplacePublicWithProtectedAsync(context.Document, diag, c), NameOf(MustInheritClassShouldNotHavePublicConstructorsCodeFixProvider)), diag) + Return Task.FromResult(0) End Function @@ -27,13 +24,17 @@ Namespace Usage Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(DiagnosticId.AbstractClassShouldNotHavePublicCtors.ToDiagnosticId()) - Private Async Function ReplacePublicWithProtectedAsync(document As Document, constructor As SubNewStatementSyntax, cancellationToken As CancellationToken) As Task(Of Document) + Private Async Function ReplacePublicWithProtectedAsync(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Document) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim span = diagnostic.Location.SourceSpan + + Dim constructor = root.FindToken(span.Start).Parent.FirstAncestorOrSelf(Of SubNewStatementSyntax) + Dim [public] = constructor.Modifiers.First(Function(m) m.IsKind(SyntaxKind.PublicKeyword)) Dim [protected] = SyntaxFactory.Token([public].LeadingTrivia, SyntaxKind.ProtectedKeyword, [public].TrailingTrivia) Dim newModifiers = constructor.Modifiers.Replace([public], [protected]) Dim newConstructor = constructor.WithModifiers(newModifiers) - Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) Dim newRoot = root.ReplaceNode(constructor, newConstructor) Dim newDocumnent = document.WithSyntaxRoot(newRoot) Return newDocumnent diff --git a/src/VisualBasic/CodeCracker/Usage/RemovePrivateMethodNeverUsedAnalyzer.vb b/src/VisualBasic/CodeCracker/Usage/RemovePrivateMethodNeverUsedAnalyzer.vb index 1be347d85..652a83ca7 100644 --- a/src/VisualBasic/CodeCracker/Usage/RemovePrivateMethodNeverUsedAnalyzer.vb +++ b/src/VisualBasic/CodeCracker/Usage/RemovePrivateMethodNeverUsedAnalyzer.vb @@ -11,7 +11,7 @@ Namespace Usage Friend Const Title = "Unused Method" Friend Const Message = "Method is not used." - Private Const Description = "When a private method is declared but not used, remove it to avoid confusion." + Private Const Description = "Unused private methods can be safely removed as they are unnecessary." Friend Shared Rule As New DiagnosticDescriptor( DiagnosticId.RemovePrivateMethodNeverUsed.ToDiagnosticId(), @@ -38,11 +38,39 @@ Namespace Usage Dim methodStatement = DirectCast(context.Node, MethodStatementSyntax) If methodStatement.HandlesClause IsNot Nothing Then Exit Sub If Not methodStatement.Modifiers.Any(Function(a) a.ValueText = SyntaxFactory.Token(SyntaxKind.PrivateKeyword).ValueText) Then Exit Sub + If (IsMethodAttributeAnException(methodStatement)) Then Return If IsMethodUsed(methodStatement, context.SemanticModel) Then Exit Sub - Dim diag = Diagnostic.Create(Rule, methodStatement.GetLocation()) + Dim props = New Dictionary(Of String, String) From {{"identifier", methodStatement.Identifier.Text}}.ToImmutableDictionary() + Dim diag = Diagnostic.Create(Rule, methodStatement.GetLocation(), props) context.ReportDiagnostic(diag) End Sub + Private Function IsMethodAttributeAnException(methodStatement As MethodStatementSyntax) As Boolean + For Each attributeList In methodStatement.AttributeLists + For Each attribute In attributeList.Attributes + Dim identifierName = TryCast(attribute.Name, IdentifierNameSyntax) + Dim nameText As String = Nothing + If (identifierName IsNot Nothing) Then + nameText = identifierName?.Identifier.Text + Else + Dim qualifiedName = TryCast(attribute.Name, QualifiedNameSyntax) + If (qualifiedName IsNot Nothing) Then + nameText = qualifiedName.Right?.Identifier.Text + End If + End If + If (nameText Is Nothing) Then Continue For + If (IsExcludedAttributeName(nameText)) Then Return True + Next + Next + Return False + End Function + + Private Shared ReadOnly excludedAttributeNames As String() = {"Fact", "ContractInvariantMethod", "DataMember"} + + Private Shared Function IsExcludedAttributeName(attributeName As String) As Boolean + Return excludedAttributeNames.Contains(attributeName) + End Function + Private Function IsMethodUsed(methodTarget As MethodStatementSyntax, semanticModel As SemanticModel) As Boolean Dim typeDeclaration = TryCast(methodTarget.Parent.Parent, ClassBlockSyntax) If typeDeclaration Is Nothing Then Return True diff --git a/src/VisualBasic/CodeCracker/Usage/RemovePrivateMethodNeverUsedCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Usage/RemovePrivateMethodNeverUsedCodeFixProvider.vb index 9f0289cb7..ca0e5eb55 100644 --- a/src/VisualBasic/CodeCracker/Usage/RemovePrivateMethodNeverUsedCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Usage/RemovePrivateMethodNeverUsedCodeFixProvider.vb @@ -15,16 +15,17 @@ Namespace Usage Return WellKnownFixAllProviders.BatchFixer End Function - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task Dim diagnostic = context.Diagnostics.First() - Dim span = diagnostic.Location.SourceSpan - Dim methodNotUsed = root.FindToken(span.Start).Parent.FirstAncestorOrSelf(Of MethodStatementSyntax) - context.RegisterCodeFix(CodeAction.Create("Remove unused private method: " & methodNotUsed.Identifier.ValueText, Function(c) RemoveMethodAsync(context.Document, methodNotUsed, c), NameOf(RemovePrivateMethodNeverUsedCodeFixProvider)), diagnostic) + context.RegisterCodeFix(CodeAction.Create($"Remove unused private method: {diagnostic.Properties!identifier}", Function(c) RemoveMethodAsync(context.Document, diagnostic, c), NameOf(RemovePrivateMethodNeverUsedCodeFixProvider)), diagnostic) + Return Task.FromResult(0) End Function - Private Async Function RemoveMethodAsync(document As Document, methodNotUsed As MethodStatementSyntax, cancellationToken As Threading.CancellationToken) As Task(Of Document) + Private Async Function RemoveMethodAsync(document As Document, diagnostic As Diagnostic, cancellationToken As Threading.CancellationToken) As Task(Of Document) Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim span = diagnostic.Location.SourceSpan + Dim methodNotUsed = root.FindToken(span.Start).Parent.FirstAncestorOrSelf(Of MethodStatementSyntax) + Dim newRoot = root.RemoveNode(methodNotUsed.Parent, SyntaxRemoveOptions.KeepNoTrivia) Return document.WithSyntaxRoot(newRoot) End Function diff --git a/src/VisualBasic/CodeCracker/Usage/UnusedParametersAnalyzer.vb b/src/VisualBasic/CodeCracker/Usage/UnusedParametersAnalyzer.vb index eeaad5a8a..8f05ce73f 100644 --- a/src/VisualBasic/CodeCracker/Usage/UnusedParametersAnalyzer.vb +++ b/src/VisualBasic/CodeCracker/Usage/UnusedParametersAnalyzer.vb @@ -11,8 +11,7 @@ Namespace Usage Friend Const Title = "Unused parameters." Friend Const Message = "Parameter '{0}' is not used." - Private Const Description = "When a method declares a parameter and does not use it might bring incorrect concolusions for anyone reading the code and anso demands the parameter when the method is called unnecessarily. -You should delete the parameter in such cases." + Private Const Description = "A method with an unused parameter creates unnecessary confusion and should be deleted." Friend Shared Rule As New DiagnosticDescriptor( DiagnosticId.UnusedParameters.ToDiagnosticId(), @@ -20,7 +19,7 @@ You should delete the parameter in such cases." Message, SupportedCategories.Usage, DiagnosticSeverity.Warning, - False, + True, Description, HelpLink.ForDiagnostic(DiagnosticId.UnusedParameters), WellKnownDiagnosticTags.Unnecessary) @@ -65,6 +64,7 @@ You should delete the parameter in such cases." Private Shared Function IsCandidateForRemoval(methodOrConstructor As MethodBlockBaseSyntax, semanticModel As SemanticModel) As Boolean If methodOrConstructor.BlockStatement.Modifiers.Any(Function(m) m.ValueText = "Partial" OrElse m.ValueText = "Overrides") OrElse Not methodOrConstructor.BlockStatement.ParameterList?.Parameters.Any() Then Return False + If methodOrConstructor.HasAttributeOnAncestorOrSelf("DllImport") Then Return False Dim method = TryCast(methodOrConstructor, MethodBlockSyntax) If method IsNot Nothing Then @@ -115,9 +115,12 @@ You should delete the parameter in such cases." End Function Private Function CreateDiagnostic(context As SyntaxNodeAnalysisContext, parameter As ParameterSyntax) As SyntaxNodeAnalysisContext - Dim diag = Diagnostic.Create(Rule, parameter.GetLocation(), parameter.Identifier.Identifier.ValueText) + Dim propsDic = New Dictionary(Of String, String) + propsDic.Add("identifier", parameter.Identifier.Identifier.Text) + Dim props = propsDic.ToImmutableDictionary() + Dim diag = Diagnostic.Create(Rule, parameter.GetLocation(), props, parameter.Identifier.Identifier.ValueText) context.ReportDiagnostic(diag) Return context End Function End Class -End Namespace \ No newline at end of file +End Namespace diff --git a/src/VisualBasic/CodeCracker/Usage/UnusedParametersCodeFixAllProvider.vb b/src/VisualBasic/CodeCracker/Usage/UnusedParametersCodeFixAllProvider.vb new file mode 100644 index 000000000..ec30c6a5a --- /dev/null +++ b/src/VisualBasic/CodeCracker/Usage/UnusedParametersCodeFixAllProvider.vb @@ -0,0 +1,123 @@ +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeActions +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports System.Collections.Generic +Imports System.Linq +Imports System.Threading.Tasks + +Namespace Usage + + Public NotInheritable Class UnusedParametersCodeFixAllProvider + Inherits FixAllProvider + + Private Sub New() + MyBase.New + + End Sub + + Public Shared Instance As UnusedParametersCodeFixAllProvider = New UnusedParametersCodeFixAllProvider + Private Const message As String = "Remove unused parameter" + + Public Overrides Function GetFixAsync(ByVal fixAllContext As FixAllContext) As Task(Of CodeAction) + Select Case (fixAllContext.Scope) + Case FixAllScope.Document + Return Task.FromResult(CodeAction.Create(message, Async Function(ct) Await GetFixedSolutionAsync(fixAllContext, Await GetSolutionWithDocsAsync(fixAllContext, fixAllContext.Document)))) + Case FixAllScope.Project + Return Task.FromResult(CodeAction.Create(message, Async Function(ct) Await GetFixedSolutionAsync(fixAllContext, Await GetSolutionWithDocsAsync(fixAllContext, fixAllContext.Project)))) + Case FixAllScope.Solution + Return Task.FromResult(CodeAction.Create(message, Async Function(ct) Await GetFixedSolutionAsync(fixAllContext, Await GetSolutionWithDocsAsync(fixAllContext, fixAllContext.Solution)))) + End Select + + Return Nothing + End Function + + Private Overloads Shared Async Function GetSolutionWithDocsAsync(ByVal fixAllContext As FixAllContext, ByVal solution As Solution) As Task(Of SolutionWithDocs) + Dim docs = New List(Of DiagnosticsInDoc) + Dim sol = New SolutionWithDocs() With {.Docs = docs, .Solution = solution} + For Each pId In solution.Projects.Select(Function(p) p.Id) + Dim project = sol.Solution.GetProject(pId) + Dim newSol = Await GetSolutionWithDocsAsync(fixAllContext, project).ConfigureAwait(False) + sol.Merge(newSol) + Next + Return sol + End Function + + Private Overloads Shared Async Function GetSolutionWithDocsAsync(ByVal fixAllContext As FixAllContext, ByVal project As Project) As Task(Of SolutionWithDocs) + Dim docs = New List(Of DiagnosticsInDoc) + Dim newSolution = project.Solution + For Each document In project.Documents + Dim doc = Await GetDiagnosticsInDocAsync(fixAllContext, document) + If doc.Equals(DiagnosticsInDoc.Empty) Then Continue For + docs.Add(doc) + newSolution = newSolution.WithDocumentSyntaxRoot(document.Id, doc.TrackedRoot) + Next + Dim sol = New SolutionWithDocs() With {.Docs = docs, .Solution = newSolution} + Return sol + End Function + + Private Overloads Shared Async Function GetSolutionWithDocsAsync(ByVal fixAllContext As FixAllContext, ByVal document As Document) As Task(Of SolutionWithDocs) + Dim docs = New List(Of DiagnosticsInDoc) + Dim doc = Await GetDiagnosticsInDocAsync(fixAllContext, document) + docs.Add(doc) + Dim newSolution = document.Project.Solution.WithDocumentSyntaxRoot(document.Id, doc.TrackedRoot) + Dim sol = New SolutionWithDocs() With {.Docs = docs, .Solution = newSolution} + Return sol + End Function + + Private Shared Async Function GetDiagnosticsInDocAsync(ByVal fixAllContext As FixAllContext, ByVal document As Document) As Task(Of DiagnosticsInDoc) + Dim diagnostics = Await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(False) + If Not diagnostics.Any Then + Return DiagnosticsInDoc.Empty + End If + Dim root = Await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(False) + Dim doc = DiagnosticsInDoc.Create(document.Id, diagnostics, root) + Return doc + End Function + + Private Shared Async Function GetFixedSolutionAsync(ByVal fixAllContext As FixAllContext, ByVal sol As SolutionWithDocs) As Task(Of Solution) + Dim newSolution = sol.Solution + For Each doc In sol.Docs + For Each node In doc.Nodes + Dim document = newSolution.GetDocument(doc.DocumentId) + Dim root = Await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(False) + Dim trackedNode = root.GetCurrentNode(node) + Dim parameter = trackedNode.AncestorsAndSelf().OfType(Of ParameterSyntax).First() + Dim docResults = Await UnusedParametersCodeFixProvider.RemoveParameterAsync(document, parameter, root, fixAllContext.CancellationToken) + For Each docResult In docResults + newSolution = newSolution.WithDocumentSyntaxRoot(docResult.DocumentId, docResult.Root) + Next + Next + Next + Return newSolution + End Function + + Private Structure DiagnosticsInDoc + Public Shared Function Create(ByVal documentId As DocumentId, ByVal diagnostics As IList(Of Diagnostic), ByVal root As SyntaxNode) As DiagnosticsInDoc + Dim nodes = diagnostics.Select(Function(d) root.FindNode(d.Location.SourceSpan)).Where(Function(n) Not n.IsMissing).ToList() + Dim diagnosticsInDoc = New DiagnosticsInDoc() With {.DocumentId = documentId, .TrackedRoot = root.TrackNodes(nodes), .Nodes = nodes} + Return diagnosticsInDoc + End Function + + Public DocumentId As DocumentId + + Public Nodes As List(Of SyntaxNode) + + Public TrackedRoot As SyntaxNode + + Public Shared Property Empty As DiagnosticsInDoc = New DiagnosticsInDoc() + End Structure + + Private Structure SolutionWithDocs + + Public Solution As Solution + + Public Docs As List(Of DiagnosticsInDoc) + + Public Sub Merge(ByVal sol As SolutionWithDocs) + Solution = sol.Solution + Docs.AddRange(sol.Docs) + End Sub + End Structure + End Class +End Namespace \ No newline at end of file diff --git a/src/VisualBasic/CodeCracker/Usage/UnusedParametersCodeFixProvider.vb b/src/VisualBasic/CodeCracker/Usage/UnusedParametersCodeFixProvider.vb index 417474144..3c8d6b5a8 100644 --- a/src/VisualBasic/CodeCracker/Usage/UnusedParametersCodeFixProvider.vb +++ b/src/VisualBasic/CodeCracker/Usage/UnusedParametersCodeFixProvider.vb @@ -4,6 +4,7 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeActions Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.FindSymbols +Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Usage @@ -11,32 +12,45 @@ Namespace Usage Public Class UnusedParametersCodeFixProvider Inherits CodeFixProvider - Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) - Dim diagnostic = context.Diagnostics.First - Dim span = diagnostic.Location.SourceSpan - Dim parameter = root.FindToken(span.Start).Parent.FirstAncestorOrSelf(Of ParameterSyntax) - context.RegisterCodeFix(CodeAction.Create(String.Format("Remove unused parameter: '{0}'", parameter.Identifier.GetText()), Function(c) RemovePArameterAsync(root, context.Document, parameter, c), NameOf(UnusedParametersCodeFixProvider)), diagnostic) + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task + Dim Diagnostic = context.Diagnostics.First() + context.RegisterCodeFix(CodeAction.Create( + String.Format("Remove unused parameter: '{0}'", Diagnostic.Properties("identifier")), + Function(c) RemoveParameterAsync(context.Document, Diagnostic, c), + NameOf(UnusedParametersCodeFixProvider)), + Diagnostic) + Return Task.FromResult(0) End Function - Public Overrides NotOverridable ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(DiagnosticId.UnusedParameters.ToDiagnosticId()) + Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(DiagnosticId.UnusedParameters.ToDiagnosticId()) Public Overrides Function GetFixAllProvider() As FixAllProvider - Return WellKnownFixAllProviders.BatchFixer + Return UnusedParametersCodeFixAllProvider.Instance + End Function + Private Shared Async Function RemoveParameterAsync(document As Document, diagnostic As Diagnostic, cancellationToken As CancellationToken) As Task(Of Solution) + Dim solution = document.Project.Solution + Dim newSolution = solution + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim parameter = root.FindToken(diagnostic.Location.SourceSpan.Start).Parent.FirstAncestorOrSelf(Of ParameterSyntax) + Dim docs = Await RemoveParameterAsync(document, parameter, root, cancellationToken) + For Each doc In docs + newSolution = newSolution.WithDocumentSyntaxRoot(doc.DocumentId, doc.Root) + Next + Return newSolution End Function - Private Shared Async Function RemovePArameterAsync(root As SyntaxNode, document As Document, parameter As ParameterSyntax, cancellationToken As CancellationToken) As Task(Of Solution) + Public Shared Async Function RemoveParameterAsync(document As Document, parameter As ParameterSyntax, root As SyntaxNode, cancellationToken As CancellationToken) As Task(Of List(Of DocumentIdAndRoot)) Dim solution = document.Project.Solution Dim parameterList = DirectCast(parameter.Parent, ParameterListSyntax) Dim parameterPosition = parameterList.Parameters.IndexOf(parameter) Dim newParameterList = parameterList.WithParameters(parameterList.Parameters.Remove(parameter)) - Dim newSolution = solution Dim foundDocument = False - Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken) + Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False) Dim method = parameter.FirstAncestorOfType(GetType(SubNewStatementSyntax), GetType(MethodBlockSyntax)) Dim methodSymbol = semanticModel.GetDeclaredSymbol(method) - Dim references = Await SymbolFinder.FindReferencesAsync(methodSymbol, solution, cancellationToken).ConfigureAwait(False) + Dim references = Await SymbolFinder.FindReferencesAsync(methodSymbol, solution, cancellationToken) Dim documentGroups = references.SelectMany(Function(r) r.Locations).GroupBy(Function(loc) loc.Document) + Dim docs = New List(Of DocumentIdAndRoot) For Each documentGroup In documentGroups Dim referencingDocument = documentGroup.Key Dim locRoot As SyntaxNode @@ -57,18 +71,37 @@ Namespace Usage Dim arguments = If(objectCreation IsNot Nothing, objectCreation.ArgumentList, methodIdentifier.FirstAncestorOfType(Of InvocationExpressionSyntax).ArgumentList) - Dim newArguments = arguments.WithArguments(arguments.Arguments.RemoveAt(parameterPosition)) - replacingArgs.Add(arguments, newArguments) + + ' Attempt to find the parameter as a named argument. Named arguments can only appear once in the argument list. + Dim namedArg = arguments.Arguments.Where(Function(arg) arg.IsNamed).OfType(Of SimpleArgumentSyntax).SingleOrDefault(Function(arg) arg.NameColonEquals.Name.Identifier.Text = parameter.Identifier.Identifier.Text) + If namedArg IsNot Nothing Then + Dim newArguments = arguments.WithArguments(arguments.Arguments.Remove(namedArg)) + replacingArgs.Add(arguments, newArguments) + ElseIf parameter.Modifiers.Any(Function(m) m.IsKind(SyntaxKind.ParamArrayKeyword)) Then + Dim newArguments = arguments + While parameterPosition < newArguments.Arguments.Count + newArguments = newArguments.WithArguments(newArguments.Arguments.RemoveAt(parameterPosition)) + End While + replacingArgs.Add(arguments, newArguments) + ElseIf parameterPosition < arguments.Arguments.Count Then + Dim newArguments = arguments.WithArguments(arguments.Arguments.RemoveAt(parameterPosition)) + replacingArgs.Add(arguments, newArguments) + End If Next Dim newLocRoot = locRoot.ReplaceNodes(replacingArgs.Keys, Function(original, rewritten) replacingArgs(original)) - newSolution = newSolution.WithDocumentSyntaxRoot(referencingDocument.Id, newLocRoot) + docs.Add(New DocumentIdAndRoot With {.DocumentId = referencingDocument.Id, .Root = newLocRoot}) Next If Not foundDocument Then Dim newRoot = root.ReplaceNode(parameterList, newParameterList) Dim newDocument = document.WithSyntaxRoot(newRoot) - newSolution = newSolution.WithDocumentSyntaxRoot(document.Id, newRoot) + docs.Add(New DocumentIdAndRoot With {.DocumentId = document.Id, .Root = newRoot}) End If - Return newSolution + Return docs End Function + + Public Structure DocumentIdAndRoot + Friend DocumentId As DocumentId + Friend Root As SyntaxNode + End Structure End Class End Namespace \ No newline at end of file diff --git a/src/VisualBasic/CodeCracker/packages.config b/src/VisualBasic/CodeCracker/packages.config deleted file mode 100644 index 3eed796a7..000000000 --- a/src/VisualBasic/CodeCracker/packages.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/test/CSharp/AnalyzeCecil.ps1 b/test/CSharp/AnalyzeCecil.ps1 index 55ba52733..a2700956e 100644 --- a/test/CSharp/AnalyzeCecil.ps1 +++ b/test/CSharp/AnalyzeCecil.ps1 @@ -3,10 +3,11 @@ $baseDir = "$([System.IO.Path]::GetTempPath())$([System.Guid]::NewGuid().ToStri $projectDir = "$baseDir\cecil" $logDir = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\log") $logFile = "$logDir\cecil.log" -$analyzerDll = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\src\CSharp\CodeCracker\bin\Debug\CodeCracker.CSharp.dll") +$analyzerDll = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\src\CSharp\CodeCracker\bin\Release\CodeCracker.CSharp.dll") +$analyzerCommonDll = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\src\CSharp\CodeCracker\bin\Release\CodeCracker.Common.dll") $gitPath = "https://github.com/jbevain/cecil.git" -if (Test-Path "C:\proj\cecil") { - $gitPath = "c:\proj\cecil" +if (Test-Path "C:\p\cecil") { + $gitPath = "c:\p\cecil" } echo "Saving to log file $logFile" @@ -56,7 +57,10 @@ foreach($csproj in $csprojs) $itemGroup = $xmlProj.CreateElement("ItemGroup", $xmlProj.Project.xmlns) $analyzer = $xmlProj.CreateElement("Analyzer", $xmlProj.Project.xmlns) $analyzer.SetAttribute("Include", $analyzerDll) + $analyzerCommon = $xmlProj.CreateElement("Analyzer", $xmlProj.Project.xmlns) + $analyzerCommon.SetAttribute("Include", $analyzerCommonDll) $itemGroup.AppendChild($analyzer) | Out-Null + $itemGroup.AppendChild($analyzerCommon) | Out-Null $xmlProj.DocumentElement.AppendChild($itemGroup) | Out-Null $xmlProj.Save($csproj.FullName) } @@ -66,15 +70,15 @@ echo "Building..." foreach($sln in $slns) { echo "Building $($sln.FullName)..." - msbuild $sln.FullName /m /t:rebuild /v:detailed /p:Configuration="net_4_0_Debug" >> $logFile + msbuild $sln.FullName /m /t:rebuild /v:detailed /p:Configuration="net_4_5_Debug" >> $logFile } -$ccBuildErrors = cat $logFile | Select-String "info AnalyzerDriver: The Compiler Analyzer 'CodeCracker" +$ccBuildErrors = cat $logFile | Select-String "info AD0001: The Compiler Analyzer 'CodeCracker" if ($ccBuildErrors -ne $null) { write-host "Errors found (see $logFile):" foreach($ccBuildError in $ccBuildErrors) { - Write-Host -ForegroundColor DarkRed "$($ccBuildError.LineNumber) $($ccBuildError.Line)" + Write-Host -ForegroundColor DarkRed "$($ccBuildError.LineNumber) $($ccBuildError.Line)" } throw "Errors found on the cecil analysis" } \ No newline at end of file diff --git a/test/CSharp/AnalyzeCoreFx.ps1 b/test/CSharp/AnalyzeCoreFx.ps1 index e6f28ab9c..c418b147a 100644 --- a/test/CSharp/AnalyzeCoreFx.ps1 +++ b/test/CSharp/AnalyzeCoreFx.ps1 @@ -3,7 +3,7 @@ $baseDir = "$([System.IO.Path]::GetTempPath())$([System.Guid]::NewGuid().ToStri $projectDir = "$baseDir\corefx" $logDir = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\log") $logFile = "$logDir\corefx.log" -$analyzerDll = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\src\CSharp\CodeCracker\bin\Debug\CodeCracker.CSharp.dll") +$analyzerDll = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\src\CSharp\CodeCracker\bin\Release\CodeCracker.CSharp.dll") $gitPath = "https://github.com/dotnet/corefx.git" if (Test-Path "C:\proj\corefx") { $gitPath = "C:\proj\corefx" @@ -69,7 +69,7 @@ if ($ccBuildErrors -ne $null) Write-Host "Errors found (see $logFile):" foreach($ccBuildError in $ccBuildErrors) { - Write-Host -ForegroundColor DarkRed "$($ccBuildError.LineNumber) $($ccBuildError.Line)" + Write-Host -ForegroundColor DarkRed "$($ccBuildError.LineNumber) $($ccBuildError.Line)" } throw "Errors found on the corefx analysis" } diff --git a/test/CSharp/AnalyzeRoslyn.ps1 b/test/CSharp/AnalyzeRoslyn.ps1 index efd208be6..0ac60d2fb 100644 --- a/test/CSharp/AnalyzeRoslyn.ps1 +++ b/test/CSharp/AnalyzeRoslyn.ps1 @@ -3,8 +3,8 @@ $baseDir = "$([System.IO.Path]::GetTempPath())$([System.Guid]::NewGuid().ToStri $projectDir = "$baseDir\roslyn" $logDir = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\log") $logFile = "$logDir\roslyn.log" -$analyzerDll = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\src\CSharp\CodeCracker\bin\Debug\CodeCracker.CSharp.dll") -$analyzerDllVB = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\src\VisualBasic\CodeCracker\bin\Debug\CodeCracker.VisualBasic.dll") +$analyzerDll = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\src\CSharp\CodeCracker\bin\Release\CodeCracker.CSharp.dll") +$analyzerDllVB = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\src\VisualBasic\CodeCracker\bin\Release\CodeCracker.VisualBasic.dll") $gitPath = "https://github.com/dotnet/roslyn.git" if (Test-Path "c:\proj\roslyn") { $gitPath = "c:\proj\roslyn" @@ -101,7 +101,7 @@ if ($ccBuildErrors -ne $null) Write-Host "Errors found (see $logFile):" foreach($ccBuildError in $ccBuildErrors) { - Write-Host -ForegroundColor DarkRed "$($ccBuildError.LineNumber) $($ccBuildError.Line)" + Write-Host -ForegroundColor DarkRed "$($ccBuildError.LineNumber) $($ccBuildError.Line)" } throw "Errors found on the roslyn analysis" } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj b/test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj index 98f8cdc3d..4a21168c3 100644 --- a/test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj +++ b/test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj @@ -1,8 +1,5 @@  - - - - + Debug AnyCPU @@ -18,6 +15,7 @@ + AD0001 true @@ -41,93 +39,29 @@ false - - ..\..\..\packages\FluentAssertions.3.4.1\lib\net45\FluentAssertions.dll - True - - - ..\..\..\packages\FluentAssertions.3.4.1\lib\net45\FluentAssertions.Core.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.Workspaces.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.Desktop.dll - True - - - ..\..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll - True - - - ..\..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll - True - - - ..\..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll - True - - - ..\..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll - True - - - ..\..\..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll - True - - - ..\..\..\packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll - True - + + + + + + + + @@ -138,11 +72,14 @@ + + + @@ -159,7 +96,6 @@ - @@ -168,6 +104,7 @@ + @@ -204,9 +141,6 @@ - - Designer - @@ -214,7 +148,7 @@ CodeCracker.Common - {FF1097FB-A890-461B-979E-064697891B96} + {ff1097fb-a890-461b-979e-064697891b96} CodeCracker @@ -232,26 +166,5 @@ - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Design/CatchEmptyTests.cs b/test/CSharp/CodeCracker.Test/Design/CatchEmptyTests.cs index 169cfc7a9..cd1582067 100644 --- a/test/CSharp/CodeCracker.Test/Design/CatchEmptyTests.cs +++ b/test/CSharp/CodeCracker.Test/Design/CatchEmptyTests.cs @@ -1,12 +1,13 @@ -using CodeCracker.CSharp.Design; -using System.Threading.Tasks; +using System.Threading.Tasks; +using CodeCracker.CSharp.Design; using Xunit; namespace CodeCracker.Test.CSharp.Design { - public class CatchEmptyTests : CodeFixVerifier - { + using Verify = CSharpCodeFixVerifier; + public class CatchEmptyTests + { [Fact] public async Task CatchEmptyAnalyserCreateDiagnostic() { @@ -17,7 +18,7 @@ namespace ConsoleApplication1 { class TypeName { - public async Task Foo() + public async {|CS0246:Task|} {|CS0161:{|CS1983:Foo|}|}() { try { @@ -31,9 +32,118 @@ public async Task Foo() } }"; - await VerifyCSharpHasNoDiagnosticsAsync(source); + await Verify.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task EmptyCatchEndsWithThrowNoDiagnostic() + { + const string source = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public async {|CS0246:Task|} {|CS0161:{|CS1983:Foo|}|}() + { + try + { + // do something + } + catch + { + throw; + } + } + } + }"; + await Verify.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task EmptyCatchWithNestedThrowNoDiagnostic() + { + const string source = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + int x; + public void Foo() + { + try + { + // do something + } + catch + { + if (x == 1) + throw; + else + throw; + } + } + } + }"; + await Verify.VerifyAnalyzerAsync(source); + } + [Fact] + public async Task NotAllowedToReturnOutOfEmtpyCatchBlock() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + int x; + public void Foo() + { + try + { + // do something + } + [|catch + { + if (x == 1) + throw; + else + return; + }|] + } } + }"; + + const string fixtest = @" + using System; + namespace ConsoleApplication1 + { + class TypeName + { + int x; + public void Foo() + { + try + { + // do something + } + catch (Exception ex) + { + if (x == 1) + throw; + else + return; + } + } + } + }"; + await Verify.VerifyCodeFixAsync(test, fixtest); + } [Fact] public async Task WhenFindCatchEmptyThenPutExceptionClass() { @@ -44,16 +154,16 @@ namespace ConsoleApplication1 { class TypeName { - public async Task Foo() + public void Foo() { try { // do something } - catch + [|catch { int x = 0; - } + }|] } } }"; @@ -65,20 +175,68 @@ namespace ConsoleApplication1 { class TypeName { - public async Task Foo() + public void Foo() { try { // do something } - catch (Exception ex) + catch (Exception ex) + { + int x = 0; + } + } + } + }"; + await Verify.VerifyCodeFixAsync(test, fixtest); + } + [Fact] + public async Task AddCatchEvenIfThereIsReturnInBlock() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public void Foo() + { + try + { + // do something + } + [|catch { int x = 0; + return; + }|] + } + } + }"; + + const string fixtest = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public void Foo() + { + try + { + // do something } + catch (Exception ex) + { + int x = 0; + return; } } + } }"; - await VerifyCSharpFixAsync(test, fixtest, 0); + await Verify.VerifyCodeFixAsync(test, fixtest); } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Design/CopyEventToVariableBeforeFireTests.cs b/test/CSharp/CodeCracker.Test/Design/CopyEventToVariableBeforeFireTests.cs deleted file mode 100644 index 376ba82f4..000000000 --- a/test/CSharp/CodeCracker.Test/Design/CopyEventToVariableBeforeFireTests.cs +++ /dev/null @@ -1,272 +0,0 @@ -using CodeCracker.CSharp.Design; -using Microsoft.CodeAnalysis; -using Xunit; - -namespace CodeCracker.Test.CSharp.Design -{ - public class CopyEventToVariableBeforeFireTests : CodeFixVerifier - { - [Fact] - public async void WarningIfEventIsFiredDirectly() - { - const string test = @" - public class MyClass - { - public event System.EventHandler MyEvent; - - public void Execute() - { - MyEvent(this, System.EventArgs.Empty); - } - }"; - - var expected = new DiagnosticResult - { - Id = DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId(), - Message = "Copy the 'MyEvent' event to a variable before fire it.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 25) } - }; - - await VerifyCSharpDiagnosticAsync(test, expected); - } - - [Fact] - public async void WarningIfCustomEventIsFiredDirectly() - { - const string test = @" - public class MyArgs : System.EventArgs - { - public string Info { get; set; } - } - - public class MyClass - { - public event System.EventHandler MyEvent; - - public void Execute() - { - MyEvent(this, new MyArgs() { Info = ""ping"" }); - } - }"; - - var expected = new DiagnosticResult - { - Id = DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId(), - Message = "Copy the 'MyEvent' event to a variable before fire it.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 13, 25) } - }; - - await VerifyCSharpDiagnosticAsync(test, expected); - } - - [Fact] - public async void WarningIfCustomEventWithCustomDelegateIsFiredDirectly() - { - const string test = @" - public class MyArgs : System.EventArgs - { - public string Info { get; set; } - } - - public delegate void Executed (object sender, MyArgs args); - - public class MyClass - { - public event Executed MyEvent; - - public void Execute() - { - MyEvent(this, new MyArgs() { Info = ""ping"" }); - } - }"; - - var expected = new DiagnosticResult - { - Id = DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId(), - Message = "Copy the 'MyEvent' event to a variable before fire it.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 15, 25) } - }; - - await VerifyCSharpDiagnosticAsync(test, expected); - } - - [Fact] - public async void NotWarningIfEventIsCopiedToLocalVariableBeforeFire() - { - const string test = @" - public class MyClass - { - public event System.EventHandler MyEvent; - - public void Execute() - { - var handler = MyEvent; - if (handler != null) - handler(this, System.EventArgs.Empty); - } - }"; - - await VerifyCSharpHasNoDiagnosticsAsync(test); - } - - [Fact] - public async void NotWarningIfIsNotAnEvent() - { - const string test = @" - public class MyClass - { - public void Execute() - { - MyClass.Run(null); - } - - public static void Run(object obj) - { - - } - }"; - - await VerifyCSharpHasNoDiagnosticsAsync(test); - } - - [Fact] - public async void NotWarningIfIsAParameter() - { - const string test = @" - public class MyClass - { - public void Execute(Action action) - { - action(); - } - }"; - - await VerifyCSharpHasNoDiagnosticsAsync(test); - } - - [Fact] - public async void WhenEventIsFiredDirectlyShouldCopyItToVariable() - { - const string source = @" - public class MyClass - { - public event System.EventHandler MyEvent; - - public void Execute() - { - MyEvent(this, System.EventArgs.Empty); - } - }"; - - const string fixtest = @" - public class MyClass - { - public event System.EventHandler MyEvent; - - public void Execute() - { - var handler = MyEvent; - if (handler != null) - handler(this, System.EventArgs.Empty); - } - }"; - - await VerifyCSharpFixAsync(source, fixtest, 0); - } - - [Fact] - public async void KeepCommentsWhenReplacedWithCodeFix() - { - const string source = @" - public class MyClass - { - public event System.EventHandler MyEvent; - - public void Execute() - { - //comment - MyEvent(this, System.EventArgs.Empty); //Some Comment - } - }"; - - const string fixtest = @" - public class MyClass - { - public event System.EventHandler MyEvent; - - public void Execute() - { - //comment - var handler = MyEvent; - if (handler != null) - handler(this, System.EventArgs.Empty); //Some Comment - } - }"; - - await VerifyCSharpFixAsync(source, fixtest, 0); - } - - [Fact] - public async void FixWhenInvocationIsInsideABlockWithoutBraces() - { - const string source = @" - public class MyClass - { - public event System.EventHandler MyEvent; - bool raiseEvents = true; - - public void Execute() - { - if (raiseEvents) MyEvent(this, System.EventArgs.Empty); - } - }"; - - const string fixtest = @" - public class MyClass - { - public event System.EventHandler MyEvent; - bool raiseEvents = true; - - public void Execute() - { - if (raiseEvents) - { - var handler = MyEvent; - if (handler != null) - handler(this, System.EventArgs.Empty); - } - } - }"; - - await VerifyCSharpFixAsync(source, fixtest, 0); - } - - [Fact] - public async void IgnoreMemberAccess() - { - var test = @"var tuple = new Tuple(1, null); -tuple.Item2();".WrapInCSharpMethod(); - await VerifyCSharpHasNoDiagnosticsAsync(test); - } - - [Fact] - public async void NotWarningIfExpressionBodied() - { - const string test = @" - public class MyClass - { - public int Foo(int par1, int par2) => FuncCalc(par1,par2); - - public int FuncCalc(int p1, int p2) - { - return p1*p2; - } - - }"; - await VerifyCSharpHasNoDiagnosticsAsync(test); - } - } -} \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Design/EmptyCatchBlockTests.cs b/test/CSharp/CodeCracker.Test/Design/EmptyCatchBlockTests.cs index 245361631..87ea33784 100644 --- a/test/CSharp/CodeCracker.Test/Design/EmptyCatchBlockTests.cs +++ b/test/CSharp/CodeCracker.Test/Design/EmptyCatchBlockTests.cs @@ -4,10 +4,13 @@ namespace CodeCracker.Test.CSharp.Design { - public class EmptyCatchBlockTests : CodeFixVerifier + using Verify = CSharpCodeFixVerifier; + + public class EmptyCatchBlockTests { readonly string test = @" using System; + using System.Threading.Tasks; namespace ConsoleApplication1 { @@ -19,9 +22,9 @@ public async Task Foo() { // do something } - catch + [|catch { - } + }|] } } }"; @@ -35,7 +38,7 @@ namespace ConsoleApplication1 { class TypeName { - public async Task Foo() + public async {|CS0246:Task|} {|CS0161:{|CS1983:Foo|}|}() { try { @@ -48,7 +51,7 @@ public async Task Foo() } } }"; - await VerifyCSharpHasNoDiagnosticsAsync(test); + await Verify.VerifyAnalyzerAsync(test); } [Fact] @@ -57,6 +60,7 @@ public async Task WhenRemoveTryCatchStatement() const string fixtest = @" using System; + using System.Threading.Tasks; namespace ConsoleApplication1 { @@ -70,7 +74,7 @@ public async Task Foo() } } }"; - await VerifyCSharpFixAsync(test, fixtest, 0,false,true); + await Verify.VerifyCodeFixAsync(test, fixtest); } [Fact] @@ -78,6 +82,7 @@ public async Task WhenRemoveTryCatchStatementAndPutComment() { const string fixtest = @" using System; + using System.Threading.Tasks; namespace ConsoleApplication1 { @@ -85,15 +90,20 @@ class TypeName { public async Task Foo() { - { - // do something - } - //TODO: Consider reading MSDN Documentation about how to use Try...Catch => http://msdn.microsoft.com/en-us/library/0yd65esw.aspx + { + // do something } + //TODO: Consider reading MSDN Documentation about how to use Try...Catch => http://msdn.microsoft.com/en-us/library/0yd65esw.aspx + } } }"; - await VerifyCSharpFixAsync(test, fixtest, 1, false, true); + await new Verify.Test + { + TestCode = test, + FixedCode = fixtest, + CodeFixIndex = 1, + }.RunAsync(); } [Fact] @@ -101,6 +111,7 @@ public async Task WhenPutExceptionClassInCatchBlock() { const string fixtest = @" using System; + using System.Threading.Tasks; namespace ConsoleApplication1 { @@ -112,15 +123,72 @@ public async Task Foo() { // do something } - catch (Exception ex) - { - throw; - } + catch (Exception ex) + { + throw; } } + } }"; + await new Verify.Test + { + TestCode = test, + FixedCode = fixtest, + CodeFixIndex = 2, + }.RunAsync(); + } + + + [Fact] + public async Task WhenMultipleCatchOnlyRemoveSelected() + { + const string test = @" +using System; - await VerifyCSharpFixAsync(test, fixtest, 2, false, true); +namespace ConsoleApplication1 +{ + class TypeName + { + public async {|CS0246:Task|} {|CS0161:{|CS1983:Foo|}|}() + { + int x; + try + { + // do something + } + [|catch (System.ArgumentException ae) + { + }|] + catch (System.Exception ex) + { + x = 1; + } + } + } +}"; + + const string fixtest = @" +using System; + +namespace ConsoleApplication1 +{ + class TypeName + { + public async {|CS0246:Task|} {|CS0161:{|CS1983:Foo|}|}() + { + int x; + try + { + // do something + } + catch (System.Exception ex) + { + x = 1; + } + } + } +}"; + await Verify.VerifyCodeFixAsync(test, fixtest); } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Design/InconsistentAccessibilityTests.FieldPropertyType.cs b/test/CSharp/CodeCracker.Test/Design/InconsistentAccessibilityTests.FieldPropertyType.cs index f90adea3e..b7d198c6d 100644 --- a/test/CSharp/CodeCracker.Test/Design/InconsistentAccessibilityTests.FieldPropertyType.cs +++ b/test/CSharp/CodeCracker.Test/Design/InconsistentAccessibilityTests.FieldPropertyType.cs @@ -1,16 +1,19 @@ using System.Threading.Tasks; +using CodeCracker.CSharp.Design.InconsistentAccessibility; using Xunit; namespace CodeCracker.Test.CSharp.Design { - public partial class InconsistentAccessibilityTests : CodeFixVerifier + using Verify = CSharpCodeFixVerifier; + + public partial class InconsistentAccessibilityTests { [Fact] public async Task ShouldFixInconsistentAccessibilityErrorInClassFieldTypeAsync() { const string testCode = @"public class Dependent { - public DependedUpon field; + public DependedUpon {|CS0052:field|}; } class DependedUpon @@ -25,7 +28,7 @@ public class DependedUpon { }"; - await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(testCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -33,7 +36,7 @@ public async Task ShouldFixInconsistentAccessibilityErrorInClassFieldTypeWhenQua { const string testCode = @"public class Dependent { - internal Dependent.DependedUpon field; + internal Dependent.DependedUpon {|CS0052:field|}; class DependedUpon { @@ -49,7 +52,7 @@ internal class DependedUpon } }"; - await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(testCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -57,7 +60,7 @@ public async Task ShouldFixInconsistentAccessibilityErrorInStructFieldTypeAsync( { const string testCode = @"public struct Dependent { - public DependedUpon field, field1; + public DependedUpon {|CS0052:field|}, {|CS0052:field1|}; } class DependedUpon @@ -72,7 +75,7 @@ public class DependedUpon { }"; - await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(testCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -80,7 +83,7 @@ public async Task ShouldFixInconsistentAccessibilityErrorInPropertyTypeAsync() { const string testCode = @"public class Dependent { - public DependedUpon Property + public DependedUpon {|CS0053:Property|} { get { return null; } set { } @@ -104,7 +107,7 @@ public class DependedUpon { }"; - await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(testCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -112,7 +115,7 @@ public async Task ShouldFixInconsistentAccessibilityErrorInPropertyTypeWhenUsing { const string testCode = @"public class Dependent { - public DependedUpon Property + public DependedUpon {|CS0053:Property|} { get; set; @@ -136,7 +139,7 @@ public class DependedUpon { }"; - await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(testCode, fixedCode).ConfigureAwait(false); } } } diff --git a/test/CSharp/CodeCracker.Test/Design/InconsistentAccessibilityTests.MethodIndexerParameter.cs b/test/CSharp/CodeCracker.Test/Design/InconsistentAccessibilityTests.MethodIndexerParameter.cs index 822d16141..34b11d5ef 100644 --- a/test/CSharp/CodeCracker.Test/Design/InconsistentAccessibilityTests.MethodIndexerParameter.cs +++ b/test/CSharp/CodeCracker.Test/Design/InconsistentAccessibilityTests.MethodIndexerParameter.cs @@ -1,11 +1,12 @@ -using CodeCracker.CSharp.Design.InconsistentAccessibility; -using Microsoft.CodeAnalysis.CodeFixes; -using System.Threading.Tasks; +using System.Threading.Tasks; +using CodeCracker.CSharp.Design.InconsistentAccessibility; using Xunit; namespace CodeCracker.Test.CSharp.Design { - public partial class InconsistentAccessibilityTests : CodeFixVerifier + using Verify = CSharpCodeFixVerifier; + + public partial class InconsistentAccessibilityTests { [Theory] [InlineData("class","internal")] @@ -17,11 +18,11 @@ public async Task ShouldChangeAccessibilityWhenErrorInConstructor(string type, s var sourceCode = @" public " + type + @" Dependent { - public Dependent(int a, DependendedUpon d, string b) + public {|CS0051:Dependent|}(int a, DependendedUpon d, string b) { } } -" + dependedUponModfifier + @" class DependendedUpon +" + dependedUponModfifier + (dependedUponModfifier.Length > 0 ? " " : "") + @"class DependendedUpon { }"; @@ -36,7 +37,7 @@ public class DependendedUpon { }"; - await VerifyCSharpFixAsync(sourceCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(sourceCode, fixedCode).ConfigureAwait(false); } [Theory] @@ -49,7 +50,7 @@ public class DependendedUpon [InlineData("protected", "internal /* comment */", "protected /* comment */")] [InlineData("protected", "private", "protected")] [InlineData("protected internal", "", "protected internal")] - [InlineData("protected internal", " protected ", " protected internal ")] + [InlineData("protected internal", " protected ", "protected internal")] [InlineData("protected internal", "internal", "protected internal")] [InlineData("internal protected", "private", "protected internal")] [InlineData("internal", "protected", "internal")] @@ -59,11 +60,11 @@ public async Task ShouldChangeAccessibilityWhenErrrorInMethod(string methodModif var sourceCode = @" public class Dependent { - " + methodModifier + @" void SomeMethod(DependendedUpon d) + " + methodModifier + @" void {|CS0051:SomeMethod|}(DependendedUpon d) { } - " + dependedUponModifier + @" class DependendedUpon + " + dependedUponModifier + (dependedUponModifier.Length > 0 ? " " : "") + @"class DependendedUpon { } }"; @@ -80,7 +81,7 @@ public class Dependent } }"; - await VerifyCSharpFixAsync(sourceCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(sourceCode, fixedCode).ConfigureAwait(false); } [Theory] @@ -91,9 +92,9 @@ public async Task ShouldChangeAccessibilityWhenErrorInInterface(string interface var sourceCode = @" " + interfaceAccessibilityModifier + @" interface Dependent { - void SomeMethod(DependendedUpon d); + void {|CS0051:SomeMethod|}(DependendedUpon d); } -" + dependedUponModifier + @" class DependendedUpon +" + dependedUponModifier + (dependedUponModifier.Length > 0 ? " " : "") + @"class DependendedUpon { }"; @@ -106,7 +107,7 @@ public async Task ShouldChangeAccessibilityWhenErrorInInterface(string interface { }"; - await VerifyCSharpFixAsync(sourceCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(sourceCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -115,7 +116,7 @@ public async Task ShouldChangeAccessibilityWhenQualifiedNameIsUsedForParameterTy const string sourceCode = @" public class Dependent { - public Dependent(Dependent.DependendedUpon d) + public {|CS0051:Dependent|}(Dependent.DependendedUpon d) { } @@ -136,7 +137,7 @@ public class DependendedUpon } }"; - await VerifyCSharpFixAsync(sourceCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(sourceCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -145,7 +146,7 @@ public async Task ShouldChangeAccessibilityWhenAliasQualifiedNameIsUsedForParame const string sourceCode = @" public class Dependent { - public Dependent(global::DependendedUpon d) + public {|CS0051:Dependent|}(global::DependendedUpon d) { } } @@ -164,7 +165,7 @@ public class DependendedUpon { }"; - await VerifyCSharpFixAsync(sourceCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(sourceCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -173,7 +174,7 @@ public async Task ShouldChangeAccessibilityWhenUsingDelegateAsParameter() const string sourceCode = @" public class Dependent { - public Dependent(DependedUpon d) + public {|CS0051:Dependent|}(DependedUpon d) { } } @@ -188,7 +189,7 @@ public Dependent(DependedUpon d) } public delegate void DependedUpon(int a);"; - await VerifyCSharpFixAsync(sourceCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(sourceCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -197,7 +198,7 @@ public async Task ShouldChangeAccessibilityWhenUsingEnumAsParameter() const string sourceCode = @" public class Dependent { - public Dependent(DependedUpon d) + public {|CS0051:Dependent|}(DependedUpon d) { } } @@ -212,7 +213,7 @@ public Dependent(DependedUpon d) } public enum DependedUpon {}"; - await VerifyCSharpFixAsync(sourceCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(sourceCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -221,7 +222,7 @@ public async Task ShouldChangeAccessibilityWhenUsingInterfaceAsParameter() const string sourceCode = @" public class Dependent { - public Dependent(DependedUpon d) + public {|CS0051:Dependent|}(DependedUpon d) { } } @@ -236,7 +237,7 @@ public Dependent(DependedUpon d) } public interface DependedUpon {}"; - await VerifyCSharpFixAsync(sourceCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(sourceCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -245,7 +246,7 @@ public async Task ShouldChangeAccessibilityWhenUsingGenericClassAsParameter() const string sourceCode = @" public class Dependent { - public Dependent(DependedUpon d) + public {|CS0051:Dependent|}(DependedUpon d) { } } @@ -260,7 +261,7 @@ public Dependent(DependedUpon d) } public class DependedUpon {}"; - await VerifyCSharpFixAsync(sourceCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(sourceCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -269,7 +270,7 @@ public async Task ShouldChangeAccessibilityToAllPartialDeclarationsAsync() const string sourceCode = @" public class Dependent { - public Dependent(DependedUpon d) + public {|CS0051:Dependent|}(DependedUpon d) { } } @@ -288,7 +289,7 @@ public partial class DependedUpon {} class SomeClass {} public partial class DependedUpon {}"; - await VerifyCSharpFixAsync(sourceCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(sourceCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -296,7 +297,7 @@ public async Task ShouldChangeAccessibilityWhenErrorInIndexerParameterAsync() { const string sourceCode = @"public class Dependent { - public int this[int idx, DependedUpon dependedUpon] + public int {|CS0055:this|}[int idx, DependedUpon dependedUpon] { get { return 0; } set { } @@ -320,7 +321,7 @@ public class DependedUpon { }"; - await VerifyCSharpFixAsync(sourceCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(sourceCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -328,7 +329,7 @@ public async Task ShouldChangeAccessibilityWhenErrorInIndexerParameterUsingQuali { const string sourceCode = @"public class Dependent { - public int this[int idx, Dependent.DependedUpon dependedUpon] + public int {|CS0055:this|}[int idx, Dependent.DependedUpon dependedUpon] { get { return 0; } set { } @@ -352,7 +353,7 @@ public class DependedUpon } }"; - await VerifyCSharpFixAsync(sourceCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(sourceCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -360,7 +361,7 @@ public async Task ShouldChangeAccessibilityWhenErrorInMoreThanOneIndexerParamete { const string sourceCode = @"public class Dependent { - public int this[int idx, Dependent.DependedUpon dependedUpon, DependedUpon2 dependedUpon2] + public int {|CS0055:{|CS0055:this|}|}[int idx, Dependent.DependedUpon dependedUpon, DependedUpon2 dependedUpon2] { get { return 0; } set { } @@ -392,9 +393,7 @@ public class DependedUpon2 { }"; - await VerifyCSharpFixAsync(sourceCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(sourceCode, fixedCode).ConfigureAwait(false); } - - protected override CodeFixProvider GetCodeFixProvider() => new InconsistentAccessibilityCodeFixProvider(); } } diff --git a/test/CSharp/CodeCracker.Test/Design/InconsistentAccessibilityTests.MethodIndexerReturnType.cs b/test/CSharp/CodeCracker.Test/Design/InconsistentAccessibilityTests.MethodIndexerReturnType.cs index e397c7a4f..7b5d324a2 100644 --- a/test/CSharp/CodeCracker.Test/Design/InconsistentAccessibilityTests.MethodIndexerReturnType.cs +++ b/test/CSharp/CodeCracker.Test/Design/InconsistentAccessibilityTests.MethodIndexerReturnType.cs @@ -1,16 +1,19 @@ using System.Threading.Tasks; +using CodeCracker.CSharp.Design.InconsistentAccessibility; using Xunit; namespace CodeCracker.Test.CSharp.Design { - public partial class InconsistentAccessibilityTests : CodeFixVerifier + using Verify = CSharpCodeFixVerifier; + + public partial class InconsistentAccessibilityTests { [Fact] public async Task ShouldFixInconsistentAccessibilityErrorInMethodReturnTypeAsync() { const string testCode = @"public class Dependent { - public DependedUpon Method() + public DependedUpon {|CS0050:Method|}() { return null; } @@ -31,7 +34,7 @@ public class DependedUpon { }"; - await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(testCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -39,7 +42,7 @@ public async Task ShouldFixInconsistentAccessibilityErrorInMethodReturnTypeWhenQ { const string testCode = @"public class Dependent { - public Dependent.DependedUpon Method() + public Dependent.DependedUpon {|CS0050:Method|}() { return null; } @@ -60,7 +63,7 @@ public class DependedUpon } }"; - await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(testCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -68,7 +71,7 @@ public async Task ShouldFixInconsistentAccessibilityErrorInIndexerReturnTypeAsyn { const string testCode = @"public class Dependent { - public DependedUpon this[int a] + public DependedUpon {|CS0054:this|}[int a] { get { return null; } set { } @@ -91,7 +94,7 @@ public class DependedUpon { }"; - await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(testCode, fixedCode).ConfigureAwait(false); } [Fact] @@ -99,7 +102,7 @@ public async Task ShouldFixInconsistentAccessibilityErrorInIndexerReturnTypeWhen { const string testCode = @"public class Dependent { - public Dependent.DependedUpon this[int a] + public Dependent.DependedUpon {|CS0054:this|}[int a] { get { return null; } set { } @@ -122,7 +125,7 @@ public class DependedUpon } }"; - await VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); + await Verify.VerifyCodeFixAsync(testCode, fixedCode).ConfigureAwait(false); } } } diff --git a/test/CSharp/CodeCracker.Test/Design/MakeMethodStaticTests.cs b/test/CSharp/CodeCracker.Test/Design/MakeMethodStaticTests.cs index 447160fa6..eb64ea7dc 100644 --- a/test/CSharp/CodeCracker.Test/Design/MakeMethodStaticTests.cs +++ b/test/CSharp/CodeCracker.Test/Design/MakeMethodStaticTests.cs @@ -1,8 +1,8 @@ using CodeCracker.CSharp.Design; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; -using System; namespace CodeCracker.Test.CSharp.Design { @@ -101,13 +101,9 @@ class C : I public async Task WithDiagnostic(string code) { var source = code.WrapInCSharpClass(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.MakeMethodStatic.ToDiagnosticId(), - Message = string.Format(MakeMethodStaticAnalyzer.MessageFormat, "Foo"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 18) } - }; + var expected = new DiagnosticResult(DiagnosticId.MakeMethodStatic.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(8, 14) + .WithMessage(string.Format(MakeMethodStaticAnalyzer.MessageFormat, "Foo")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -123,7 +119,7 @@ static void Foo() { } #endregion")] [InlineData(@" ///Method summary -void Foo() { }", +void Foo() { }", @" ///Method summary static void Foo() { }")] @@ -540,5 +536,537 @@ await VerifyCSharpHasNoDiagnosticsAsync(new string[] { nunitWithoutTestFixtureWithTestCaseSourceAttributeAndOtherNonAttributedMethodsSource }); } + + [Theory] + [InlineData(@"void Application_AuthenticateRequest() { }")] + [InlineData(@"void Application_BeginRequest() { }")] + [InlineData(@"void Application_End() { }")] + [InlineData(@"void Application_EndRequest() { }")] + [InlineData(@"void Application_Error() { }")] + [InlineData(@"void Application_Start(object sender, EventArgs e) { }")] + [InlineData(@"void Session_End() { }")] + [InlineData(@"void Session_Start() { }")] + public async Task IgnoreKnownWebFormsMethods(string code) + { + var source = $@" + using System; + namespace System.Web + {{ + public class HttpApplication {{ }} + }} + namespace MyWebApp1 + {{ + public class Global : System.Web.HttpApplication + {{ + {code} + }} + }}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task FixWhenUsedAsMethodGroup() + { + const string source = @" +class Bar +{ + void ShouldBeStatic() + { + } + void Caller() + { + Foo.M(new Baz(ShouldBeStatic)); + } +} +class Foo +{ + public static void M(Baz b) { } +} +class Baz +{ + public Baz(Action a) + { + } +}"; + const string fixtest = @" +class Bar +{ + static void ShouldBeStatic() + { + } + void Caller() + { + Foo.M(new Baz(ShouldBeStatic)); + } +} +class Foo +{ + public static void M(Baz b) { } +} +class Baz +{ + public Baz(Action a) + { + } +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + + [Fact] + public async Task FixWhenUsedAsMethodGroupInMultipleDocs() + { + const string source1 = @" +namespace Ns +{ + class Bar + { + public void ShouldBeStatic() + { + } + } +}"; + const string source2 = @" +namespace Ns +{ + class Foo + { + public static void M(Baz b) { } + } + class Baz + { + public Baz(Action a) + { + } + static void Caller() + { + Foo.M(new Baz(new Bar().ShouldBeStatic)); + } + } +}"; + const string fixtest1 = @" +namespace Ns +{ + class Bar + { + public static void ShouldBeStatic() + { + } + } +}"; + const string fixtest2 = @" +namespace Ns +{ + class Foo + { + public static void M(Baz b) { } + } + class Baz + { + public Baz(Action a) + { + } + static void Caller() + { + Foo.M(new Baz(Bar.ShouldBeStatic)); + } + } +}"; + await VerifyCSharpFixAllAsync(new[] { source1, source2 }, new[] { fixtest1, fixtest2 }); + } + + [Fact] + public async Task FixWhenUsedAsMethodGroupWithThis() + { + const string source = @" +class Bar +{ + void ShouldBeStatic() + { + } + void Caller() + { + Foo.M(new Baz(this.ShouldBeStatic)); + } +} +class Foo +{ + public static void M(Baz b) { } +} +class Baz +{ + public Baz(Action a) + { + } +}"; + const string fixtest = @" +class Bar +{ + static void ShouldBeStatic() + { + } + void Caller() + { + Foo.M(new Baz(ShouldBeStatic)); + } +} +class Foo +{ + public static void M(Baz b) { } +} +class Baz +{ + public Baz(Action a) + { + } +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenUsedAsMethodGroupWithThisAndAnOverload() + { + const string source = @" +class Bar +{ + private int j; + void ShouldBeStatic(int i) + { + j = i; + } + void ShouldBeStatic() + { + } + void Caller() + { + Foo.M(new Baz(this.ShouldBeStatic)); + } +} +class Foo +{ + public static void M(Baz b) { } +} +class Baz +{ + public Baz(Action a) + { + } +}"; + const string fixtest = @" +class Bar +{ + private int j; + void ShouldBeStatic(int i) + { + j = i; + } + static void ShouldBeStatic() + { + } + void Caller() + { + Foo.M(new Baz(ShouldBeStatic)); + } +} +class Foo +{ + public static void M(Baz b) { } +} +class Baz +{ + public Baz(Action a) + { + } +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenUsedWithParenthesisOnMethodGroup() + { + const string source = @" +class Bar +{ + void ShouldBeStatic() + { + } + void Caller() + { + (this.ShouldBeStatic)(); + } +}"; + const string fixtest = @" +class Bar +{ + static void ShouldBeStatic() + { + } + void Caller() + { + ShouldBeStatic(); + } +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWithVariable() + { + const string source = @" +class Foo +{ + static void M() + { + var b = new Bar(); + b.M(); + } +} +class Bar +{ + public void M() + { + } +}"; + const string fixtest = @" +class Foo +{ + static void M() + { + var b = new Bar(); + Bar.M(); + } +} +class Bar +{ + public static void M() + { + } +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWithNew() + { + const string source = @" +class Foo +{ + static void M() + { + new Bar().M(); + } +} +class Bar +{ + public void M() + { + } +}"; + const string fixtest = @" +class Foo +{ + static void M() + { + Bar.M(); + } +} +class Bar +{ + public static void M() + { + } +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixInAHierarchy() + { + const string source = @" +class Foo +{ + public void M() + { + } + class Bar + { + static void N() + { + new Foo().M(); + } + } +}"; + const string fixtest = @" +class Foo +{ + public static void M() + { + } + class Bar + { + static void N() + { + M(); + } + } +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixInAHierarchyWithNameClash() + { + const string source = @" +class Foo +{ + public void M() + { + } + class Bar + { + static void M() + { + new Foo().M(); + } + } +}"; + const string fixtest = @" +class Foo +{ + public static void M() + { + } + class Bar + { + static void M() + { + Foo.M(); + } + } +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWithAGetterMethod() + { + const string source = @" +class Foo +{ + public void M() + { + } +} +class Bar +{ + static void M() + { + GetFoo().M(); + } + static Foo GetFoo() => new Foo(); +}"; + const string fixtest = @" +class Foo +{ + public static void M() + { + } +} +class Bar +{ + static void M() + { + Foo.M(); + } + static Foo GetFoo() => new Foo(); +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task IgnoreInStructs() + { + const string source = @" +struct Foo +{ + private int x; + + public void M(int x) + { + this.x = x; + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task ReportInStructs() + { + const string source = @" +struct Foo +{ + public void M() + { + N(); + } + public static void N() { } +}"; + var expected = new DiagnosticResult(DiagnosticId.MakeMethodStatic.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(4, 17) + .WithMessage(string.Format(MakeMethodStaticAnalyzer.MessageFormat, "M")); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task IgnoreForGetEnumerator() + { + const string source = @" +class Foo +{ + public System.Collections.IEnumerator GetEnumerator() + { + yield return 1; + yield return 2; + } +}"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task ReportForGetEnumeratorNotReturningIEnumerator() + { + const string source = @" +class Foo +{ + public void GetEnumerator() + { + N(); + } + + public static void N() { } +}"; + var expected = new DiagnosticResult(DiagnosticId.MakeMethodStatic.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(4, 17) + .WithMessage(string.Format(MakeMethodStaticAnalyzer.MessageFormat, "GetEnumerator")); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task IgnoreMethodsWithRoutedEventArgs() + { + const string source = @" +public class MainWindow +{ + void Window_Loaded(object sender, System.Windows.RoutedEventArgs e) + { + } +} +namespace System.Windows +{ + public class RoutedEventArgs : System.EventArgs { } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Design/NameOfTests.cs b/test/CSharp/CodeCracker.Test/Design/NameOfTests.cs index 8dd2fc6c9..819252585 100644 --- a/test/CSharp/CodeCracker.Test/Design/NameOfTests.cs +++ b/test/CSharp/CodeCracker.Test/Design/NameOfTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Design; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -118,6 +119,22 @@ void Foo(string a) await VerifyCSharpHasNoDiagnosticsAsync(test); } + [Fact] + public async Task WhenReferencingExternalSymbolShouldReportDiagnostic() + { + const string test = @" +using System; +public class TypeName +{ + public static void Foo() + { + var a = ""DateTime""; + } +}"; + var expected = CreateNameofDiagnosticResult("DateTime", 7, 17, DiagnosticId.NameOf_External); + await VerifyCSharpDiagnosticAsync(test, expected); + } + [Theory] [InlineData("xyz", false)] [InlineData("OtherProperty", true)] @@ -307,7 +324,7 @@ public class OtherTypeName public class TypeName { - + void Foo(string a) { var instance = new OtherTypeName @@ -331,7 +348,7 @@ public class TypeName { private readonly int readonlyField; public interface IInterface {} - + void Foo(string a) { var tab = new[] { ""readonlyField"", ""xyz"", ""IInterface"" }; @@ -352,7 +369,7 @@ public async Task WhenCreatingNewObjectWithStringLiterals() const string source = @" public struct TypeName { - + void Foo(string a) { var instance = new OtherTypeName(""b"", ""xyz""); @@ -395,18 +412,19 @@ public class @class : BaseTypeName public string someName = ""variable""; public string namespaceName = ""using""; public string namespaceName2 = ""N2""; - + void Foo() => string.Format(""{0}"", ""xyz""); void Foo2() => string.Format(""{0}"", ""readonlyField""); public @class() : base(""SomeDelegate"") { } - void Foo3(string a) + void Foo3(string a, BaseTypeName d) { Dictionary dict = new Dictionary { { ""b"", ""readonlyField"" }, - { ""xyz"", ""ParticularEvent"" } + { ""xyz"", ""ParticularEvent"" }, + { ""c"", ""Foo3""}, }; } @@ -436,7 +454,8 @@ public int Property CreateNameofDiagnosticResult("readonlyField", 27, 49), CreateNameofDiagnosticResult("SomeDelegate", 29, 36), CreateNameofDiagnosticResult("readonlyField", 35, 28), - CreateNameofDiagnosticResult("ParticularEvent", 36, 30) + CreateNameofDiagnosticResult("ParticularEvent", 36, 30), + CreateNameofDiagnosticResult("Foo3", 37, 28) }; await VerifyCSharpDiagnosticAsync(source, expected); @@ -685,16 +704,34 @@ private void Invoke(string arg1, string arg2) }"; await VerifyCSharpFixAllAsync(source.Replace("", $@"""{stringLiteral}"""), source.Replace("", $@"nameof({stringLiteral})")); } + [Fact] + public async Task ReplacesUsingMethodName() + { + const string source = @" +public class TypeName +{ + void Foo(TypeName d) + { + var bar = new [] { ""Foo"" }; + } +}"; + + const string fixtest = @" +public class TypeName +{ + void Foo(TypeName d) + { + var bar = new [] { nameof(Foo) }; + } +}"; + await VerifyCSharpFixAsync(source, fixtest); + } - private static DiagnosticResult CreateNameofDiagnosticResult(string nameofArgument, int diagnosticLine, int diagnosticColumn) + private static DiagnosticResult CreateNameofDiagnosticResult(string nameofArgument, int diagnosticLine, int diagnosticColumn, DiagnosticId id = DiagnosticId.NameOf) { - return new DiagnosticResult - { - Id = DiagnosticId.NameOf.ToDiagnosticId(), - Message = $"Use 'nameof({nameofArgument})' instead of specifying the program element name.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", diagnosticLine, diagnosticColumn) } - }; + return new DiagnosticResult(id.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(diagnosticLine, diagnosticColumn) + .WithMessage($"Use 'nameof({nameofArgument})' instead of specifying the program element name."); } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Design/StaticConstructorExceptionTests.cs b/test/CSharp/CodeCracker.Test/Design/StaticConstructorExceptionTests.cs index 5672bd5a9..a8697697b 100644 --- a/test/CSharp/CodeCracker.Test/Design/StaticConstructorExceptionTests.cs +++ b/test/CSharp/CodeCracker.Test/Design/StaticConstructorExceptionTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Design; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -19,13 +20,9 @@ static MyClass() } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.StaticConstructorException.ToDiagnosticId(), - Message = "Don't throw exception inside static constructors.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.StaticConstructorException.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(6, 25) + .WithMessage("Don't throw exceptions inside static constructors."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -149,4 +146,4 @@ static MyClass2() await VerifyCSharpFixAllAsync(new string[] { source1, source2 }, new string[] { fixtest1, fixtest2 }); } } -} \ No newline at end of file +} diff --git a/test/CSharp/CodeCracker.Test/Design/SwicthWithoutDefaultTests.cs b/test/CSharp/CodeCracker.Test/Design/SwicthWithoutDefaultTests.cs new file mode 100644 index 000000000..88d7a4cc8 --- /dev/null +++ b/test/CSharp/CodeCracker.Test/Design/SwicthWithoutDefaultTests.cs @@ -0,0 +1,389 @@ +using CodeCracker.CSharp.Design; +using System.Threading.Tasks; +using Xunit; + +namespace CodeCracker.Test.CSharp.Design +{ + public class SwicthWithoutDefaultTests : CodeFixVerifier + { + [Fact] + public async Task SwithWithoutDefaultAnalyserString() + { + const string source = @"using System;namespace + ConsoleApplication1 + { + class TypeName + { + static void Main() + { +var a = """"; + switch (a)// c1 + {// c2 + case """": + break; + }// c3 + } + } + }"; + + const string fixtest = @"using System;namespace + ConsoleApplication1 + { + class TypeName + { + static void Main() + { +var a = """"; + switch (a)// c1 + {// c2 + case """": + break; + default: + throw new Exception(""Unexpected Case""); + }// c3 + } + } + }"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task SwithWithoutDefaultAnalyserBool() + { + const string source = @"using System;namespace + ConsoleApplication1 + { + class TypeName + { + static void Main() + { + var s = true; + switch (s) + { + case false: + break; + } + } + }"; + + const string fixtest = @"using System;namespace + ConsoleApplication1 + { + class TypeName + { + static void Main() + { + var s = true; + switch (s) + { + case false: + break; + case true: + break; + } + } + }"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task SwitchWithoutDefaultAnalyserInt() + { + const string source = @"using System; + namespace ConsoleApplication1 + { + class TypeName + { + static void Main() + { + var s = 10; + switch (s) + { + case 10: + break; + } + } + } + }"; + + const string fixtest = @"using System; + namespace ConsoleApplication1 + { + class TypeName + { + static void Main() + { + var s = 10; + switch (s) + { + case 10: + break; + default: + throw new Exception(""Unexpected Case""); + } + } + } + }"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task SwitchWithoutDefaultAnalyserBoolMethod() + { + const string source = @"using System;namespace + ConsoleApplication1 + { + class TypeName + { + void Bar(bool myBool) + { + switch (myBool) + { + case false: + break; + } + } + } + }"; + + const string fixtest = @"using System;namespace + ConsoleApplication1 + { + class TypeName + { + void Bar(bool myBool) + { + switch (myBool) + { + case false: + break; + case true: + break; + } + } + } + }"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task SwithWithoutDefaultAnalyserIntMethod() + { + const string source = @"namespace + ConsoleApplication1 + { + class TypeName + { + void Bar3(int a) + { + switch (a) + { + case 10: + break; + } + } + } + }"; + + const string fixtest = @"namespace + ConsoleApplication1 + { + class TypeName + { + void Bar3(int a) + { + switch (a) + { + case 10: + break; + default: + throw new System.Exception(""Unexpected Case""); + } + } +} + }"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task SwitchWithCast() + { + const string source = @" +using System; +class TypeName +{ + void Bar() + { + var t = new TypeName(); + switch ((int)t) + { + case 1: break; + } + } + public static explicit operator int(TypeName v) => 1; +}"; + + const string fixtest = @" +using System; +class TypeName +{ + void Bar() + { + var t = new TypeName(); + switch ((int)t) + { + case 1: break; + default: + throw new Exception(""Unexpected Case""); + } + } + public static explicit operator int(TypeName v) => 1; +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task SwithWithoutDefaultAnalyserIntMethodTwoParams() + { + const string source = @"using System;namespace + ConsoleApplication1 + { + class TypeName + { +void Bar4(int a, int b) + { + switch (a) + { + case 10: + break; + } + } + } + }"; + + const string fixtest = @"using System;namespace + ConsoleApplication1 + { + class TypeName + { +void Bar4(int a, int b) + { + switch (a) + { + case 10: + break; + default: + throw new Exception(""Unexpected Case""); + } + } +} + }"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task SwithWithoutDefaultAnalyserIntMethodStatic() + { + const string source = @"using System;namespace + ConsoleApplication1 + { + class TypeName + { +static void Bar5(int a) + { + switch (a) + { + case 10: + break; + } + } + + } + }"; + + const string fixtest = @"using System;namespace + ConsoleApplication1 + { + class TypeName + { +static void Bar5(int a) + { + switch (a) + { + case 10: + break; + default: + throw new Exception(""Unexpected Case""); + } + } + +} + }"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task SwithWithoutDefaultAnalyseCS0151Exception() + { + const string source = @"static void M() { } + static void Main() + { + switch (M()) // CS0151 + { + default: + break; + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + + [Fact] + public async Task SwithWithoutDefaultAnalyserNoDiagnostic() + { + const string source = @"using System;namespace + ConsoleApplication1 + { + class TypeName + { + static void Main() + { + var s = ""; + switch (s) + { + case "":{break;} + default:{break;} + } + } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task SwitchWithoutDefaultAnalyzerNoDiagnosticWithCompileError() + { + const string source = @"using System; + namespace ConsoleApplication1 + { + ConsoleApplication1 + { + class Teste { } + class Program + { + static void Main(string[] args) + { + Teste vo_teste = new Teste(); + switch (vo_teste) + { + case "":break; + default:break; + } + } + } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + } +} \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Design/UseInvokeMethodToFireEventTests.cs b/test/CSharp/CodeCracker.Test/Design/UseInvokeMethodToFireEventTests.cs index e6dabcc73..90acb1bdd 100644 --- a/test/CSharp/CodeCracker.Test/Design/UseInvokeMethodToFireEventTests.cs +++ b/test/CSharp/CodeCracker.Test/Design/UseInvokeMethodToFireEventTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Design; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using Xunit; namespace CodeCracker.Test.CSharp.Design @@ -20,17 +21,375 @@ public void Execute() } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), - Message = "Use ?.Invoke operator and method to fire 'MyEvent' event.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(8, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); await VerifyCSharpDiagnosticAsync(test, expected); } + [Fact] + public async void WarningIfEventIsReadOnlyFiredDirectlyAndNotInitialized() + { + const string test = @" + public class MyClass + { + public readonly event System.EventHandler MyEvent; + + public void Execute() + { + MyEvent(this, System.EventArgs.Empty); + } + }"; + + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(8, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void WarningIfEventIsReadOnlyAndAssignedInIfInConstructor() + { + const string test = @" + public class MyClass + { + readonly System.EventHandler MyEvent; + + public MyClass(bool shouldAssign) + { + if(shouldAssign) + { + MyEvent = (sender, args) => { }; + } + } + + public void Execute() + { + MyEvent(this, System.EventArgs.Empty); + } + }"; + + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(16, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void WarningIfEventIsReadOnlyAndAssignedInForeachInConstructor() + { + const string test = @" + public class MyClass + { + readonly System.EventHandler MyEvent; + + public MyClass(int[] values) + { + foreach(var value in values) + { + MyEvent = (sender, args) => { }; + } + } + + public void Execute() + { + MyEvent(this, System.EventArgs.Empty); + } + }"; + + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(16, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void WarningIfEventIsReadOnlyAndAssignedInForInConstructor() + { + const string test = @" + public class MyClass + { + readonly System.EventHandler MyEvent; + + public MyClass(int number) + { + for(int i = 0; i < number; i++) + { + MyEvent = (sender, args) => { }; + } + } + + public void Execute() + { + MyEvent(this, System.EventArgs.Empty); + } + }"; + + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(16, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void WarningIfEventIsReadOnlyAndAssignedInWhileInConstructor() + { + const string test = @" + public class MyClass + { + readonly System.EventHandler MyEvent; + + public MyClass(int number) + { + while(number > 0) + { + MyEvent = (sender, args) => { }; + number--; + } + } + + public void Execute() + { + MyEvent(this, System.EventArgs.Empty); + } + }"; + + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(17, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void WarningIfEventIsReadOnlyAndAssignedAfterReturnInConstructor() + { + const string test = @" + public class MyClass + { + readonly System.EventHandler MyEvent; + + public MyClass(bool returnEarly) + { + if(returnEarly) + { + return; + } + MyEvent = (sender, args) => { }; + } + + public void Execute() + { + MyEvent(this, System.EventArgs.Empty); + } + }"; + + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(17, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void WarningIfEventIsReadOnlyAndAssignedToNullRegularAssignmentOnFieldDeclaration() + { + const string test = @" + public class MyClass + { + readonly System.EventHandler MyEvent = null; + + public void Execute() + { + MyEvent(this, System.EventArgs.Empty); + } + }"; + + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(8, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void WarningIfEventIsReadOnlyAndAssignedToNullInConstructor() + { + const string test = @" + public class MyClass + { + readonly System.EventHandler MyEvent; + + public MyClass(bool returnEarly) + { + MyEvent = null; + } + + public void Execute() + { + MyEvent(this, System.EventArgs.Empty); + } + }"; + + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(13, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void WarningIfEventIsReadOnlyAndAssignedToNullAfterRegularAssignmentInConstructor() + { + const string test = @" + public class MyClass + { + readonly System.EventHandler MyEvent; + + public MyClass(bool returnEarly) + { + MyEvent = (sender, args) => { }; + MyEvent = null; + } + + public void Execute() + { + MyEvent(this, System.EventArgs.Empty); + } + }"; + + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(14, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void WarningIfEventIsReadOnlyAndAssignedInAllSwitchCasesButNoDefaultInConstructor() + { + const string test = @" + public class MyClass + { + readonly System.EventHandler MyEvent; + + public MyClass(int value) + { + switch(value) + { + case 1: MyEvent = (sender, args) => { }; break; + case 2: MyEvent = (sender, args) => { }; break; + } + } + + public void Execute() + { + MyEvent(this, System.EventArgs.Empty); + } + }"; + + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(17, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void WarningIfEventIsReadOnlyAndAssignedInASwitchCaseButNotAllInConstructor() + { + const string test = @" + public class MyClass + { + readonly System.EventHandler MyEvent; + + public MyClass(int value) + { + switch(value) + { + case 1: MyEvent = (sender, args) => { }; break; + default: break; + } + } + + public void Execute() + { + MyEvent(this, System.EventArgs.Empty); + } + }"; + + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(17, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void NotWarningIfEventIsReadOnlyAndAssignedInConstructor() + { + const string test = @" + public class MyClass + { + readonly System.EventHandler MyEvent; + + public MyClass() + { + MyEvent = (sender, args) => { }; + } + + public void Execute() + { + MyEvent(this, System.EventArgs.Empty); + } + }"; + + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + + [Fact] + public async void AcceptExpressionBodiedMethods() + { + const string test = @" + public class MyClass + { + public event System.EventHandler MyEvent; + public void Execute() => + MyEvent(this, System.EventArgs.Empty); + }"; + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(6, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void FixExpressionBodiedMethods() + { + const string source = @" + public class MyClass + { + public event System.EventHandler MyEvent; + public void Execute() => + MyEvent(this, System.EventArgs.Empty); + }"; + const string fixtest = @" + public class MyClass + { + public event System.EventHandler MyEvent; + public void Execute() => + MyEvent?.Invoke(this, System.EventArgs.Empty); + }"; + + await VerifyCSharpFixAsync(source, fixtest); + } + [Fact] public async void WarningIfCustomEventIsFiredDirectly() { @@ -46,67 +405,231 @@ public class MyClass public void Execute() { - MyEvent(this, new MyArgs() { Info = ""ping"" }); + MyEvent(this, new MyArgs() { Info = ""ping"" }); + } + }"; + + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(13, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void WarningIfCustomEventWithCustomDelegateIsFiredDirectly() + { + const string test = @" + public class MyArgs : System.EventArgs + { + public string Info { get; set; } + } + + public delegate void Executed (object sender, MyArgs args); + + public class MyClass + { + public event Executed MyEvent; + + public void Execute() + { + MyEvent(this, new MyArgs() { Info = ""ping"" }); + } + }"; + + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(15, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void NotWarningIfEventIsFiredWithInvokeMethod() + { + const string test = @" + public class MyClass + { + public event System.EventHandler MyEvent; + public void Execute() + { + MyEvent?.Invoke(this, System.EventArgs.Empty); + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + [Fact] + public async void NotWarningIfEventIsReadOnlyWithInitializer() + { + const string test = @" + public class MyClass + { + public readonly System.Action MyAction = () => { }; + public void Execute() + { + MyAction(); + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + [Fact] + public async void RaiseDiagnosticEvenWhenVerifiedForNullAndNotReturnedOrThrown() + { + const string test = @" + public class MyClass + { + public static void Execute(System.Action action) + { + if (action == null) + { + var a = 1; + } + action(); + } + }"; + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(10, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "action")); + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void RaiseDiagnosticEvenWhenVerifiedForNullAndNotReturnedOrThrownWithBlocklessIf() + { + const string test = @" + public class MyClass + { + public static void Execute(System.Action action) + { + if (action == null) + System.Console.WriteLine(); + action(); + } + }"; + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(8, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "action")); + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void RaiseDiagnosticIfNullCheckIsAfterInvocation() + { + const string test = @" + public class MyClass + { + public static void Execute(System.Action action) + { + action(); + if (action == null) throw new Exception(); + } + }"; + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(6, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "action")); + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async void IgnoreIfAlreadyVerifiedForNullWithThrow() + { + const string test = @" + public class MyClass + { + public static void Execute(System.Action action) + { + if (action == null) throw new Exception(); + action(); } }"; - - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), - Message = "Use ?.Invoke operator and method to fire 'MyEvent' event.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 13, 25) } - }; - - await VerifyCSharpDiagnosticAsync(test, expected); + await VerifyCSharpHasNoDiagnosticsAsync(test); } [Fact] - public async void WarningIfCustomEventWithCustomDelegateIsFiredDirectly() + public async void IgnoreIfAlreadyVerifiedForNullInverted() { const string test = @" - public class MyArgs : System.EventArgs - { - public string Info { get; set; } - } - - public delegate void Executed (object sender, MyArgs args); - public class MyClass { - public event Executed MyEvent; - - public void Execute() + public static void Execute(System.Action action) { - MyEvent(this, new MyArgs() { Info = ""ping"" }); + if (null == action) throw new Exception(); + action(); } }"; + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + [Fact] + public async void IgnoreIfAlreadyVerifiedForNotNullOnGrandparentIf() + { + var test = @" +public static void Execute(System.Action action) +{ + if (action != null) + { + if (1 > 0) + { + action(); + } + } +}".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + [Fact] + public async void IgnoreIfAlreadyVerifiedForNotNullWithNullOnRight() + { + var test = @" +public static void Execute(System.Action action) +{ + if (action != null) + action(); +}".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(test); + } - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), - Message = "Use ?.Invoke operator and method to fire 'MyEvent' event.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 15, 25) } - }; + [Fact] + public async void IgnoreIfAlreadyVerifiedForNotNullWithNullOnLeft() + { + var test = @" +public static void Execute(System.Action action) +{ + if (null != action) + action(); +}".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + [Fact] + public async void IgnoreIfAlreadyVerifiedForNullCreatesDiagnostic() + { + var test = @" +public static void Execute(System.Action action) +{ + if (null == action) + action(); +}".WrapInCSharpClass(); + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(12, 9) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "action")); await VerifyCSharpDiagnosticAsync(test, expected); } [Fact] - public async void NotWarningIfEventIsFiredWithInvokeMethod() + public async void IgnoreIfAlreadyVerifiedForNullWithReturn() { const string test = @" public class MyClass { - public event System.EventHandler MyEvent; - - public void Execute() + public static void Execute(System.Action action) { - MyEvent?.Invoke(this, System.EventArgs.Empty); + if (action == null) return; + action(); } }"; - await VerifyCSharpHasNoDiagnosticsAsync(test); } @@ -155,7 +678,7 @@ public void Execute() } }"; - await VerifyCSharpFixAsync(source, fixtest, 0); + await VerifyCSharpFixAsync(source, fixtest); } [Fact] @@ -182,8 +705,7 @@ public void Execute() MyEvent?.Invoke(this, System.EventArgs.Empty); //Some Comment } }"; - - await VerifyCSharpFixAsync(source, fixtest, 0); + await VerifyCSharpFixAsync(source, fixtest); } [Fact] @@ -200,29 +722,358 @@ public async void ReportOnParametersWhenReturnTypeIsAReferenceType() var test = @" public static TReturn Method(System.Func getter) where T : System.Attribute where TReturn : class { - if (getter == null) return default(TReturn); return getter(default(T)); }".WrapInCSharpClass(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), - Message = "Use ?.Invoke operator and method to fire 'getter' event.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 12, 12) } - }; + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(11, 12) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "getter")); await VerifyCSharpDiagnosticAsync(test, expected); } [Fact] - public async void WhenMethodInvokedWithNonReferenceTypeHasNoDiagnostic() + public async void WhenMethodInvokedWithNonReferenceTypeHasOnlyIfNullDiagnostic() { var test = @" + public static TReturn Method(System.Func getter) where TReturn : struct + { + return getter?.Invoke(default(T)) ?? default(TReturn); + }".WrapInCSharpClass(); + await VerifyCSharpHasNumberOfCodeActionsAsync(test, 1); + } + + public static TReturn Method(System.Func getter) where TReturn : struct + { + getter?.Invoke(default(T)); + return getter?.Invoke(default(T)) ?? default(TReturn); + } + + [Fact] + public async void FixWithInvokeWithNonReferenceType() + { + var source = @" + public static TReturn Method(System.Func getter) where T : System.Attribute where TReturn : struct + { + return getter(default(T)); + }".WrapInCSharpClass(); + var fix = @" + public static TReturn Method(System.Func getter) where T : System.Attribute where TReturn : struct + { + return getter?.Invoke(default(T)) ?? default(TReturn); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fix, codeFixIndex: 0); + } + + [Fact] + public async void FixWithCheckForNullWithNonReferenceType() + { + var source = @" public static TReturn Method(System.Func getter) where T : System.Attribute where TReturn : struct { return getter(default(T)); }".WrapInCSharpClass(); + var fix = @" + public static TReturn Method(System.Func getter) where T : System.Attribute where TReturn : struct + { + var handler = getter; + if (handler != null) + return handler(default(T)); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fix, codeFixIndex: 1, allowNewCompilerDiagnostics: true); + } + + [Fact] + public async void FixWithCheckForNullAndKeepCommentsWhenReplacedWithCodeFix() + { + const string source = @" + public class MyClass + { + public event System.EventHandler MyEvent; + + public void Execute() + { + //comment + MyEvent(this, System.EventArgs.Empty); //Some Comment + } + }"; + + const string fixtest = @" + public class MyClass + { + public event System.EventHandler MyEvent; + + public void Execute() + { + //comment + var handler = MyEvent; + if (handler != null) + handler(this, System.EventArgs.Empty); //Some Comment + } + }"; + await VerifyCSharpFixAsync(source, fixtest, 1); + } + + [Fact] + public async void FixWhenInvocationIsInsideABlockWithoutBraces() + { + const string source = @" + public class MyClass + { + public event System.EventHandler MyEvent; + bool raiseEvents = true; + + public void Execute() + { + if (raiseEvents) MyEvent(this, System.EventArgs.Empty); + } + }"; + + const string fixtest = @" + public class MyClass + { + public event System.EventHandler MyEvent; + bool raiseEvents = true; + + public void Execute() + { + if (raiseEvents) + { + var handler = MyEvent; + if (handler != null) + handler(this, System.EventArgs.Empty); + } + } + }"; + + await VerifyCSharpFixAsync(source, fixtest, 1); + } + + [Fact] + public async void OnlyOneFixIfExpressionBodied() + { + const string test = @" + public class MyClass + { + public int Foo(int par1, int par2) => FuncCalc(par1,par2); + + public int FuncCalc(int p1, int p2) + { + return p1*p2; + } + }"; + await VerifyCSharpHasNumberOfCodeActionsAsync(test, 1); + } + + [Fact] + public async void FixWhenInsideExpressionWithInvoke() + { + var code = @" +public System.Func AllowInteraction { get; protected set; } +protected bool AllowedInteraction() +{ + if (!AllowInteraction("""") || 1 == System.DateTime.Now.Second) + { + return false; + } + return true; +}".WrapInCSharpClass(); + var fix = @" +public System.Func AllowInteraction { get; protected set; } +protected bool AllowedInteraction() +{ + if (!AllowInteraction?.Invoke("""") ?? default(bool) || 1 == System.DateTime.Now.Second) + { + return false; + } + return true; +}".WrapInCSharpClass(); + await VerifyCSharpFixAsync(code, fix); + } + + [Fact] + public async void FixWhenInsideABinaryExpressionWithPrecedenceWithInvoke() + { + var code = @" +void Foo(Func f) +{ + var b = true; + if (b || f()) + { + } +}".WrapInCSharpClass(); + var fix = @" +void Foo(Func f) +{ + var b = true; + if (b || (f?.Invoke() ?? default(bool))) + { + } +}".WrapInCSharpClass(); + await VerifyCSharpFixAsync(code, fix); + } + + [Fact] + public async void FixWhenInsideExpressionWithCheckForNull() + { + var code = @" +public System.Func AllowInteraction { get; protected set; } +protected bool AllowedInteraction() +{ + if (!AllowInteraction("""") || 1 == System.DateTime.Now.Second) + { + return false; + } + return true; +}".WrapInCSharpClass(); + var fix = @" +public System.Func AllowInteraction { get; protected set; } +protected bool AllowedInteraction() +{ + var handler = AllowInteraction; + if (handler != null) + if (!handler("""") || 1 == System.DateTime.Now.Second) + { + return false; + } + return true; +}".WrapInCSharpClass(); + await VerifyCSharpFixAsync(code, fix, 1); + } + + [Fact] + public async void FixWhenInsideExpressionAndNameAlreadyExists() + { + var code = @" +public System.Func AllowInteraction { get; protected set; } +protected bool AllowedInteraction() +{ + var handler = 1; + if (!AllowInteraction("""") || 1 == System.DateTime.Now.Second) + { + return false; + } + return true; +}".WrapInCSharpClass(); + var fix = @" +public System.Func AllowInteraction { get; protected set; } +protected bool AllowedInteraction() +{ + var handler = 1; + var handler1 = AllowInteraction; + if (handler1 != null) + if (!handler1("""") || 1 == System.DateTime.Now.Second) + { + return false; + } + return true; +}".WrapInCSharpClass(); + await VerifyCSharpFixAsync(code, fix, 1); + } + + [Fact] + public async void WhenEventIsFiredDirectlyShouldCopyItToVariable() + { + const string source = @" + public class MyClass + { + public event System.EventHandler MyEvent; + + public void Execute() + { + MyEvent(this, System.EventArgs.Empty); + } + }"; + + const string fixtest = @" + public class MyClass + { + public event System.EventHandler MyEvent; + + public void Execute() + { + var handler = MyEvent; + if (handler != null) + handler(this, System.EventArgs.Empty); + } + }"; + + await VerifyCSharpFixAsync(source, fixtest, 1); + } + + [Fact] + public async void NotWarningIfEventIsCopiedToLocalVariableBeforeFire() + { + const string test = @" + public class MyClass + { + public event System.EventHandler MyEvent; + + public void Execute() + { + var handler = MyEvent; + if (handler != null) + handler(this, System.EventArgs.Empty); + } + }"; await VerifyCSharpHasNoDiagnosticsAsync(test); } + + [Fact] + public async void IgnoreIfAlreadyCheckedForNull() + { + const string test = @" +public static int Get(Func method) { + return method != null ? method(12345) : 0; +}"; + + await VerifyCSharpHasNoDiagnosticsAsync(test.WrapInCSharpClass()); + } + + [Fact] + public async void IgnoreIfInLogicalOrThatCheckedForNull() + { + const string test = @" +public class Foo +{ + private System.Func _filter; + public void Bar() + { + var b = _filter == null || _filter(); + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(test.WrapInCSharpClass()); + } + + [Fact] + public async void IgnoreIfInLogicalAndThatCheckedForNotNull() + { + const string test = @" +public class Foo +{ + private System.Func _filter; + public void Bar() + { + var b = _filter != null && _filter(); + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(test.WrapInCSharpClass()); + } + + [Fact] + public async void IgnoreIfInConstructorAndThatCheckedForNotNull() + { + //https://github.com/code-cracker/code-cracker/issues/926 + const string test = @" +public class Foo +{ + public Foo(System.Action action) + { + if (action == null) + throw new System.ArgumentNullException(); + action(); + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(test.WrapInCSharpClass()); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/GeneratedCodeAnalysisExtensionsTests.cs b/test/CSharp/CodeCracker.Test/GeneratedCodeAnalysisExtensionsTests.cs index 7103a6ab6..67479b659 100644 --- a/test/CSharp/CodeCracker.Test/GeneratedCodeAnalysisExtensionsTests.cs +++ b/test/CSharp/CodeCracker.Test/GeneratedCodeAnalysisExtensionsTests.cs @@ -287,9 +287,8 @@ public static void SyntaxNodeAnalysis_HasGeneratedCodeAttributeOnNestedClass(str public static void SymbolicAnalysis_HasGeneratedCodeAttributeOnNestedClass(string source) => GetSymbolAnalysisContext(source).IsGenerated().Should().BeTrue(); - [Fact] - public static void SyntaxNodeAnalysis_WithAutoGeneratedCommentBasedOnWebForms() => - GetSyntaxNodeAnalysisContext( + [Theory] + [InlineData( @"//------------------------------------------------------------------------------ // // This code was generated by a tool. @@ -304,11 +303,8 @@ namespace WebApplication3 public partial class _Default { } -}").IsGenerated().Should().BeTrue(); - - [Fact] - public static void SyntaxTreeAnalysis_WithAutoGeneratedCommentBasedOnWebForms() => - GetSyntaxTreeAnalysisContext( +}", false)] + [InlineData( @"//------------------------------------------------------------------------------ // // This code was generated by a tool. @@ -316,57 +312,46 @@ public static void SyntaxTreeAnalysis_WithAutoGeneratedCommentBasedOnWebForms() // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // -//------------------------------------------------------------------------------ - +//------------------------------------------------------------------------------", true)] + [InlineData( +@"// +// This code was generated by a tool. namespace WebApplication3 { public partial class _Default { } -}").IsGenerated().Should().BeTrue(); - - [Fact] - public static void SyntaxTreeAnalysis_WithAutoGeneratedCommentEmpty() => - GetSyntaxTreeAnalysisContext( -@"//------------------------------------------------------------------------------ -// -// This code was generated by a tool. +}", false)] + [InlineData( +@"// +// This code was generated by a tool.", true)] + [InlineData( +@"// This code was generated by a tool. // -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------").IsGenerated().Should().BeTrue(); - - [Fact] - public static void SymbolicAnalysis_WithAutoGeneratedCommentBasedOnWebForms() => - GetSymbolAnalysisContext( -@"//------------------------------------------------------------------------------ // -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - namespace WebApplication3 { public partial class _Default { } -}").IsGenerated().Should().BeTrue(); - - [Fact] - public static void SyntaxNodeAnalysis_WithAutoGeneratedCommentEmpty() => - GetSyntaxNodeAnalysisContext( -@"//------------------------------------------------------------------------------ -// -// This code was generated by a tool. +}", false)] + [InlineData( +@"// This code was generated by a tool. // -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------").IsGenerated().Should().BeTrue(); +// ", true)] + public static void SyntaxNodeAnalysis_WithAutoGeneratedComment(string source, bool isEmpty) + { + GetSyntaxTreeAnalysisContext(source).IsGenerated().Should().BeTrue(); + if (isEmpty) + { + GetSyntaxNodeAnalysisContext(source).IsGenerated().Should().BeTrue(); + } + else + { + GetSyntaxNodeAnalysisContext(source).IsGenerated().Should().BeTrue(); + GetSymbolAnalysisContext(source).IsGenerated().Should().BeTrue(); + } + } private static SyntaxNodeAnalysisContext GetSyntaxNodeAnalysisContext(string code, string fileName = baseProjectPath + "a.cs") => GetSyntaxNodeAnalysisContext(code, fileName); diff --git a/test/CSharp/CodeCracker.Test/Helpers/IQueriableExtensions.cs b/test/CSharp/CodeCracker.Test/Helpers/IQueriableExtensions.cs new file mode 100644 index 000000000..c37c9ebf4 --- /dev/null +++ b/test/CSharp/CodeCracker.Test/Helpers/IQueriableExtensions.cs @@ -0,0 +1,43 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace CodeCracker.Test.CSharp.Helpers +{ + public static class IQueriableExtensions + { +#pragma warning disable CC0057 // Unused parameters + public static Task FirstAsync( + this IQueryable source, + CancellationToken cancellationToken = default(CancellationToken)) => null; + + public static Task FirstOrDefaultAsync( + this IQueryable source, + CancellationToken cancellationToken = default(CancellationToken)) => null; + + public static Task LastAsync( + this IQueryable source, + CancellationToken cancellationToken = default(CancellationToken)) => null; + + public static Task LastOrDefaultAsync( + this IQueryable source, + CancellationToken cancellationToken = default(CancellationToken)) => null; + + public static Task AnyAsync(this IQueryable source, + CancellationToken cancellationToken = default(CancellationToken)) => Task.FromResult(false); + + public static Task SingleAsync( + this IQueryable source, + CancellationToken cancellationToken = default(CancellationToken)) => null; + + public static Task SingleOrDefaultAsync( + this IQueryable source, + CancellationToken cancellationToken = default(CancellationToken)) => null; + + public static Task CountAsync( + this IQueryable source, + CancellationToken cancellationToken = default(CancellationToken)) => Task.FromResult(0); + +#pragma warning restore CC0057 // Unused parameters + } +} \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Maintainability/XmlDocumentationTests.cs b/test/CSharp/CodeCracker.Test/Maintainability/XmlDocumentationTests.cs index e7e28362f..db37a35fa 100644 --- a/test/CSharp/CodeCracker.Test/Maintainability/XmlDocumentationTests.cs +++ b/test/CSharp/CodeCracker.Test/Maintainability/XmlDocumentationTests.cs @@ -1,11 +1,12 @@ using CodeCracker.CSharp.Maintainability; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; namespace CodeCracker.Test.CSharp.Maintainability { - public class XmlDocumentationAnalyzerTests : CodeFixVerifier + public class XmlDocumentationAnalyzerTests : CodeFixVerifier { [Fact] public async Task IgnoresClassDocs() @@ -26,7 +27,7 @@ class TypeName [Fact] public async Task XmlDocumentationInsideMethodWithAttributeDoesNotCreateDiagnostic() { -var source = @" + var source = @" /// /// /// @@ -40,10 +41,42 @@ public int Foo(int value) await VerifyCSharpHasNoDiagnosticsAsync(source); } + [Fact] + public async Task XmlDocumentationWithInheritDocNotCreateDiagnostic() + { + var source = @" +interface IBar +{ + /// + /// Lololol + /// + /// + /// + void foo(int a, int b); +} + +class Bar : IBar +{ + /// + public void foo(int a, int b) { } +} + +class Bar2 : IBar +{ + /// + /// + /// You can still specify all the normal XML tags here, and they will + /// overwrite inherited ones accordingly. + /// + public void foo(int a, int b) { } +}".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + [Fact] public async Task XmlDocumentationInsideMethodDoesNotCreateDiagnostic() { -var source = @" + var source = @" /// /// /// @@ -78,13 +111,9 @@ protected async static Task GetSortedDiagnosticsFromDocumentsAsync } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.XmlDocumentation.ToDiagnosticId(), - Message = "You have missing/unexistent parameters in Xml Docs", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 16) } - }; + var expected = new DiagnosticResult(DiagnosticId.XmlDocumentation_MissingInCSharp.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 16) + .WithMessage("You have missing/unexistent parameters in Xml Docs"); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -109,13 +138,9 @@ public static Project CreateProject(string[] sources, out AdhocWorkspace workspa } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.XmlDocumentation.ToDiagnosticId(), - Message = "You have missing/unexistent parameters in Xml Docs", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 16) } - }; + var expected = new DiagnosticResult(DiagnosticId.XmlDocumentation_MissingInXml.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(6, 16) + .WithMessage("You have missing/unexistent parameters in Xml Docs"); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -160,7 +185,7 @@ public int Foo(int value) } } - public class XmlDocumentationRemoveUnexistentParametersCodeFixTests : CodeFixVerifier + public class XmlDocumentationRemoveUnexistentParametersCodeFixTests : CodeFixVerifier { [Fact] public async Task FixRemoveParameterDoc() @@ -246,7 +271,7 @@ protected async static Task GetSortedDiagnosticsFromDocumentsAsync } - public class XmlDocumentationCreateMissingParametersCodeFixTests : CodeFixVerifier + public class XmlDocumentationCreateMissingParametersCodeFixTests : CodeFixVerifier { [Fact] public async Task FixCreateOneParameterDoc() @@ -328,7 +353,7 @@ public static Project CreateProject(string[] sources, out AdhocWorkspace workspa await VerifyCSharpFixAsync(source, expected); } - + [Fact] public async Task FixCreateManyParameterDocWhenHaveFullDocSyntax() { diff --git a/test/CSharp/CodeCracker.Test/Performance/EmptyFinalizerTests.cs b/test/CSharp/CodeCracker.Test/Performance/EmptyFinalizerTests.cs index f86dde304..aef768752 100644 --- a/test/CSharp/CodeCracker.Test/Performance/EmptyFinalizerTests.cs +++ b/test/CSharp/CodeCracker.Test/Performance/EmptyFinalizerTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Performance; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -19,13 +20,9 @@ public class MyClass } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.EmptyFinalizer.ToDiagnosticId(), - Message = "Remove Empty Finalizers", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.EmptyFinalizer.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(4, 21) + .WithMessage("Remove Empty Finalizers"); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -42,13 +39,9 @@ public class MyClass } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.EmptyFinalizer.ToDiagnosticId(), - Message = "Remove Empty Finalizers", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.EmptyFinalizer.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(4, 21) + .WithMessage("Remove Empty Finalizers"); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -68,13 +61,9 @@ public class MyClass } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.EmptyFinalizer.ToDiagnosticId(), - Message = "Remove Empty Finalizers", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.EmptyFinalizer.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(4, 21) + .WithMessage("Remove Empty Finalizers"); await VerifyCSharpDiagnosticAsync(test, expected); } diff --git a/test/CSharp/CodeCracker.Test/Performance/MakeLocalVariablesConstWhenItIsPossibleTests.cs b/test/CSharp/CodeCracker.Test/Performance/MakeLocalVariablesConstWhenItIsPossibleTests.cs index 1fc75b5c0..5ab66e579 100644 --- a/test/CSharp/CodeCracker.Test/Performance/MakeLocalVariablesConstWhenItIsPossibleTests.cs +++ b/test/CSharp/CodeCracker.Test/Performance/MakeLocalVariablesConstWhenItIsPossibleTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Performance; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -52,17 +53,21 @@ public async Task IgnoresVariablesThatChangesValueOutsideDeclaration() await VerifyCSharpHasNoDiagnosticsAsync(test); } + [Fact] + public async Task IgnoresPointerDeclarations() + { + var test = @"void* value = null;".WrapInCSharpMethod(); + + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + [Fact] public async Task CreateDiagnosticsWhenAssigningAPotentialConstant() { var test = @"int a = 10;".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.MakeLocalVariableConstWhenItIsPossible.ToDiagnosticId(), - Message = "This variables can be made const.", - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.MakeLocalVariableConstWhenItIsPossible.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 13) + .WithMessage("This variable can be made const."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -71,13 +76,9 @@ public async Task CreateDiagnosticsWhenAssigningAPotentialConstantInAVarDeclarat { var test = @"var a = 10;".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.MakeLocalVariableConstWhenItIsPossible.ToDiagnosticId(), - Message = "This variables can be made const.", - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.MakeLocalVariableConstWhenItIsPossible.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 13) + .WithMessage("This variable can be made const."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -86,13 +87,9 @@ public async Task CreateDiagnosticsWhenAssigningNullToAReferenceType() { var test = @"Foo a = null;".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.MakeLocalVariableConstWhenItIsPossible.ToDiagnosticId(), - Message = "This variables can be made const.", - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.MakeLocalVariableConstWhenItIsPossible.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 13) + .WithMessage("This variable can be made const."); await VerifyCSharpDiagnosticAsync(test, expected); } diff --git a/test/CSharp/CodeCracker.Test/Performance/RemoveWhereWhenItIsPossibleTests.cs b/test/CSharp/CodeCracker.Test/Performance/RemoveWhereWhenItIsPossibleTests.cs index dc64b6c7b..445fd62b0 100644 --- a/test/CSharp/CodeCracker.Test/Performance/RemoveWhereWhenItIsPossibleTests.cs +++ b/test/CSharp/CodeCracker.Test/Performance/RemoveWhereWhenItIsPossibleTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Performance; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -32,13 +33,9 @@ public async Task DoSomething() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RemoveWhereWhenItIsPossible.ToDiagnosticId(), - Message = "You can remove 'Where' moving the predicate to '" + method + "'.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 23) } - }; + var expected = new DiagnosticResult(DiagnosticId.RemoveWhereWhenItIsPossible.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(11, 23) + .WithMessage("You can remove 'Where' moving the predicate to '" + method + "'."); await VerifyCSharpDiagnosticAsync(test, expected); @@ -69,11 +66,19 @@ public async Task DoSomething() } } }"; - await VerifyCSharpHasNoDiagnosticsAsync(test); - } + [Fact] + public async Task DoNotCreateDiagnosticWhenWhereUsesIndexer() + { + var test = @" +var first = Enumerable.Range(1, 10).ToList(); +var second = Enumerable.Range(1, 10); +var isNotMatch = second.Where((t, i) => first[i] != t).Any(); +".WrapInCSharpMethod(usings: "using System.Linq;"); + await VerifyCSharpHasNoDiagnosticsAsync(test); + } [Theory] [InlineData("First")] @@ -96,11 +101,10 @@ public class Foo public async Task DoSomething() { var a = new int[10]; - var f = a.Where(item => item > 10)." + method + @"(); + var f = a.Where((item) => item > 10)." + method + @"(); } } }"; - var expected = @" using System.Linq; @@ -111,13 +115,11 @@ public class Foo public async Task DoSomething() { var a = new int[10]; - var f = a." + method + @"(item => item > 10); + var f = a." + method + @"((item) => item > 10); } } }"; - await VerifyCSharpFixAsync(test, expected); - } [Theory] @@ -149,6 +151,128 @@ public async Task DoSomething() var expected = @" using System.Linq; +namespace Sample +{ + public class Foo + { + public async Task DoSomething() + { + var a = new int[10]; + var f = a.OrderBy(item => item)." + method + @"(item => item > 10); + } + } +}"; + + await VerifyCSharpFixAsync(test, expected); + + } + + [Theory] + [InlineData("FirstAsync")] + [InlineData("FirstOrDefaultAsync")] + [InlineData("LastAsync")] + [InlineData("LastOrDefaultAsync")] + [InlineData("AnyAsync")] + [InlineData("SingleAsync")] + [InlineData("SingleOrDefaultAsync")] + [InlineData("CountAsync")] + public async Task DoNotCreateDiagnosticWhenUsingWhereAndAnotherMethodWithPredicatesAsync(string method) + { + var test = @" +using System.Linq; + +namespace Sample +{ + public class Foo + { + public async Task DoSomething() + { + var a = new int[10]; + var f = a.Where(item => item > 10)." + method + @"(item => item < 50); + } + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + [Fact] + public async Task DoNotCreateDiagnosticWhenWhereUsesIndexerAsync() + { + var test = @" +var first = Enumerable.Range(1, 10).ToList(); +var second = Enumerable.Range(1, 10); +var isNotMatch = second.Where((t, i) => first[i] != t).Any(); +".WrapInCSharpMethod(usings: "using System.Linq;"); + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + [Theory] + [InlineData("FirstAsync")] + [InlineData("FirstOrDefaultAsync")] + [InlineData("LastAsync")] + [InlineData("LastOrDefaultAsync")] + [InlineData("AnyAsync")] + [InlineData("SingleAsync")] + [InlineData("SingleOrDefaultAsync")] + [InlineData("CountAsync")] + public async Task FixRemovesWhereMovingPredicateToAsync(string method) + { + var test = @" +namespace Sample +{ + public class Foo + { + public async Task DoSomething() + { + var a = new int[10]; + var f = a.Where((item) => item > 10)." + method + @"(); + } + } +}"; + var expected = @" +namespace Sample +{ + public class Foo + { + public async Task DoSomething() + { + var a = new int[10]; + var f = a." + method + @"((item) => item > 10); + } + } +}"; + await VerifyCSharpFixAsync(test, expected); + } + + [Theory] + [InlineData("FirstAsync")] + [InlineData("FirstOrDefaultAsync")] + [InlineData("LastAsync")] + [InlineData("LastOrDefaultAsync")] + [InlineData("AnyAsync")] + [InlineData("SingleAsync")] + [InlineData("SingleOrDefaultAsync")] + [InlineData("CountAsync")] + public async Task FixRemovesWherePreservingPreviousExpressionsMovingPredicateToAsync(string method) + { + var test = @" +using System.Linq; + +namespace Sample +{ + public class Foo + { + public async Task DoSomething() + { + var a = new int[10]; + var f = a.OrderBy(item => item).Where(item => item > 10)." + method + @"(); + } + } +}"; + + var expected = @" +using System.Linq; + namespace Sample { public class Foo diff --git a/test/CSharp/CodeCracker.Test/Performance/SealedAttributeTests.cs b/test/CSharp/CodeCracker.Test/Performance/SealedAttributeTests.cs index e14f5e926..ce0fe3dfa 100644 --- a/test/CSharp/CodeCracker.Test/Performance/SealedAttributeTests.cs +++ b/test/CSharp/CodeCracker.Test/Performance/SealedAttributeTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Performance; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -16,13 +17,9 @@ public class MyAttribute : System.Attribute }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.SealedAttribute.ToDiagnosticId(), - Message = "Mark 'MyAttribute' as sealed.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 2, 30) } - }; + var expected = new DiagnosticResult(DiagnosticId.SealedAttribute.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(2, 30) + .WithMessage("Mark 'MyAttribute' as sealed."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -41,13 +38,9 @@ public class OtherAttribute : MyAttribute }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.SealedAttribute.ToDiagnosticId(), - Message = "Mark 'OtherAttribute' as sealed.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 30) } - }; + var expected = new DiagnosticResult(DiagnosticId.SealedAttribute.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(7, 30) + .WithMessage("Mark 'OtherAttribute' as sealed."); await VerifyCSharpDiagnosticAsync(test, expected); } diff --git a/test/CSharp/CodeCracker.Test/Performance/StringBuilderInLoopTests.cs b/test/CSharp/CodeCracker.Test/Performance/StringBuilderInLoopTests.cs index 8d467d1b5..75f557017 100644 --- a/test/CSharp/CodeCracker.Test/Performance/StringBuilderInLoopTests.cs +++ b/test/CSharp/CodeCracker.Test/Performance/StringBuilderInLoopTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -29,7 +30,6 @@ public void Method() { } await VerifyCSharpHasNoDiagnosticsAsync(source); } - [Fact] public async Task WhileWithoutStringConcatDoesNotCreateDiagnostic() { @@ -51,13 +51,9 @@ public async Task WhileWithStringConcatOnLocalVariableCreatesDiagnostic() { myString += """"; }".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), - Message = string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 14, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(14, 21) + .WithMessage(string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -79,13 +75,9 @@ public void Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), - Message = string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(11, 21) + .WithMessage(string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -108,13 +100,9 @@ public void Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), - Message = string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "MyString"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(11, 21) + .WithMessage(string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "MyString")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -129,20 +117,12 @@ public async Task WhileWithStringConcatWithSeveralConcatsOnDifferentVarsCreatesS myString1 += """"; myString2 += """"; }".WrapInCSharpMethod(); - var expected1 = new DiagnosticResult - { - Id = DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), - Message = string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString1"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 15, 21) } - }; - var expected2 = new DiagnosticResult - { - Id = DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), - Message = string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString2"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 16, 21) } - }; + var expected1 = new DiagnosticResult(DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(15, 21) + .WithMessage(string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString1")); + var expected2 = new DiagnosticResult(DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(16, 21) + .WithMessage(string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString2")); await VerifyCSharpDiagnosticAsync(source, expected1, expected2); } @@ -155,13 +135,9 @@ public async Task WhileWithStringConcatWithSimpleAssignmentCreatesDiagnostic() { myString = myString + """"; }".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), - Message = string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 14, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(14, 21) + .WithMessage(string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -220,6 +196,7 @@ public async Task FixesAddAssignmentInWhileWithoutBlock() public async Task FixesAddAssignmentInWhileWithSystemTextInContext() { const string source = @" + using System; using System.Text; namespace ConsoleApplication1 { @@ -236,6 +213,7 @@ public void Foo() } }"; const string fixtest = @" + using System; using System.Text; namespace ConsoleApplication1 { @@ -386,20 +364,16 @@ public async Task ForWithStringConcatOnLocalVariableCreatesDiagnostic() { myString += """"; }".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), - Message = string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 14, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(14, 21) + .WithMessage(string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString")); await VerifyCSharpDiagnosticAsync(source, expected); } [Fact] public async Task ForWithStringConcatOnLoopLocalVariableShouldNotCreateDiagnostic() { - var source = @" + var source = @" for (;;) { var myString = """"; @@ -442,13 +416,9 @@ public async Task ForeachWithtStringConcatOnLocalVariableCreatesDiagnostic() { myString += """"; }".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), - Message = string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 14, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(14, 21) + .WithMessage(string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -482,13 +452,9 @@ public async Task DoWithtStringConcatOnLocalVariableCreatesDiagnostic() { myString += """"; } while (DateTime.Now.Second % 2 == 0);".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), - Message = string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 14, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(14, 21) + .WithMessage(string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -536,13 +502,9 @@ public void Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), - Message = string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "someObject.A"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 16, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(16, 21) + .WithMessage(string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "someObject.A")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -569,13 +531,9 @@ public void Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), - Message = string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "someObject.A[DateTime.Now.Second]"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 16, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(16, 21) + .WithMessage(string.Format(StringBuilderInLoopAnalyzer.MessageFormat, "someObject.A[DateTime.Now.Second]")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -629,5 +587,28 @@ public void Looper(ref int a) }".WrapInCSharpClass(); await VerifyCSharpHasNoDiagnosticsAsync(source); } + + [Fact] + public async Task IgnoreWhenVariableInTheLoopContextIsChanged() + { + const string source = @" +class MyObject +{ + public string MyObjectString; +} +class MyClass +{ + private readonly System.Collections.Generic.List items = new System.Collections.Generic.List(); + private void M(string suffix) + { + foreach (MyObject o in items) + { + o.MyObjectString += suffix; + } + } +} + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Performance/UseStaticRegexIsMatchTests.cs b/test/CSharp/CodeCracker.Test/Performance/UseStaticRegexIsMatchTests.cs index 2c6f5bbda..5d4d8f76c 100644 --- a/test/CSharp/CodeCracker.Test/Performance/UseStaticRegexIsMatchTests.cs +++ b/test/CSharp/CodeCracker.Test/Performance/UseStaticRegexIsMatchTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Performance; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -26,13 +27,9 @@ public async Task Foo() [Fact] public async Task CreatesDiagnosticsWhenDeclaringALocalRegexAndUsingIsMatch() { - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseStaticRegexIsMatch.ToDiagnosticId(), - Message = UseStaticRegexIsMatchAnalyzer.MessageFormat, - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 12, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.UseStaticRegexIsMatch.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(12, 17) + .WithMessage(UseStaticRegexIsMatchAnalyzer.MessageFormat); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -60,7 +57,6 @@ public async Task Foo() [Fact] public async Task WhenMakeMethodCallStatic() { - const string fixtest = @" using System; using System.Text.RegularExpressions; @@ -75,7 +71,7 @@ public async Task Foo() } } }"; - await VerifyCSharpFixAsync(test, fixtest, 0); + await VerifyCSharpFixAsync(test, fixtest, 0, allowNewCompilerDiagnostics: true); //todo: should not need to allow new compiler diagnostic, fix test infrastructure to understand the Regex type } [Fact] @@ -97,7 +93,7 @@ public async Task Foo() } } }"; - await VerifyCSharpFixAsync(test, fixtest, 2); + await VerifyCSharpFixAsync(test, fixtest, 2, allowNewCompilerDiagnostics: true); //todo: should not need to allow new compiler diagnostic, fix test infrastructure to understand the Regex type } [Fact] @@ -118,7 +114,58 @@ public async Task Foo() } } }"; - await VerifyCSharpFixAsync(test, fixtest, 1); + await VerifyCSharpFixAsync(test, fixtest, 1, allowNewCompilerDiagnostics: true); //todo: should not need to allow new compiler diagnostic, fix test infrastructure to understand the Regex type + } + + [Fact] + public async Task IgnoresIsMatchCallClassMember() + { + const string testStatic = @" + public class RegexTestClass + { + private TestModel testModel; + + private void Test(string text) + { + if (testModel.Regex.IsMatch(text)) + { + return; + } + } + } + + public class TestModel + { + public Regex Regex { get; set; } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(testStatic); + } + + [Fact] + public async Task IgnoresIsMatchCallClassMemberInsideClass() + { + const string testStatic = @" + public class RegexTestClass + { + private C c; + + private void Test(string text) + { + if (c.TestModel.Regex.IsMatch(text)) + { + return; + } + } + } + public class C + { + public TestModel TestModel { get; set; } + } + public class TestModel + { + public System.Text.RegularExpressions.Regex Regex { get; set; } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(testStatic); } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Properties/AssemblyInfo.cs b/test/CSharp/CodeCracker.Test/Properties/AssemblyInfo.cs index 422edebdf..355d9badb 100644 --- a/test/CSharp/CodeCracker.Test/Properties/AssemblyInfo.cs +++ b/test/CSharp/CodeCracker.Test/Properties/AssemblyInfo.cs @@ -8,10 +8,10 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CodeCracker")] -[assembly: AssemblyCopyright("Copyright © 2014-2015")] +[assembly: AssemblyCopyright("Copyright © 2014-2018")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: NeutralResourcesLanguage("en")] [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("1.0.0.10")] +[assembly: AssemblyFileVersion("1.1.0.0")] diff --git a/test/CSharp/CodeCracker.Test/Refactoring/AddBracesToSwitchSectionsTests.cs b/test/CSharp/CodeCracker.Test/Refactoring/AddBracesToSwitchSectionsTests.cs index eab5fc7bb..086e7287f 100644 --- a/test/CSharp/CodeCracker.Test/Refactoring/AddBracesToSwitchSectionsTests.cs +++ b/test/CSharp/CodeCracker.Test/Refactoring/AddBracesToSwitchSectionsTests.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using CodeCracker.CSharp.Refactoring; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using Xunit; namespace CodeCracker.Test.CSharp.Refactoring @@ -69,13 +70,9 @@ public async Task CreateDiagnosticWhenSingleSwitchSectionHasNoBraces() Foo(); break; }"; - var diagnostic = new DiagnosticResult - { - Id = DiagnosticId.AddBracesToSwitchSections.ToDiagnosticId(), - Message = "Add braces for each section in this switch", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] {new DiagnosticResultLocation("Test0.cs", 10, 17)} - }; + var diagnostic = new DiagnosticResult(DiagnosticId.AddBracesToSwitchSections.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(10, 13) + .WithMessage("Add braces for each section in this switch"); await VerifyCSharpDiagnosticAsync(test.WrapInCSharpMethod(), diagnostic); } @@ -98,13 +95,9 @@ public async Task CreateDiagnosticWhenNotAllSwitchSectionsHaveBraces() break; } }"; - var diagnostic = new DiagnosticResult - { - Id = DiagnosticId.AddBracesToSwitchSections.ToDiagnosticId(), - Message = "Add braces for each section in this switch", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + var diagnostic = new DiagnosticResult(DiagnosticId.AddBracesToSwitchSections.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(10, 13) + .WithMessage("Add braces for each section in this switch"); await VerifyCSharpDiagnosticAsync(test.WrapInCSharpMethod(), diagnostic); } @@ -127,13 +120,9 @@ public async Task CreateDiagnosticWhenDefaultSectionsHasNoBraces() Baz(); break; }"; - var diagnostic = new DiagnosticResult - { - Id = DiagnosticId.AddBracesToSwitchSections.ToDiagnosticId(), - Message = "Add braces for each section in this switch", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + var diagnostic = new DiagnosticResult(DiagnosticId.AddBracesToSwitchSections.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(10, 13) + .WithMessage("Add braces for each section in this switch"); await VerifyCSharpDiagnosticAsync(test.WrapInCSharpMethod(), diagnostic); } diff --git a/test/CSharp/CodeCracker.Test/Refactoring/AllowMembersOrderingAnalyzerTests.cs b/test/CSharp/CodeCracker.Test/Refactoring/AllowMembersOrderingAnalyzerTests.cs index 07c58f2cc..bbede299f 100644 --- a/test/CSharp/CodeCracker.Test/Refactoring/AllowMembersOrderingAnalyzerTests.cs +++ b/test/CSharp/CodeCracker.Test/Refactoring/AllowMembersOrderingAnalyzerTests.cs @@ -1,6 +1,7 @@ using CodeCracker.CSharp.Refactoring; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; using Xunit; namespace CodeCracker.Test.CSharp.Refactoring { @@ -14,7 +15,7 @@ protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() [Theory] [InlineData("class")] [InlineData("struct")] - public async void AllowMembersOrderingForEmptyTypeShouldNotTiggerDiagnostic(string typeDeclaration) + public async void AllowMembersOrderingForEmptyTypeShouldNotTriggerDiagnostic(string typeDeclaration) { var test = @" " + typeDeclaration + @" Foo @@ -27,7 +28,7 @@ public async void AllowMembersOrderingForEmptyTypeShouldNotTiggerDiagnostic(stri [Theory] [InlineData("class")] [InlineData("struct")] - public async void AllowMembersOrderingForOneMemberShouldNotTiggerDiagnostic(string typeDeclaration) + public async void AllowMembersOrderingForOneMemberShouldNotTriggerDiagnostic(string typeDeclaration) { var test = @" " + typeDeclaration + @" Foo @@ -41,7 +42,7 @@ public async void AllowMembersOrderingForOneMemberShouldNotTiggerDiagnostic(stri [Theory] [InlineData("class")] [InlineData("struct")] - public async void AllowMembersOrderingForMoreThanOneMemberShouldTiggerDiagnostic(string typeDeclaration) + public async void AllowMembersOrderingForMoreThanOneMemberShouldTriggerDiagnostic(string typeDeclaration) { var test = @" " + typeDeclaration + @" Foo @@ -50,13 +51,9 @@ public async void AllowMembersOrderingForMoreThanOneMemberShouldTiggerDiagnostic void car() { } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.AllowMembersOrdering.ToDiagnosticId(), - Message = AllowMembersOrderingAnalyzer.MessageFormat, - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 2, 14 + typeDeclaration.Length) } - }; + var expected = new DiagnosticResult(DiagnosticId.AllowMembersOrdering.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(2, 14 + typeDeclaration.Length) + .WithMessage(AllowMembersOrderingAnalyzer.MessageFormat); await VerifyCSharpDiagnosticAsync(test, expected); } diff --git a/test/CSharp/CodeCracker.Test/Refactoring/ChangeAnyToAllTests.cs b/test/CSharp/CodeCracker.Test/Refactoring/ChangeAnyToAllTests.cs index a77e81b83..86ca15d52 100644 --- a/test/CSharp/CodeCracker.Test/Refactoring/ChangeAnyToAllTests.cs +++ b/test/CSharp/CodeCracker.Test/Refactoring/ChangeAnyToAllTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Refactoring; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -29,13 +30,9 @@ public class ChangeAnyToAllTests : CodeFixVerifier await VerifyCSharpFixAsync(original.WrapInCSharpMethod(usings: "\nusing System.Linq;"), fix.WrapInCSharpMethod(usings: "\nusing System.Linq;")); + + [Theory] + [InlineData("Any", DiagnosticId.ChangeAnyToAll)] + [InlineData("All", DiagnosticId.ChangeAllToAny)] + public async Task WithElvisOperatorCreatesDiagnostic(string methodName, DiagnosticId diagnosticId) + { + var source = $@" +using System; +using System.Linq; +class TypeSymbol +{{ + public System.Collections.Generic.IList AllInterfaces; +}} +class TypeName +{{ + void Foo() + {{ + var typeSymbol = new TypeSymbol(); + var y = typeSymbol?.AllInterfaces.{methodName}(i => i == 1); + }} +}}"; + var expected = new DiagnosticResult(diagnosticId.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(13, 43) + .WithMessage(diagnosticId == DiagnosticId.ChangeAnyToAll ? ChangeAnyToAllAnalyzer.MessageAny : ChangeAnyToAllAnalyzer.MessageAll); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Theory] + [InlineData("typeSymbol?.AllInterfaces.Any(i => i == 1)", "!typeSymbol?.AllInterfaces.All(i => i != 1)")] + [InlineData("!typeSymbol?.AllInterfaces.All(i => i != 1)", "typeSymbol?.AllInterfaces.Any(i => i == 1)")] + public async Task FixesWithElvisOperator(string code, string fixedCode) + { + const string source = @" +using System; +using System.Linq; +class TypeSymbol +{{ + public System.Collections.Generic.IList AllInterfaces; +}} +class TypeName +{{ + void Foo() + {{ + var typeSymbol = new TypeSymbol(); + var y = {0}; + }} +}}"; + await VerifyCSharpFixAsync(string.Format(source, code), string.Format(source, fixedCode)); + } + + [Theory] + [InlineData("Any", DiagnosticId.ChangeAnyToAll)] + [InlineData("All", DiagnosticId.ChangeAllToAny)] + public async Task ExpressionBodiedMemberCreatesDiagnostic(string methodName, DiagnosticId diagnosticId) + { + var source = $@" +using System; +using System.Linq; +class TypeName +{{ + private System.Collections.Generic.IList xs; + bool Foo() => xs.{methodName}(i => i == 1); +}}"; + var expected = new DiagnosticResult(diagnosticId.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(7, 22) + .WithMessage(diagnosticId == DiagnosticId.ChangeAnyToAll ? ChangeAnyToAllAnalyzer.MessageAny : ChangeAnyToAllAnalyzer.MessageAll); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Theory] + [InlineData("xs.Any(i => i == 1)", "!xs.All(i => i != 1)")] + [InlineData("!xs.All(i => i != 1)", "xs.Any(i => i == 1)")] + public async Task FixesExpressionBodiedMember(string code, string fixedCode) + { + const string source = @" +using System; +using System.Linq; +class TypeName +{{ + private System.Collections.Generic.IList xs; + bool Foo() => {0}; +}}"; + await VerifyCSharpFixAsync(string.Format(source, code), string.Format(source, fixedCode)); + } + + [Theory] + [InlineData("Any", DiagnosticId.ChangeAnyToAll)] + [InlineData("All", DiagnosticId.ChangeAllToAny)] + public async Task NegationWithCoalesceExpressionCreatesDiagnostic(string methodName, DiagnosticId diagnosticId) + { + var source = $@" +var ints = new [] {{ 1 }}; +var query = !ints?.{methodName}(i => i == 1) ?? true;"; + var expected = new DiagnosticResult(diagnosticId.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(13, 20) + .WithMessage(diagnosticId == DiagnosticId.ChangeAnyToAll ? ChangeAnyToAllAnalyzer.MessageAny : ChangeAnyToAllAnalyzer.MessageAll); + await VerifyCSharpDiagnosticAsync(source.WrapInCSharpMethod(usings: "\nusing System.Linq;"), expected); + } + + [Theory] + [InlineData(@" + var ints = new [] {1, 2}; + var query = !ints?.Any(i => i == 1) ?? true;", @" + var ints = new [] {1, 2}; + var query = ints?.All(i => i != 1) ?? true;")] + public async Task FixesNegationWithCoalesceExpression(string original, string fix) => + await VerifyCSharpFixAsync(original.WrapInCSharpMethod(usings: "\nusing System.Linq;"), + fix.WrapInCSharpMethod(usings: "\nusing System.Linq;")); } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Refactoring/ComputeExpressionTests.cs b/test/CSharp/CodeCracker.Test/Refactoring/ComputeExpressionTests.cs index 4b92b70be..66f89503c 100644 --- a/test/CSharp/CodeCracker.Test/Refactoring/ComputeExpressionTests.cs +++ b/test/CSharp/CodeCracker.Test/Refactoring/ComputeExpressionTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Refactoring; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -38,13 +39,9 @@ public async Task BinaryExpressionWithLiteralOnLeftAndRightCreatesDiagnostic(str { var source = original.WrapInCSharpMethod(); var expression = original.Substring(columnOffset, original.Length - columnOffset - columnRightTrim - 1); - var expected = new DiagnosticResult - { - Id = DiagnosticId.ComputeExpression.ToDiagnosticId(), - Message = string.Format(ComputeExpressionAnalyzer.Message, expression), - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17 + columnOffset) } - }; + var expected = new DiagnosticResult(DiagnosticId.ComputeExpression.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(10, 13 + columnOffset) + .WithMessage(string.Format(ComputeExpressionAnalyzer.Message, expression)); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -80,5 +77,13 @@ public async Task CompilerErrorDoesNotRegisterAFix() => [Fact] public async Task ExpressionThatThrowsDoesNotRegisterAFix() => await VerifyCSharpHasNoFixAsync("var a = int.MaxValue + int.MaxValue;".WrapInCSharpMethod()); + + [Fact] + public async Task ExpressionOnArgumentsFix() + { + var source = "string.Format(\"2 Hours in minutes: {0}\", 60 * 2)".WrapInCSharpMethod(); + var fix = "string.Format(\"2 Hours in minutes: {0}\", 120)".WrapInCSharpMethod(); + await VerifyCSharpFixAsync(source, fix); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Refactoring/IntroduceFieldFromConstructorTest.cs b/test/CSharp/CodeCracker.Test/Refactoring/IntroduceFieldFromConstructorTest.cs index 63fb4df8a..e0bac6a01 100644 --- a/test/CSharp/CodeCracker.Test/Refactoring/IntroduceFieldFromConstructorTest.cs +++ b/test/CSharp/CodeCracker.Test/Refactoring/IntroduceFieldFromConstructorTest.cs @@ -27,6 +27,76 @@ public TypeName(int par) await VerifyCSharpHasNoDiagnosticsAsync(test); } + [Fact] + public async Task WhenConstructorInMidlleOfNoWhere() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + public TypeName(int par) + { + this.par = par; + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + + [Fact] + public async Task WhenConstructorOfStruct() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + struct TypeName + { + public TypeName(int par) + { + } + } + }"; + + const string expected = @" + using System; + + namespace ConsoleApplication1 + { + struct TypeName + { + private readonly int par; + + public TypeName(int par) + { + this.par = par; + } + } + }"; + await VerifyCSharpFixAsync(test, expected); + } + + [Fact] + public async Task WhenConstructorOfInterface() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + interface TypeName + { + public TypeName(int par) + { + this.par = par; + } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + [Fact] public async Task WhenConstructorParameterHasPrivateReadOnlyField() { @@ -103,6 +173,48 @@ public TypeName(int par) await VerifyCSharpFixAsync(test, expected); } + [Fact] + public async Task ConstructorParameterWithPrivateFieldTwoConstructors() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public TypeName() + { + } + + public TypeName(int par) + { + } + } + }"; + + const string expected = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + private readonly int par; + + public TypeName() + { + } + + public TypeName(int par) + { + this.par = par; + } + } + }"; + await VerifyCSharpFixAsync(test, expected); + } + [Fact] public async Task FieldAlreadyExistsAndMatchesType() { @@ -472,8 +584,6 @@ public TypeName(int par) await VerifyCSharpFixAsync(test, expected); } - - [Fact] public async Task IntroduceFieldConstructorFixAllInProject() { @@ -518,7 +628,7 @@ class foo1 { private readonly int a; - public foo1(int a) + public foo1(int a) { this.a = a; } @@ -528,7 +638,7 @@ class foo2 private readonly int b; private readonly int a; - public foo2(int a, int b) + public foo2(int a, int b) { this.a = a; this.b = b; diff --git a/test/CSharp/CodeCracker.Test/Refactoring/InvertForTests.cs b/test/CSharp/CodeCracker.Test/Refactoring/InvertForTests.cs index 89bcb57b9..c46800685 100644 --- a/test/CSharp/CodeCracker.Test/Refactoring/InvertForTests.cs +++ b/test/CSharp/CodeCracker.Test/Refactoring/InvertForTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Refactoring; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -125,13 +126,9 @@ public async Task CreateDiagnosticsWithForLoopsFrom0ToN() { var test = WrapInCSharpMethod(@"for (var i = 0; i < n; i++){}"); - var expected = new DiagnosticResult - { - Id = DiagnosticId.InvertFor.ToDiagnosticId(), - Message = "Make it a for loop that decrement the counter.", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.InvertFor.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(10, 17) + .WithMessage("Make it a for loop that decrement the counter."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -141,13 +138,9 @@ public async Task CreateDiagnosticsWithForLoopsTheUsesAnDeclaredVariableAsCounte { var test = WrapInCSharpMethod(@"int i = 0; for (i = 0; i < n; i++){}"); - var expected = new DiagnosticResult - { - Id = DiagnosticId.InvertFor.ToDiagnosticId(), - Message = "Make it a for loop that decrement the counter.", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 28) } - }; + var expected = new DiagnosticResult(DiagnosticId.InvertFor.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(10, 28) + .WithMessage("Make it a for loop that decrement the counter."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -157,13 +150,9 @@ public async Task CreateDiagnosticsWithForLoopsFromNTo0() { var test = WrapInCSharpMethod(@"for (var i = n - 1; i >= 0; i--){}"); - var expected = new DiagnosticResult - { - Id = DiagnosticId.InvertFor.ToDiagnosticId(), - Message = "Make it a for loop that increment the counter.", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.InvertFor.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(10, 17) + .WithMessage("Make it a for loop that increment the counter."); await VerifyCSharpDiagnosticAsync(test, expected); } diff --git a/test/CSharp/CodeCracker.Test/Refactoring/MergeNestedIfTest.cs b/test/CSharp/CodeCracker.Test/Refactoring/MergeNestedIfTest.cs index 3adee8668..32911a9f5 100644 --- a/test/CSharp/CodeCracker.Test/Refactoring/MergeNestedIfTest.cs +++ b/test/CSharp/CodeCracker.Test/Refactoring/MergeNestedIfTest.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Refactoring; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -39,13 +40,9 @@ public async Task NestedIfCreatesDiagnostic() var a = 1; } }".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.MergeNestedIf.ToDiagnosticId(), - Message = MergeNestedIfAnalyzer.MessageFormat, - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.MergeNestedIf.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(11, 17) + .WithMessage(MergeNestedIfAnalyzer.MessageFormat); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -185,5 +182,70 @@ public async Task IfWithoutBlockWithNestedIfWithoutBlockFixed() var a = 1;//comment2".WrapInCSharpMethod(); await VerifyCSharpFixAsync(test, fixtest); } + + [Fact] + public async Task FixAddsParenthesisWhenSecondConditionHasLogicalORExpression() + { + var test = @" +var conditionA = false; +var conditionB = false; +var conditionC = false; +if (conditionA) + if (conditionB || conditionC) + System.Console.WriteLine(); +".WrapInCSharpMethod(); + var fixtest = @" +var conditionA = false; +var conditionB = false; +var conditionC = false; +if (conditionA && (conditionB || conditionC)) + System.Console.WriteLine(); +".WrapInCSharpMethod(); + await VerifyCSharpFixAsync(test, fixtest); + } + + [Fact] + public async Task FixAddsParenthesisWhenSecondConditionHasConditionalExpression() + { + var test = @" +var conditionA = false; +var conditionB = false; +var conditionC = false; +var conditionD = false; +if (conditionA) + if (conditionB ? conditionC : conditionD) + System.Console.WriteLine(); +".WrapInCSharpMethod(); + var fixtest = @" +var conditionA = false; +var conditionB = false; +var conditionC = false; +var conditionD = false; +if (conditionA && (conditionB ? conditionC : conditionD)) + System.Console.WriteLine(); +".WrapInCSharpMethod(); + await VerifyCSharpFixAsync(test, fixtest); + } + + [Fact] + public async Task FixAddsParenthesisWhenSecondConditionHasCoalescingExpression() + { + var test = @" +var conditionA = false; +bool? conditionB = false; +var conditionC = false; +if (conditionA) + if (conditionB ?? conditionC) + System.Console.WriteLine(); +".WrapInCSharpMethod(); + var fixtest = @" +var conditionA = false; +bool? conditionB = false; +var conditionC = false; +if (conditionA && (conditionB ?? conditionC)) + System.Console.WriteLine(); +".WrapInCSharpMethod(); + await VerifyCSharpFixAsync(test, fixtest); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Refactoring/NumericLiteralTests.cs b/test/CSharp/CodeCracker.Test/Refactoring/NumericLiteralTests.cs index 20637f936..00949a8b8 100644 --- a/test/CSharp/CodeCracker.Test/Refactoring/NumericLiteralTests.cs +++ b/test/CSharp/CodeCracker.Test/Refactoring/NumericLiteralTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Refactoring; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -54,6 +55,7 @@ public async Task FixReplacesLiteral(string literal, string fixedLiteral) await VerifyCSharpFixAsync(source, fixtest); } + [Fact] public async Task FixOnMethodCallReplacesLiteral() { var source = @" @@ -79,13 +81,11 @@ void Foo() await VerifyCSharpFixAsync(source, fixtest); } - private static DiagnosticResult CreateDiagnosticResult(string literal, bool isDecimal, int row = 10, int col = 25) => - new DiagnosticResult - { - Id = DiagnosticId.NumericLiteral.ToDiagnosticId(), - Message = string.Format(NumericLiteralAnalyzer.Message, literal, isDecimal ? "hexadecimal" : "decimal"), - Severity = DiagnosticSeverity.Hidden, - Locations = new[] {new DiagnosticResultLocation("Test0.cs", row, col)} - }; + private static DiagnosticResult CreateDiagnosticResult(string literal, bool isDecimal, int row = 10, int col = 21) + { + return new DiagnosticResult(DiagnosticId.NumericLiteral.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(row, col) + .WithMessage(string.Format(NumericLiteralAnalyzer.Message, literal, isDecimal ? "hexadecimal" : "decimal")); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Refactoring/ParameterRefectoryTests.cs b/test/CSharp/CodeCracker.Test/Refactoring/ParameterRefectoryTests.cs index 93ce74d8a..6a310b211 100644 --- a/test/CSharp/CodeCracker.Test/Refactoring/ParameterRefectoryTests.cs +++ b/test/CSharp/CodeCracker.Test/Refactoring/ParameterRefectoryTests.cs @@ -152,28 +152,23 @@ public async Task ShouldUpdateParameterToClass() { const string oldTest = @" using System; - namespace ConsoleApplication1 { class TypeName { public void Foo(string a, string b, int year, string d) { - } } - }"; const string newTest = @" using System; - namespace ConsoleApplication1 { class TypeName { public void Foo(NewClassFoo newClassFoo) { - } } @@ -185,10 +180,7 @@ public class NewClassFoo public string D { get; set; } } }"; - await VerifyCSharpFixAsync(oldTest, newTest, 0); - - - + await VerifyCSharpFixAsync(oldTest, newTest); } [Fact] diff --git a/test/CSharp/CodeCracker.Test/Refactoring/PropertyChangedEventArgsUnnecessaryAllocationTests.cs b/test/CSharp/CodeCracker.Test/Refactoring/PropertyChangedEventArgsUnnecessaryAllocationTests.cs new file mode 100644 index 000000000..27fcfbb2f --- /dev/null +++ b/test/CSharp/CodeCracker.Test/Refactoring/PropertyChangedEventArgsUnnecessaryAllocationTests.cs @@ -0,0 +1,462 @@ +using CodeCracker.CSharp.Refactoring; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace CodeCracker.Test.CSharp.Refactoring +{ + public class PropertyChangedEventArgsUnnecessaryAllocationTests : CodeFixVerifier + { + public static IEnumerable SharedDataAnalyzer + { + get + { + yield return new[] { "\"Name\"" }; + yield return new[] { "nameof(Name)" }; + yield return new[] { "null" }; + } + } + + [Fact] + public async Task DoesNotTriggerDiagnosticWithEmptySourceCodeAsync() + { + const string source = @""; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Theory] + [MemberData(nameof(SharedDataAnalyzer))] + public async Task DoesTriggerDiagnosticAtPropertyChangedEventArgsInstanceCreation(string ctorArg) + { + var source = $"var args = new PropertyChangedEventArgs({ctorArg})"; + + await VerifyCSharpDiagnosticAsync(source, PropertyChangedUnnecessaryAllocationDiagnostic(1, 12)); + } + + [Theory] + [MemberData(nameof(SharedDataAnalyzer))] + public async Task DoesTriggerDiagnosticAtPropertyChangedEventArgsInstanceCreationInMethodInvocation(string ctorArg) + { + var source = $@" +public class Test +{{ + public void TestMethod() + {{ + PropertyChanged(new PropertyChangedEventArgs({ctorArg})) + }} +}}"; + + await VerifyCSharpDiagnosticAsync(source, PropertyChangedUnnecessaryAllocationDiagnostic(6, 25)); + } + + [Theory] + [MemberData(nameof(SharedDataAnalyzer))] + public async Task DoesTriggerDiagnosticAtPropertyChangedEventArgsInstanceCreationInObjectInitializer(string ctorArg) + { + var source = $"object args = new {{ Name = new PropertyChangedEventArgs({ctorArg}) }}"; + + await VerifyCSharpDiagnosticAsync(source, PropertyChangedUnnecessaryAllocationDiagnostic(1, 28)); + } + + [Theory] + [MemberData(nameof(SharedDataAnalyzer))] + public async Task DoesNotTriggerDiagnosticAtPropertyChangedEventArgsInstanceCreationInFieldAssignmentWhenFieldIsStatic(string ctorArg) + { + var source = $@" +public class Test +{{ + private static PropertyChangedEventArgs field = new PropertyChangedEventArgs({ctorArg}); +}}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task DoesNotTriggerDiagnosticAtPropertyChangedEventArgsInstanceCreationInStaticConstructor() + { + const string source = @" +public class Test +{ + private static PropertyChangedEventArgs field; + + static Test() + { + field = new PropertyChangedEventArgs(""Name""); + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task DoesNotTriggerDiagnosticAtObjectInstanceCreation() + { + const string source = @" +public class Test +{ + private object field = new object(); +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task DoesTriggerDiagnosticAtObjectInstanceCreationUsingQualifiedName() + { + const string source = @" +public class Test +{ + private object field = new System.ComponentModel.PropertyChangedEventArgs(null); +}"; + + await VerifyCSharpDiagnosticAsync(source, PropertyChangedUnnecessaryAllocationDiagnostic(4, 28)); + } + + [Theory] + [MemberData(nameof(SharedDataAnalyzer))] + public async Task DoesTriggerDiagnosticInLambdaExpression(string ctorArg) + { + var source = $@" +using System; +using System.ComponentModel; +public class Test +{{ + private PropertyChangedEventArgs field; + + public Test() + {{ + Action action = () => field = new PropertyChangedEventArgs({ctorArg}); + }} +}}"; + + await VerifyCSharpDiagnosticAsync(source, PropertyChangedUnnecessaryAllocationDiagnostic(10, 39)); + } + + [Fact] + public async Task DoesNotTriggerWhenArgumentIsNotLiteral() + { + var source = $@" +public class Test +{{ + public void TestMethod(string propertyName) + {{ + PropertyChanged(new PropertyChangedEventArgs(propertyName)) + }} +}}"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + public static DiagnosticResult PropertyChangedUnnecessaryAllocationDiagnostic(int line, int column) + { + return new DiagnosticResult(DiagnosticId.PropertyChangedEventArgsUnnecessaryAllocation.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(line, column) + .WithMessage("Create PropertyChangedEventArgs static instance and reuse it to avoid unecessary memory allocation."); + } + + public static IEnumerable SharedDataCodeFix + { + get + { + yield return new[] { "\"Name\"", "Name" }; + yield return new[] { "nameof(Name)", "Name" }; + yield return new[] { "null", "AllProperties" }; + yield return new[] { "\"*\"", "AllProperties" }; + yield return new[] { "\"Name-\"", "Name" }; + } + } + + [Theory] + [MemberData(nameof(SharedDataCodeFix))] + public async Task ChangesPropertyChangedEventArgsInstanceToUseStaticField(string ctorArg, string fieldSuffix) + { + var source = $@" +using System.ComponentModel; +public class TestClass +{{ + public string Name {{ get;set; }} + + public void Foo() + {{ + var args = new PropertyChangedEventArgs({ctorArg}); + }} +}}"; + + var fixedCode = $@" +using System.ComponentModel; +public class TestClass +{{ + private static readonly PropertyChangedEventArgs PropertyChangedEventArgsFor{fieldSuffix} = new PropertyChangedEventArgs({ctorArg}); + + public string Name {{ get;set; }} + + public void Foo() + {{ + var args = PropertyChangedEventArgsFor{fieldSuffix}; + }} +}}"; + + await VerifyCSharpFixAsync(source, fixedCode); + } + + [Theory] + [MemberData(nameof(SharedDataCodeFix))] + public async Task DoesFixWhenEventArgsUsedInMethodInvocation(string ctorArg, string fieldSuffix) + { + var source = $@" +using System.ComponentModel; +public class TestClass +{{ + public string Name {{ get;set; }} + + public void Foo() + {{ + On(new PropertyChangedEventArgs({ctorArg})); + }} + + public void On(PropertyChangedEventArgs args) {{ }} +}}"; + + var fixedCode = $@" +using System.ComponentModel; +public class TestClass +{{ + private static readonly PropertyChangedEventArgs PropertyChangedEventArgsFor{fieldSuffix} = new PropertyChangedEventArgs({ctorArg}); + + public string Name {{ get;set; }} + + public void Foo() + {{ + On(PropertyChangedEventArgsFor{fieldSuffix}); + }} + + public void On(PropertyChangedEventArgs args) {{ }} +}}"; + + await VerifyCSharpFixAsync(source, fixedCode); + } + + [Theory] + [MemberData(nameof(SharedDataCodeFix))] + public async Task HandlesMultipleClassDeclarations(string ctorArg, string fieldSuffix) + { + var source = $@" +using System.ComponentModel; +public class TestClass +{{ + public string Name {{ get;set; }} + + public void Foo() + {{ + var args = new PropertyChangedEventArgs({ctorArg}); + }} +}} + +public class TestClass2 +{{ +}}"; + + var fixedCode = $@" +using System.ComponentModel; +public class TestClass +{{ + private static readonly PropertyChangedEventArgs PropertyChangedEventArgsFor{fieldSuffix} = new PropertyChangedEventArgs({ctorArg}); + + public string Name {{ get;set; }} + + public void Foo() + {{ + var args = PropertyChangedEventArgsFor{fieldSuffix}; + }} +}} + +public class TestClass2 +{{ +}}"; + + await VerifyCSharpFixAsync(source, fixedCode); + } + + [Theory] + [MemberData(nameof(SharedDataCodeFix))] + public async Task DoesFixWhenQualifiedNameUsed(string ctorArg, string fieldSuffix) + { + var source = $@" +public class TestClass +{{ + public string Name {{ get;set; }} + + public void Foo() + {{ + var args = new System.ComponentModel.PropertyChangedEventArgs({ctorArg}); + }} +}}"; + + var fixedCode = $@" +public class TestClass +{{ + private static readonly System.ComponentModel.PropertyChangedEventArgs PropertyChangedEventArgsFor{fieldSuffix} = new System.ComponentModel.PropertyChangedEventArgs({ctorArg}); + + public string Name {{ get;set; }} + + public void Foo() + {{ + var args = PropertyChangedEventArgsFor{fieldSuffix}; + }} +}}"; + + await VerifyCSharpFixAsync(source, fixedCode, allowNewCompilerDiagnostics: true); + } + + [Theory] + [MemberData(nameof(SharedDataCodeFix))] + public async Task DoesFixWhenEventArgsCreatedInField(string ctorArg, string fieldSuffix) + { + var source = $@" +using System.ComponentModel; +public class TestClass +{{ + private PropertyChangedEventArgs field = new PropertyChangedEventArgs({ctorArg}); +}}"; + + var fixedCode = $@" +using System.ComponentModel; +public class TestClass +{{ + private static readonly PropertyChangedEventArgs PropertyChangedEventArgsFor{fieldSuffix} = new PropertyChangedEventArgs({ctorArg}); + private PropertyChangedEventArgs field = PropertyChangedEventArgsFor{fieldSuffix}; +}}"; + + await VerifyCSharpFixAsync(source, fixedCode); + } + + [Theory] + [MemberData(nameof(SharedDataCodeFix))] + public async Task DoesFixWhenEventArgsCreatedInObjectInitializer(string ctorArg, string fieldSuffix) + { + var source = $@" +using System.ComponentModel; +public class TestClass +{{ + public string Name {{ get;set; }} + + public void Foo() + {{ + object args = new {{ Name = new PropertyChangedEventArgs({ctorArg}) }}; + }} +}}"; + + var fixedCode = $@" +using System.ComponentModel; +public class TestClass +{{ + private static readonly PropertyChangedEventArgs PropertyChangedEventArgsFor{fieldSuffix} = new PropertyChangedEventArgs({ctorArg}); + + public string Name {{ get;set; }} + + public void Foo() + {{ + object args = new {{ Name = PropertyChangedEventArgsFor{fieldSuffix} }}; + }} +}}"; + + await VerifyCSharpFixAsync(source, fixedCode); + } + + [Theory] + [MemberData(nameof(SharedDataCodeFix))] + public async Task HandlesNestedClass(string ctorArg, string fieldSuffix) + { + var source = $@" +using System.ComponentModel; +public class OuterClass +{{ + public class TestClass + {{ + private PropertyChangedEventArgs field = new PropertyChangedEventArgs({ctorArg}); + }} +}}"; + + var fixedCode = $@" +using System.ComponentModel; +public class OuterClass +{{ + public class TestClass + {{ + private static readonly PropertyChangedEventArgs PropertyChangedEventArgsFor{fieldSuffix} = new PropertyChangedEventArgs({ctorArg}); + private PropertyChangedEventArgs field = PropertyChangedEventArgsFor{fieldSuffix}; + }} +}}"; + + await VerifyCSharpFixAsync(source, fixedCode); + } + + [Theory] + [MemberData(nameof(SharedDataCodeFix))] + public async Task DoesFixLambdaExpression(string ctorArg, string fieldSuffix) + { + var source = $@" +using System; +using System.ComponentModel; +public class Test +{{ + private PropertyChangedEventArgs field; + + public Test() + {{ + Action action = () => field = new PropertyChangedEventArgs({ctorArg}); + }} +}}"; + + var fixedCode = $@" +using System; +using System.ComponentModel; +public class Test +{{ + private static readonly PropertyChangedEventArgs PropertyChangedEventArgsFor{fieldSuffix} = new PropertyChangedEventArgs({ctorArg}); + private PropertyChangedEventArgs field; + + public Test() + {{ + Action action = () => field = PropertyChangedEventArgsFor{fieldSuffix}; + }} +}}"; + + await VerifyCSharpFixAsync(source, fixedCode); + } + + [Theory] + [MemberData(nameof(SharedDataCodeFix))] + public async Task DoesFixWhenFieldNameIsAlreadyUsed(string ctorArg, string fieldSuffix) + { + var source = $@" +using System; +using System.ComponentModel; +public class Test +{{ + private PropertyChangedEventArgs PropertyChangedEventArgsFor{fieldSuffix}; + + public Test() + {{ + Action action = () => PropertyChangedEventArgsFor{fieldSuffix} = new PropertyChangedEventArgs({ctorArg}); + }} +}}"; + + var fixedCode = $@" +using System; +using System.ComponentModel; +public class Test +{{ + private static readonly PropertyChangedEventArgs PropertyChangedEventArgsFor{fieldSuffix}1 = new PropertyChangedEventArgs({ctorArg}); + private PropertyChangedEventArgs PropertyChangedEventArgsFor{fieldSuffix}; + + public Test() + {{ + Action action = () => PropertyChangedEventArgsFor{fieldSuffix} = PropertyChangedEventArgsFor{fieldSuffix}1; + }} +}}"; + + await VerifyCSharpFixAsync(source, fixedCode); + } + } +} \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Refactoring/ReplaceWithGetterOnlyAutoPropertyTests.cs b/test/CSharp/CodeCracker.Test/Refactoring/ReplaceWithGetterOnlyAutoPropertyTests.cs new file mode 100644 index 000000000..528ab9f0d --- /dev/null +++ b/test/CSharp/CodeCracker.Test/Refactoring/ReplaceWithGetterOnlyAutoPropertyTests.cs @@ -0,0 +1,752 @@ +using CodeCracker.CSharp.Refactoring; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Testing; +using System.Threading.Tasks; +using Xunit; + +namespace CodeCracker.Test.CSharp.Refactoring +{ + public class ReplaceWithGetterOnlyAutoPropertyTests : CodeFixVerifier + { + private static string GetDiagnosticMessage(string propertyName) => $"Property {propertyName} can be converted to an getter-only auto-property."; + + [Fact] + public async Task EmptyCodeBlockPassesWithoutErrors() + { + const string test = @""; + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + [Fact] + public async Task SimplePropertyGetsTransformed() + { + var test = @" + readonly string _value; + + TypeName(string value) + { + _value=value; + } + + public string Value { get { return _value; } } + ".WrapInCSharpClass(); + var expected = new DiagnosticResult(DiagnosticId.ReplaceWithGetterOnlyAutoProperty.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(16, 27) + .WithMessage(GetDiagnosticMessage("Value")); + + await VerifyCSharpDiagnosticAsync(test, expected); + + var fixtest = @" + TypeName(string value) + { + Value = value; + } + + public string Value { get; } + ".WrapInCSharpClass(); + await VerifyCSharpFixAsync(test, fixtest); + } + + [Fact] + public async Task SimplePropertyGetsNotTransformedIfLessThanCSharp6() + { + var test = @" + readonly string _value; + + TypeName(string value) + { + _value=value; + } + + public string Value { get { return _value; } } + ".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(test, LanguageVersion.CSharp5); + } + + [Fact] + public async Task FieldInitializerIsPreserved() + { + var test = @" + readonly string value, value2 = ""InitValue""; + + TypeName(string value) + { + this.value=value; + } + + public string Value { get { return this.value; } } + ".WrapInCSharpClass(); + var expected = new DiagnosticResult(DiagnosticId.ReplaceWithGetterOnlyAutoProperty.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(16, 27) + .WithMessage(GetDiagnosticMessage("Value")); + + await VerifyCSharpDiagnosticAsync(test, expected); + + var fixtest = @" + readonly string value2 = ""InitValue""; + + TypeName(string value) + { + this.Value = value; + } + + public string Value { get; } = ""InitValue""; + ".WrapInCSharpClass(); + await VerifyCSharpFixAsync(test, fixtest); + } + + [Fact] + public async Task MultiplePropertiesPerClassGetTranformed() + { + var test = @" + readonly string value, value2=""InitValue""; + + TypeName(string value) + { + this.value=value; + this.value2=value; + } + + public string Value { get { return this.value; } } + public string Value2 { get { return this.value2; } } + ".WrapInCSharpClass(); + + var expected1 = new DiagnosticResult(DiagnosticId.ReplaceWithGetterOnlyAutoProperty.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(17, 27) + .WithMessage(GetDiagnosticMessage("Value")); + var expected2 = new DiagnosticResult(DiagnosticId.ReplaceWithGetterOnlyAutoProperty.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(18, 27) + .WithMessage(GetDiagnosticMessage("Value2")); + + await VerifyCSharpDiagnosticAsync(test, new DiagnosticResult[] { expected1, expected2 }); + + var fixtest = @" + TypeName(string value) + { + this.Value = value; + this.Value2 = value; + } + + public string Value { get; } = ""InitValue""; + public string Value2 { get; } = ""InitValue""; + ".WrapInCSharpClass(); + await VerifyCSharpFixAsync(test, fixtest); + } + + [Fact] + public async Task MultiplePropertiesPerClassWithFieldInitilizerAndUnusedFieldsGetTranformed() + { + var test = @" + readonly string value, value2, value3=""InitValue""; + + TypeName(string value) + { + this.value=value; + this.value2=value; + } + + public string Value { get { return this.value; } } + public string Value2 { get { return this.value2; } } + ".WrapInCSharpClass(); + var expected1 = new DiagnosticResult(DiagnosticId.ReplaceWithGetterOnlyAutoProperty.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(17, 27) + .WithMessage(GetDiagnosticMessage("Value")); + var expected2 = new DiagnosticResult(DiagnosticId.ReplaceWithGetterOnlyAutoProperty.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(18, 27) + .WithMessage(GetDiagnosticMessage("Value2")); + + await VerifyCSharpDiagnosticAsync(test, new DiagnosticResult[] { expected1, expected2 }); + + var fixtest = @" + readonly string value3=""InitValue""; + + TypeName(string value) + { + this.Value = value; + this.Value2 = value; + } + + public string Value { get; } = ""InitValue""; + public string Value2 { get; } = ""InitValue""; + ".WrapInCSharpClass(); + await VerifyCSharpFixAsync(test, fixtest); + } + + [Fact] + public async Task TypeOfPropertyMustFitTypeOfBackingField() + { + var test = @" + readonly IList value, value2; + + TypeName(IEnumerable value) + { + this.value=value.ToList(); + this.value2=value.ToList(); + } + + public IEnumerable Value { get { return this.value; } } + public IList Value2 { get { return this.value2; } } + ".WrapInCSharpClass(); + var expected = new DiagnosticResult(DiagnosticId.ReplaceWithGetterOnlyAutoProperty.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(18, 34) + .WithMessage(GetDiagnosticMessage("Value2")); + + await VerifyCSharpDiagnosticAsync(test, expected); + + var fixtest = @" + readonly IList value; + + TypeName(IEnumerable value) + { + this.value=value.ToList(); + this.Value2 = value.ToList(); + } + + public IEnumerable Value { get { return this.value; } } + public IList Value2 { get; } + ".WrapInCSharpClass(); + await VerifyCSharpFixAsync(test, fixtest); + } + + [Fact] + public async Task ExplicitPropertyImplementationsAreIgnored() + { + const string test = @" + namespace ConsoleApplication1 + { + interface ITestInterface + { + string Property { get; } + } + class TestClass2: ITestInterface + { + readonly string _Property; + + string ITestInterface.Property + { + get + { + return _Property; + } + } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + [Fact] + public async Task SeveralInitializerAreAssignedProperly() + { + var test = @" + readonly int a = 0, x, y = 1, z = 2; + + public int X + { + get + { + return x; + } + } + ".WrapInCSharpClass(); + var fixtest = @" + readonly int a = 0, y = 1, z = 2; + + public int X { get; } = 1; + ".WrapInCSharpClass(); + await VerifyCSharpFixAsync(test, fixtest); + } + + [Fact] + public async Task FieldNameIsRenamedInClass() + { + var test = @" + readonly int _X; + + public TypeName(int x) + { + _X=x; + _X=_X*2; + Console.Write(_X); + } + + protected void M() => Console.Write(_X); + + public int X + { + get + { + return _X; + } + } + ".WrapInCSharpClass(); + var fixtest = @" + public TypeName(int x) + { + X=x; + X=X*2; + Console.Write(X); + } + + protected void M() => Console.Write(X); + + public int X { get; } + ".WrapInCSharpClass(); + await VerifyCSharpFixAsync(test, fixtest); + } + + [Fact] + public async Task ShadowedFieldNameIsNotRenamedInClass() + { + var test = @" + readonly int _X; + + public TypeName(int x) + { + _X=x; + } + + protected void M() + { + string _X=""; + Console.Write(_X); + } + + public int X + { + get + { + return _X; + } + } + ".WrapInCSharpClass(); + var fixtest = @" + public TypeName(int x) + { + X=x; + } + + protected void M() + { + string _X=""; + Console.Write(_X); + } + + public int X { get; } + ".WrapInCSharpClass(); + await VerifyCSharpFixAsync(test, fixtest); + } + + [Fact] + public async Task FieldAccessInInnerClassIsRenamed() + { + var test = @" + readonly int _A; + + public TypeName(int a) + { + _A=a; + } + + class InnerClass { + InnerClass(TypeName outterObject) + { + Console.Write(outterObject._A); + } + } + public int A + { + get + { + return _A; + } + } + ".WrapInCSharpClass(); + var fixtest = @" + public TypeName(int a) + { + A=a; + } + + class InnerClass { + InnerClass(TypeName outterObject) + { + Console.Write(outterObject.A); + } + } + public int A { get; } + ".WrapInCSharpClass(); + await VerifyCSharpFixAsync(test, fixtest); + } + + [Fact] + public async Task FieldWithSameNameInOtherClassIsNotRenamed() + { + const string test = @" + using System; + namespace App { + public class C1 + { + readonly int _A; + + public C1(int a) + { + _A=a; + } + + public int A + { + get + { + return _A; + } + } + } + public class C2 + { + readonly int _A; + } + }"; + const string fixtest = @" + using System; + namespace App { + public class C1 + { + + public C1(int a) + { + A=a; + } + + public int A { get; } + } + public class C2 + { + readonly int _A; + } + }"; + await VerifyCSharpFixAsync(test, fixtest); + } + + [Fact] + public async Task RenamingOfFieldAccessCanIntroduceNameClashesCaughtByCompilerWarningCS1717() + { + var test = @" + readonly int _A; + + public TypeName(int A) + { + _A=A; + } + + public int A + { + get + { + return _A; + } + } + ".WrapInCSharpClass(); + var fixtest = @" + public TypeName(int A) + { + A=A; + } + + public int A { get; } + ".WrapInCSharpClass(); + // "A=A;" causes new compiler warning CS1717: Assignment made to same variable; did you mean to assign something else? + // The fix would be to transform the expression to this.A=A; + // Maybe using Microsoft.CodeAnalysis.Rename.Renamer.RenameSymbolAsync() for the renaming is the able to fix this. + await VerifyCSharpFixAsync(oldSource: test, newSource: fixtest, allowNewCompilerDiagnostics: true); + } + + [Fact] + public async Task FieldReferencesInPartialClassesGetRenamedIfInTheSameDocument() + { + const string test = @" + namespace A + { + using System; + public partial class A1 + { + readonly int _I; + public A1(int i) + { + _I = i; + } + public int I { get { return _I; } } + } + public partial class A1 + { + public void Print() => Console.Write(_I); + } + }"; + const string fixtest = @" + namespace A + { + using System; + public partial class A1 + { + public A1(int i) + { + I = i; + } + public int I { get; } + } + public partial class A1 + { + public void Print() => Console.Write(I); + } + }"; + await VerifyCSharpFixAsync(oldSource: test, newSource: fixtest, allowNewCompilerDiagnostics: false); + } + + [Fact] + public async Task FieldReferencesInPartialClassesInDifferentDocumentsGetNotRenamedAndCauseCompilerErrorCS0103() + { + const string testPart1 = @" + namespace A + { + public partial class A1 + { + readonly int _I; + public A1(int i) + { + _I = i; + } + public int I { get { return _I; } } + } + }"; + const string testPart2 = @" + namespace A + { + using System; + public partial class A1 + { + public void Print() => Console.Write(_I); + } + }"; + const string fixtestPart1 = @" + namespace A + { + public partial class A1 + { + public A1(int i) + { + I = i; + } + public int I { get; } + } + }"; + const string fixtestPart2 = @" + namespace A + { + using System; + public partial class A1 + { + public void Print() => Console.Write(_I); + } + }"; + //Console.Write(_I); causes CS0103 The name '_I' does not exist in the current context + await VerifyCSharpFixAllAsync(oldSources: new string[] { testPart1, testPart2 }, newSources: new string[] { fixtestPart1, fixtestPart2 }, allowNewCompilerDiagnostics: true); + } + + #region FixAll Tests + + [Fact] + public async Task ReplaceMultiplePropertiesInOneClassFixAllTest() + { + var source1 = @" + readonly int _A; + readonly int _B; + readonly string _C; + + public TypeName(int a, int b, string c) + { + _A=a; + _B=b; + _C=c; + } + public int A { get { return _A; } } + public int B { get { return _B; } } + public string C { get { return _C; } } + ".WrapInCSharpClass(); + var fixtest1 = @" + public TypeName(int a, int b, string c) + { + A=a; + B=b; + C=c; + } + public int A { get; } + public int B { get; } + public string C { get; } + ".WrapInCSharpClass(); + await VerifyCSharpFixAllAsync(new[] { source1 }, new[] { fixtest1 }); + } + + [Fact] + public async Task ReplaceMultiplePropertiesInOneClassInMultipleDocumentsFixAllTest() + { + const string source1 = @" + namespace A + { + public class A1 + { + readonly int _A; + readonly int _B; + readonly string _C; + public A1(int a, int b, string c) + { + _A=a; + _B=b; + _C=c; + } + public int A { get { return _A; } } + public int B { get { return _B; } } + public string C { get { return _C; } } + } + }"; + const string source2 = @" + namespace A + { + public class A2 + { + readonly int _A; + readonly int _B; + readonly string _C; + public A2(int a, int b, string c) + { + _A=a; + _B=b; + _C=c; + } + public int A { get { return _A; } } + public int B { get { return _B; } } + public string C { get { return _C; } } + } + }"; + const string source3 = @" + namespace B + { + public class B1 + { + readonly int _A; + readonly int _B; + readonly string _C; + public B1(int a, int b, string c) + { + _A=a; + _B=b; + _C=c; + } + public int A { get { return _A; } } + public int B { get { return _B; } } + public string C { get { return _C; } } + } + }"; + const string fixtest1 = @" + namespace A + { + public class A1 + { + public A1(int a, int b, string c) + { + A=a; + B=b; + C=c; + } + public int A { get; } + public int B { get; } + public string C { get; } + } + }"; + const string fixtest2 = @" + namespace A + { + public class A2 + { + public A2(int a, int b, string c) + { + A=a; + B=b; + C=c; + } + public int A { get; } + public int B { get; } + public string C { get; } + } + }"; + const string fixtest3 = @" + namespace B + { + public class B1 + { + public B1(int a, int b, string c) + { + A=a; + B=b; + C=c; + } + public int A { get; } + public int B { get; } + public string C { get; } + } + }"; + await VerifyCSharpFixAllAsync(new[] { source1, source2, source3 }, new[] { fixtest1, fixtest2, fixtest3 }); + } + + [Fact] + public async Task ReplaceMultiplePropertiesInOneClassInMultipleDocumentsAndKeepExisitingDocumentsWithoutDiagnosticsFixAllTest() + { + const string source1 = @" + namespace A + { + public class A1 + { + readonly int _A; + readonly int _B; + readonly string _C; + public A1(int a, int b, string c) + { + _A=a; + _B=b; + _C=c; + } + public int A { get { return _A; } } + public int B { get { return _B; } } + public string C { get { return _C; } } + } + }"; + const string source2 = @" + namespace A + { + public class A2 + { + public A2() + { + } + } + }"; + const string fixtest1 = @" + namespace A + { + public class A1 + { + public A1(int a, int b, string c) + { + A=a; + B=b; + C=c; + } + public int A { get; } + public int B { get; } + public string C { get; } + } + }"; + await VerifyCSharpFixAllAsync(new[] { source1, source2 }, new[] { fixtest1, source2 }); + } + #endregion + } +} \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Refactoring/SplitIntoNestedIfTests.cs b/test/CSharp/CodeCracker.Test/Refactoring/SplitIntoNestedIfTests.cs index bf3507e67..41da694f2 100644 --- a/test/CSharp/CodeCracker.Test/Refactoring/SplitIntoNestedIfTests.cs +++ b/test/CSharp/CodeCracker.Test/Refactoring/SplitIntoNestedIfTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Refactoring; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -32,13 +33,9 @@ public async Task IfWithElseDoesNotCreateDiagnostic() public async Task IfWithAndCreatesDiagnostic() { var source = "if (true && true) { }".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.SplitIntoNestedIf.ToDiagnosticId(), - Message = string.Format(SplitIntoNestedIfAnalyzer.Message), - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.SplitIntoNestedIf.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(10, 17) + .WithMessage(string.Format(SplitIntoNestedIfAnalyzer.Message)); await VerifyCSharpDiagnosticAsync(source, expected); } diff --git a/test/CSharp/CodeCracker.Test/Refactoring/StringRepresentationTests.cs b/test/CSharp/CodeCracker.Test/Refactoring/StringRepresentationTests.cs index 8a97aca6f..d716b7a01 100644 --- a/test/CSharp/CodeCracker.Test/Refactoring/StringRepresentationTests.cs +++ b/test/CSharp/CodeCracker.Test/Refactoring/StringRepresentationTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Refactoring; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Xunit; @@ -55,13 +56,9 @@ void M() } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringRepresentation_RegularString.ToDiagnosticId(), - Message = "Change to regular string", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringRepresentation_RegularString.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(6, 17) + .WithMessage("Change to regular string"); return VerifyCSharpDiagnosticAsync(source, expected); } @@ -77,13 +74,9 @@ void M() } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringRepresentation_VerbatimString.ToDiagnosticId(), - Message = "Change to verbatim string", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringRepresentation_VerbatimString.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(6, 17) + .WithMessage("Change to verbatim string"); return VerifyCSharpDiagnosticAsync(source, expected); } diff --git a/test/CSharp/CodeCracker.Test/Reliability/UseConfigureAwaitFalseTests.cs b/test/CSharp/CodeCracker.Test/Reliability/UseConfigureAwaitFalseTests.cs index b0aaf74ff..ef2e99a68 100644 --- a/test/CSharp/CodeCracker.Test/Reliability/UseConfigureAwaitFalseTests.cs +++ b/test/CSharp/CodeCracker.Test/Reliability/UseConfigureAwaitFalseTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Reliability; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -8,23 +9,19 @@ namespace CodeCracker.Test.CSharp.Reliability public class UseConfigureAwaitFalseTests : CodeFixVerifier { [Theory] - [InlineData("System.Threading.Tasks.Task t; await t;", 48)] - [InlineData("System.Threading.Tasks.Task t; await t.ContinueWith(_ => 42);", 48)] - [InlineData("await System.Threading.Tasks.Task.Delay(1000);", 17)] - [InlineData("await System.Threading.Tasks.Task.FromResult(0);", 17)] - [InlineData("await System.Threading.Tasks.Task.Run(() => {});", 17)] - [InlineData("Func f; await f();", 54)] + [InlineData("System.Threading.Tasks.Task t; await t;", 44)] + [InlineData("System.Threading.Tasks.Task t; await t.ContinueWith(_ => 42);", 44)] + [InlineData("await System.Threading.Tasks.Task.Delay(1000);", 13)] + [InlineData("await System.Threading.Tasks.Task.FromResult(0);", 13)] + [InlineData("await System.Threading.Tasks.Task.Run(() => {});", 13)] + [InlineData("Func f; await f();", 50)] public async Task WhenAwaitingTaskAnalyzerCreatesDiagnostic(string sample, int column) { var test = sample.WrapInCSharpMethod(isAsync: true); - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseConfigureAwaitFalse.ToDiagnosticId(), - Message = "Consider using ConfigureAwait(false) on the awaited task.", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, column) } - }; + var expected = new DiagnosticResult(DiagnosticId.UseConfigureAwaitFalse.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(10, column) + .WithMessage("Consider using ConfigureAwait(false) on the awaited task."); await VerifyCSharpDiagnosticAsync(test, expected); } diff --git a/test/CSharp/CodeCracker.Test/Style/AlwaysUseVarTests.cs b/test/CSharp/CodeCracker.Test/Style/AlwaysUseVarTests.cs index 148f41b20..54f83e00d 100644 --- a/test/CSharp/CodeCracker.Test/Style/AlwaysUseVarTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/AlwaysUseVarTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -7,6 +8,7 @@ namespace CodeCracker.Test.CSharp.Style { public class AlwaysUseVarTests : CodeFixVerifier { + [Fact] public async Task IgnoresConstantDeclarations() { @@ -94,10 +96,54 @@ class Fee: IFee {} } [Fact] - public async Task CreateDiagnosticsWhenAssigningValueWithSameDeclaringType() + public async Task IgnoresDeclarationsWithDynamicVaribleName() + { + const string test = @" + using System; + namespace ConsoleApplication1 + { + class TypeName + { + void Foo() + { + dynamic fee = Fee(); + } + object Fee() { return 42; } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + + [Fact] + public async Task DeclarationsWithDynamicVaribleNameWithInitializerAlsoDynamicCreatesDiagnosic() { const string test = @" using System; + namespace ConsoleApplication1 + { + class TypeName + { + void Foo() + { + dynamic fee = Fee(); + } + dynamic Fee() { return 42; } + } + }"; + var expected = new DiagnosticResult(DiagnosticId.AlwaysUseVar.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(9, 17) + .WithMessage("Use 'var' instead of specifying the type name."); + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Theory] + [InlineData("int")] + [InlineData("System.Int32")] + public async Task CreateDiagnosticsWhenAssigningValueWithSameDeclaringTypePrimitive(string value) + { + var test = @" + using System; namespace ConsoleApplication1 { @@ -105,17 +151,38 @@ class TypeName { public async Task Foo() { - int a = 10; + " + value + @" a = 10; } } }"; - var expected = new DiagnosticResult + var expected = new DiagnosticResult(DiagnosticId.AlwaysUseVarOnPrimitives.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(10, 17) + .WithMessage("Use 'var' instead of specifying the type name."); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Theory] + [InlineData("string a = \"10\";")] + [InlineData("DateTime date1 = new DateTime(2013, 6, 1, 12, 32, 30);")] + public async Task CreateDiagnosticsWhenAssigningValueWithSameDeclaringTypeNonPrimitive(string value) + { + var test = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public async Task Foo() { - Id = DiagnosticId.AlwaysUseVar.ToDiagnosticId(), - Message = "Use 'var' instead of specifying the type name.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + " + value + @" + } + } + }"; + var expected = new DiagnosticResult(DiagnosticId.AlwaysUseVar.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(10, 17) + .WithMessage("Use 'var' instead of specifying the type name."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -215,5 +282,81 @@ public void Foo() }"; await VerifyCSharpFixAllAsync(new[] { source1, source2 }, new[] { fixtest1, fixtest2 }); } + + + [Fact] + public async Task FixReplacesMultipleDeclarationWithMultipleVars() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public async Task Foo() + { + int a = 10, b = 12; + } + } + }"; + + const string expected = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public async Task Foo() + { + var a = 10; + var b = 12; + } + } + }"; + await VerifyCSharpFixAsync(test, expected); + } + + + + [Fact] + public async Task FixPreservesTriviaSensibly() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public async Task Foo() + { + int a = 10; //Blue + + /*variables for use*/ string /*desc of b*/b = /* why not*/ ""12"", /*Formatter does this My next variable*/ c /* is quite nice and it */ = ""23""; + } + } + }"; + + const string expected = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public async Task Foo() + { + var a = 10; //Blue + + /*variables for use*/ var /*desc of b*/b = /* why not*/ ""12""; + var +/*Formatter does this My next variable*/ c /* is quite nice and it */ = ""23""; + } + } + }"; + await VerifyCSharpFixAllAsync(test, expected); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Style/ConsoleWriteLineTests.cs b/test/CSharp/CodeCracker.Test/Style/ConsoleWriteLineTests.cs index 1335bc5df..ad678d592 100644 --- a/test/CSharp/CodeCracker.Test/Style/ConsoleWriteLineTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/ConsoleWriteLineTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -174,13 +175,9 @@ void Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ConsoleWriteLine.ToDiagnosticId(), - Message = ConsoleWriteLineAnalyzer.MessageFormat.ToString(), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.ConsoleWriteLine.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(11, 17) + .WithMessage(ConsoleWriteLineAnalyzer.MessageFormat.ToString()); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -200,13 +197,9 @@ void Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ConsoleWriteLine.ToDiagnosticId(), - Message = ConsoleWriteLineAnalyzer.MessageFormat.ToString(), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.ConsoleWriteLine.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 17) + .WithMessage(ConsoleWriteLineAnalyzer.MessageFormat.ToString()); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -404,13 +397,9 @@ void Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ConsoleWriteLine.ToDiagnosticId(), - Message = ConsoleWriteLineAnalyzer.MessageFormat.ToString(), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.ConsoleWriteLine.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(11, 17) + .WithMessage(ConsoleWriteLineAnalyzer.MessageFormat.ToString()); await VerifyCSharpDiagnosticAsync(source, expected); } diff --git a/test/CSharp/CodeCracker.Test/Style/ConvertLambdaExpressionToMethodGroupTests.cs b/test/CSharp/CodeCracker.Test/Style/ConvertLambdaExpressionToMethodGroupTests.cs index b82e826f1..f26fcd8b9 100644 --- a/test/CSharp/CodeCracker.Test/Style/ConvertLambdaExpressionToMethodGroupTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/ConvertLambdaExpressionToMethodGroupTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -12,13 +13,9 @@ public class ConvertLambdaExpressionToMethodGroupTests public async Task CreateDiagnosticForSimpleLambdaExpression() { const string test = @"var f = a.Where(item => filter(item));"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ConvertLambdaExpressionToMethodGroup.ToDiagnosticId(), - Message = "You should remove the lambda expression and pass just 'filter' instead.", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 1, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.ConvertLambdaExpressionToMethodGroup.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(1, 17) + .WithMessage("You should remove the lambda expression and pass just 'filter' instead."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -27,13 +24,9 @@ public async Task CreateDiagnosticForSimpleLambdaExpression() public async Task CreateDiagnosticForSimpleLambdaExpressionWithBlockInBody() { const string test = @"var f = a.Where(item => { return filter(item); });"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ConvertLambdaExpressionToMethodGroup.ToDiagnosticId(), - Message = "You should remove the lambda expression and pass just 'filter' instead.", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 1, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.ConvertLambdaExpressionToMethodGroup.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(1, 17) + .WithMessage("You should remove the lambda expression and pass just 'filter' instead."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -42,13 +35,9 @@ public async Task CreateDiagnosticForSimpleLambdaExpressionWithBlockInBody() public async Task CreateDiagnosticForParenthesizedLambdaExpressionWithBlockInBody() { const string test = @"var f = a.Foo((param1, param2) => { return filter(param1, param2); });"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ConvertLambdaExpressionToMethodGroup.ToDiagnosticId(), - Message = "You should remove the lambda expression and pass just 'filter' instead.", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 1, 15) } - }; + var expected = new DiagnosticResult(DiagnosticId.ConvertLambdaExpressionToMethodGroup.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(1, 15) + .WithMessage("You should remove the lambda expression and pass just 'filter' instead."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -609,5 +598,23 @@ public static Task Finally(this Task task, Func(); + xs.Select(x => Func(x)); + } + object Func(object x) => x; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(oldCode); + } + } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Style/ConvertToExpressionBodiedMemberTests.cs b/test/CSharp/CodeCracker.Test/Style/ConvertToExpressionBodiedMemberTests.cs index dd4f00c79..ef0170df5 100644 --- a/test/CSharp/CodeCracker.Test/Style/ConvertToExpressionBodiedMemberTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/ConvertToExpressionBodiedMemberTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -177,13 +178,9 @@ public async Task Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ConvertToExpressionBodiedMember.ToDiagnosticId(), - Message = ConvertToExpressionBodiedMemberAnalyzer.MessageFormat, - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 13) } - }; + var expected = new DiagnosticResult(DiagnosticId.ConvertToExpressionBodiedMember.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(8, 13) + .WithMessage(ConvertToExpressionBodiedMemberAnalyzer.MessageFormat); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -204,13 +201,9 @@ class TypeName } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ConvertToExpressionBodiedMember.ToDiagnosticId(), - Message = ConvertToExpressionBodiedMemberAnalyzer.MessageFormat, - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 13) } - }; + var expected = new DiagnosticResult(DiagnosticId.ConvertToExpressionBodiedMember.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(8, 13) + .WithMessage(ConvertToExpressionBodiedMemberAnalyzer.MessageFormat); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -231,13 +224,9 @@ public static implicit operator string(TypeName n) } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ConvertToExpressionBodiedMember.ToDiagnosticId(), - Message = ConvertToExpressionBodiedMemberAnalyzer.MessageFormat, - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 13) } - }; + var expected = new DiagnosticResult(DiagnosticId.ConvertToExpressionBodiedMember.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(8, 13) + .WithMessage(ConvertToExpressionBodiedMemberAnalyzer.MessageFormat); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -258,13 +247,9 @@ public Foo this[int id] } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ConvertToExpressionBodiedMember.ToDiagnosticId(), - Message = ConvertToExpressionBodiedMemberAnalyzer.MessageFormat, - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 13) } - }; + var expected = new DiagnosticResult(DiagnosticId.ConvertToExpressionBodiedMember.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(8, 13) + .WithMessage(ConvertToExpressionBodiedMemberAnalyzer.MessageFormat); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -285,13 +270,9 @@ public string Foo } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ConvertToExpressionBodiedMember.ToDiagnosticId(), - Message = ConvertToExpressionBodiedMemberAnalyzer.MessageFormat, - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 13) } - }; + var expected = new DiagnosticResult(DiagnosticId.ConvertToExpressionBodiedMember.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(8, 13) + .WithMessage(ConvertToExpressionBodiedMemberAnalyzer.MessageFormat); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -373,6 +354,7 @@ public Foo this[int id] await VerifyCSharpHasNoDiagnosticsAsync(test); } + [Fact] public async Task IgnoresIndexersWithArrows() { const string test = @" @@ -431,6 +413,7 @@ public string Foo await VerifyCSharpHasNoDiagnosticsAsync(test); } + [Fact] public async Task IgnoresPropertiesWithArrows() { const string test = @" diff --git a/test/CSharp/CodeCracker.Test/Style/ConvertToSwitchTests.cs b/test/CSharp/CodeCracker.Test/Style/ConvertToSwitchTests.cs index b4fb48bf0..bcdb5d47d 100644 --- a/test/CSharp/CodeCracker.Test/Style/ConvertToSwitchTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/ConvertToSwitchTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -38,13 +39,9 @@ public async Task Foo(string s) } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ConvertToSwitch.ToDiagnosticId(), - Message = "You could use 'switch' instead of 'if'.", - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.ConvertToSwitch.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 17) + .WithMessage("You could use 'switch' instead of 'if'."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -76,13 +73,9 @@ public async Task Foo(string s) } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ConvertToSwitch.ToDiagnosticId(), - Message = "You could use 'switch' instead of 'if'.", - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.ConvertToSwitch.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 17) + .WithMessage("You could use 'switch' instead of 'if'."); await VerifyCSharpDiagnosticAsync(test, expected); } diff --git a/test/CSharp/CodeCracker.Test/Style/EmptyObjectInitializerTests.cs b/test/CSharp/CodeCracker.Test/Style/EmptyObjectInitializerTests.cs index a32dccc61..2c978823b 100644 --- a/test/CSharp/CodeCracker.Test/Style/EmptyObjectInitializerTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/EmptyObjectInitializerTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -11,13 +12,9 @@ public class EmptyObjectInitializerTests : CodeFixVerifier 1; + public int this[int index] => 1; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IfHasMethodGetEnumeratorReturningIEnumeratorCreatesDiagnostic() + { + const string source = @" +class Foo +{ + void Bar() + { + var list = new MyList(); + for (int i = 0; i < list.Count; i++) + { + var item = list[i]; + } + } +} +class MyList +{ + public int Count => 1; + public int this[int index] => 1; + public System.Collections.IEnumerator GetEnumerator() + { + yield return 1; + } +}"; + var expected = new DiagnosticResult(DiagnosticId.ForInArray.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(7, 9) + .WithMessage(ForInArrayAnalyzer.MessageFormat); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task IfHasMethodGetEnumeratorReturningClassThatImplementsIEnumeratorCreatesDiagnostic() + { + const string source = @" +class Foo +{ + void Bar() + { + var list = new MyList(); + for (int i = 0; i < list.Count; i++) + { + var item = list[i]; + } + } +} +public class MyEnumerator : System.Collections.IEnumerator +{ + public object Current => 1; + public bool MoveNext() { } + public void Reset() { } +} +class MyList +{ + public int Count => 1; + public int this[int index] => 1; + public MyEnumerator GetEnumerator() + { + yield return 1; + } +}"; + var expected = new DiagnosticResult(DiagnosticId.ForInArray.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(7, 9) + .WithMessage(ForInArrayAnalyzer.MessageFormat); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task WithFieldCreatesDiagnostic() + { + var source = @" +private int[] array = new [] {1}; +public int Foo() +{ + for (var i = 0; i < array.Length; i++) + { + var item = array[i]; + } +}".WrapInCSharpClass(); + var expected = new DiagnosticResult(DiagnosticId.ForInArray.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(12, 5) + .WithMessage("You can use foreach instead of for."); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task WithPropertyCreatesDiagnostic() + { + var source = @" +public int[] Foo +{ + set + { + for (var i = 0; i < value.Length; i++) + { + var item = value[i]; + } + } +} +}".WrapInCSharpClass(); + var expected = new DiagnosticResult(DiagnosticId.ForInArray.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(13, 9) + .WithMessage("You can use foreach instead of for."); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task WithValueTypeNoDiagnostic() + { + const string source = @" + using System; + using System.Collections.Generic; + + namespace Test + { + struct Foo + { + public int X; + } + + public class bar + { + static void Goo() + { + var array = new Foo[1]; + for (int i = 0; i < array.Length; i++) + { + var actual = array[i]; + array[i].X = 5; + } + } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task WithValueTypeAndIEnumeratorDoesNotCreateDiagnostic() + { + const string source = @" +class Foo +{ + void Bar() + { + var list = new MyList(); + for (int i = 0; i < list.Count; i++) + { + var actual = list[i]; + actual.X = 5; + } + } +} +struct Struct +{ + public int X { get; set; } +} +class MyList +{ + public int Count => 1; + public Struct this[int index] => new Struct(); + public System.Collections.Generic.IEnumerator GetEnumerator() + { + yield return new Struct(); + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task WithValueTypeListNoDiagnostic() + { + const string source = @" + using System; + using System.Collections.Generic; + + namespace Test + { + struct Foo + { + public int X; + } + + public class bar + { + static void Goo() + { + var array = new List(); + for (int i = 0; i < array.Length; i++) + { + var actual = array[i]; + array[i].X = 5; + } + } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IgnoreWhenWouldChangeTheIterationVariableWithAssignment() + { + var source = @" +var a = new[] { 1 }; +for (var i = 0; i < a.Length; i++) +{ + var item = a[i]; + item = 0; +}".WrapInCSharpMethod(); + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IgnoreWhenWouldChangeTheIterationVariableWithRef() + { + var source = @" +void Foo() +{ + var a = new[] { 1 }; + for (var i = 0; i < a.Length; i++) + { + var item = a[i]; + Bar(ref item); + } +} +void Bar(ref int i) +{ + i = 1; +}".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IgnoreWhenWouldChangeTheIterationVariableWithPostfixUnary() + { + var source = @" +void Foo() +{ + var a = new[] { 1 }; + for (var i = 0; i < a.Length; i++) + { + var item = a[i]; + item++; + } +}".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IgnoreWhenWouldChangeTheIterationVariableWithPrefixUnary() + { + var source = @" +void Foo() +{ + var a = new[] { 1 }; + for (var i = 0; i < a.Length; i++) + { + var item = a[i]; + ++item; + } +}".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(source); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Style/InterfaceNameTests.cs b/test/CSharp/CodeCracker.Test/Style/InterfaceNameTests.cs index 8267d58cb..a37f973d4 100644 --- a/test/CSharp/CodeCracker.Test/Style/InterfaceNameTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/InterfaceNameTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -32,13 +33,9 @@ public interface Foo void Test(); } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.InterfaceName.ToDiagnosticId(), - Message = InterfaceNameAnalyzer.MessageFormat, - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 9) } - }; + var expected = new DiagnosticResult(DiagnosticId.InterfaceName.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(4, 9) + .WithMessage(InterfaceNameAnalyzer.MessageFormat); await VerifyCSharpDiagnosticAsync(source, expected); } diff --git a/test/CSharp/CodeCracker.Test/Style/ObjectInitializerTests.cs b/test/CSharp/CodeCracker.Test/Style/ObjectInitializerTests.cs index 246d45b7e..dc52f6bb3 100644 --- a/test/CSharp/CodeCracker.Test/Style/ObjectInitializerTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/ObjectInitializerTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -7,6 +8,16 @@ namespace CodeCracker.Test.CSharp.Style { public class ObjectInitializerWithLocalDeclarationTests : CodeFixVerifier { + [Fact] + public async Task WhenDeclaringADynamicVariableDoesNotCreateDiagnostic() + { + var source = @" +dynamic p = new Person(); +p.Name = ""Giovanni""; +p.Age = 25; +"; + await VerifyCSharpHasNoDiagnosticsAsync(source.WrapInCSharpMethod()); + } [Fact] public async Task WhenAssigningButNotCreatingAnalyzerDoesNotCreateDiagnostic() @@ -42,6 +53,24 @@ public int Foo() await VerifyCSharpHasNoDiagnosticsAsync(source); } + [Fact] + public async Task WhenUsedWithCollectionDoesNotCreateDiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + class TypeName + { + public int Foo() + { + var ys = new System.Collections.Generic.List { 4 }; + ys.Capacity = 3; + } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + [Fact] public async Task WhenVariableIsDeclaredAndObjectIsCreatedButNoAssignmentsHappenLaterAnalyzerDoesNotCreateDiagnostic() { @@ -113,13 +142,9 @@ public int Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ObjectInitializer_LocalDeclaration.ToDiagnosticId(), - Message = "You can use initializers in here.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.ObjectInitializer_LocalDeclaration.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(8, 17) + .WithMessage("You can use initializers in here."); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -166,6 +191,50 @@ public int Foo() await VerifyCSharpFixAsync(source, fixtest, 0); } + [Fact] + public async Task FixDoesNotRemoveNextStatement() + { + const string source = @" +class DataObject +{ + public int MyInt { get; set; } + public double MyDouble { get; set; } +} + +class Program +{ + static void Main() + { + var data = new DataObject + { + MyInt = 5 + }; + data.MyDouble = 5.6; + var a = 1; + } +}"; + const string fixtest = @" +class DataObject +{ + public int MyInt { get; set; } + public double MyDouble { get; set; } +} + +class Program +{ + static void Main() + { + var data = new DataObject + { + MyInt = 5, + MyDouble = 5.6 + }; + var a = 1; + } +}"; + await VerifyCSharpFixAsync(source, fixtest, 0); + } + [Fact] public async Task ObjectCreationWithoutConstructorDoesNotCreateDiagnostic() { @@ -212,13 +281,9 @@ void Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ObjectInitializer_LocalDeclaration.ToDiagnosticId(), - Message = "You can use initializers in here.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 15, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.ObjectInitializer_LocalDeclaration.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(15, 17) + .WithMessage("You can use initializers in here."); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -325,6 +390,69 @@ public void Bar() }"; await VerifyCSharpHasNoDiagnosticsAsync(source); } + + [Fact] + public async Task WhenUsingANewVariableDeclaredAndAssigningToPropertiesOfJustCreatedObjectWithAssignmentTriviaChangeToObjectInitializersFix() + { + const string source = @" + namespace ConsoleApplication1 + { + class TypeName + { + public int Foo() + { + string a; + var p = new Person(); + #pragma warning disable CS0618 + p.Name = ""Giovanni""; // A name. + #pragma warning restore CS0618 + p.Age = 25; // An age. + string b; + } + } + }"; + + const string fixtest = @" + namespace ConsoleApplication1 + { + class TypeName + { + public int Foo() + { + string a; + var p = new Person + { + #pragma warning disable CS0618 + Name = ""Giovanni"", // A name. + #pragma warning restore CS0618 + Age = 25 // An age. + }; + string b; + } + } + }"; + await VerifyCSharpFixAsync(source, fixtest, 0); + } + + [Fact] + public async Task FixNestedConstructors() + { + var source = @" +public void Foo() +{ + var p = new Person(new Name()); + p.Age = 25; +}".WrapInCSharpClass(); + var fixtest = @" +public void Foo() +{ + var p = new Person(new Name()) + { + Age = 25 + }; +}".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } } public class ObjectInitializerWithAssignmentTests : CodeFixVerifier @@ -348,6 +476,26 @@ public int Foo() await VerifyCSharpHasNoDiagnosticsAsync(source); } + [Fact] + public async Task WhenUsedWithCollectionDoesNotCreateDiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + class TypeName + { + public int Foo() + { + System.Collections.Generic.List ys; + ys = new System.Collections.Generic.List { 4 }; + ys.Capacity = 3; + } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] public async Task WhenCreatingObjectAndAssigningPropertiesThenAnalyzerCreatesDiagnostic() { @@ -365,13 +513,9 @@ public int Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ObjectInitializer_Assignment.ToDiagnosticId(), - Message = "You can use initializers in here.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 9, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.ObjectInitializer_Assignment.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(9, 17) + .WithMessage("You can use initializers in here."); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -395,6 +539,7 @@ public int Foo() await VerifyCSharpHasNoDiagnosticsAsync(source); } + [Fact] public async Task WhenObjectIsCreatedButOnlyUnrelatedAssignmentsHappenLaterAnalyzerDoesNotCreateDiagnostic() { @@ -460,6 +605,55 @@ public int Foo() await VerifyCSharpFixAsync(source, fixtest, 0); } + [Fact] + public async Task WhenUsingAPreviouslyDeclaredVariableAndAssigningToPropertiesOfJustCreatedObjectWithAssignmentTriviaChangeToObjectInitializersFix() + { + const string source = @" + namespace ConsoleApplication1 + { + class TypeName + { + public int Foo() + { + string a; + //some comment before + Person p; + p = new Person(); + #pragma warning disable CS0618 + p.Name = ""Giovanni""; // A name. + #pragma warning restore CS0618 + p.Age = 25; // An age. + //some comment after + string b; + } + } + }"; + + const string fixtest = @" + namespace ConsoleApplication1 + { + class TypeName + { + public int Foo() + { + string a; + //some comment before + Person p; + p = new Person + { + #pragma warning disable CS0618 + Name = ""Giovanni"", // A name. + #pragma warning restore CS0618 + Age = 25 // An age. + }; + //some comment after + string b; + } + } + }"; + await VerifyCSharpFixAsync(source, fixtest, 0); + } + [Fact] public async Task ObjectCreationWithoutConstructorDoesNotCreateDiagnostic() { @@ -568,5 +762,23 @@ public int Foo() }"; await VerifyCSharpFixAsync(source, fixtest, 0); } + + [Fact] + public async Task IgnoreSelfAssignment() + { + const string source = @" +class Foo +{ + public Foo F { get; set; } + static void Baz() + { + Foo foo; + foo = new Foo(); + foo.F = foo; + } +} +"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Style/PropertyPrivateSetTests.cs b/test/CSharp/CodeCracker.Test/Style/PropertyPrivateSetTests.cs index 4217cfc4c..29f79dda9 100644 --- a/test/CSharp/CodeCracker.Test/Style/PropertyPrivateSetTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/PropertyPrivateSetTests.cs @@ -1,4 +1,6 @@ using CodeCracker.CSharp.Style; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -22,6 +24,42 @@ class TypeName await VerifyCSharpHasNoDiagnosticsAsync(test); } + [Fact] + public async Task NotWarningPrivatePropertyDeclaration() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + private int MyProperty { get; set; } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + [Fact] + public async Task WarningOnPublicPropertyDeclaration() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public int MyProperty { get; set; } + } + }"; + var expected = new DiagnosticResult(DiagnosticId.PropertyPrivateSet.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(8, 13) + .WithMessage(PropertyPrivateSetAnalyzer.MessageFormat); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + [Fact] public async Task PropertyPrivateBrackets() @@ -33,7 +71,7 @@ namespace ConsoleApplication1 { class TypeName { - public int MyProperty + public int MyProperty { get { return 0; } private set { } @@ -125,7 +163,7 @@ namespace ConsoleApplication1 { class TypeName { - public int MyProperty + public int MyProperty { get { return 0; } set { } @@ -140,10 +178,10 @@ namespace ConsoleApplication1 { class TypeName { - public int MyProperty + public int MyProperty { get { return 0; } - private set + private set { } } } @@ -163,7 +201,7 @@ namespace ConsoleApplication1 { class TypeName { - public int MyProperty + public int MyProperty { get { return 0; } set { } @@ -178,10 +216,10 @@ namespace ConsoleApplication1 { class TypeName { - public int MyProperty + public int MyProperty { get { return 0; } - protected set + protected set { } } } @@ -201,7 +239,7 @@ namespace ConsoleApplication1 class TypeName { [Obsolete(""this property is obsolete"")] - public int MyProperty + public int MyProperty { get { return 0; } set { } @@ -217,10 +255,10 @@ namespace ConsoleApplication1 class TypeName { [Obsolete(""this property is obsolete"")] - public int MyProperty + public int MyProperty { get { return 0; } - protected set + protected set { } } } diff --git a/test/CSharp/CodeCracker.Test/Style/RemoveAsyncFromMethodTests.cs b/test/CSharp/CodeCracker.Test/Style/RemoveAsyncFromMethodTests.cs index 553b62189..16c72882a 100644 --- a/test/CSharp/CodeCracker.Test/Style/RemoveAsyncFromMethodTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/RemoveAsyncFromMethodTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -21,14 +22,16 @@ public class Foo await VerifyCSharpHasNoDiagnosticsAsync(source); } + [Fact] public async Task MethodAsyncWithoutAsyncKeyword() { const string source = @" + using System.Threading.Tasks; namespace ConsoleApplication1 { public class Foo { - Task TestAsync() {}; + Task TestAsync() {} } }"; await VerifyCSharpHasNoDiagnosticsAsync(source); @@ -45,13 +48,9 @@ public class Foo void TestAsync() {}; } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RemoveAsyncFromMethod.ToDiagnosticId(), - Message = string.Format(RemoveAsyncFromMethodAnalyzer.MessageFormat, "TestAsync"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 18) } - }; + var expected = new DiagnosticResult(DiagnosticId.RemoveAsyncFromMethod.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 18) + .WithMessage(string.Format(RemoveAsyncFromMethodAnalyzer.MessageFormat, "TestAsync")); await VerifyCSharpDiagnosticAsync(source, expected); } diff --git a/test/CSharp/CodeCracker.Test/Style/RemoveCommentedCodeTests.cs b/test/CSharp/CodeCracker.Test/Style/RemoveCommentedCodeTests.cs index ef4f222db..e287e5941 100644 --- a/test/CSharp/CodeCracker.Test/Style/RemoveCommentedCodeTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/RemoveCommentedCodeTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -21,17 +22,13 @@ public async Task IgnoresRegularComments() await VerifyCSharpHasNoDiagnosticsAsync(test); } - [Fact(Skip ="Skipped until SourceCodeKind.Interactive can be set on CSharpParseOptions on the analyzer.")] + [Fact] public async Task CreateDiagnosticForSingleLineCommentedCode() { var test = @"// a = 10;".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.RemoveCommentedCode.ToDiagnosticId(), - Message = RemoveCommentedCodeAnalyzer.MessageFormat, - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.RemoveCommentedCode.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 13) + .WithMessage(RemoveCommentedCodeAnalyzer.MessageFormat); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -49,7 +46,7 @@ public async Task RemovesCommentedCodePreservingRegularComments() await VerifyCSharpFixAsync(test, fixtest); } - [Fact(Skip ="Skipped until SourceCodeKind.Interactive can be set on CSharpParseOptions on the analyzer.")] + [Fact] public async Task CreateDiagnosticForMultipleLinesCommentedCode() { var test = @" @@ -57,18 +54,14 @@ public async Task CreateDiagnosticForMultipleLinesCommentedCode() // { // DoStuff(); // }".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.RemoveCommentedCode.ToDiagnosticId(), - Message = RemoveCommentedCodeAnalyzer.MessageFormat, - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 13) } - }; + var expected = new DiagnosticResult(DiagnosticId.RemoveCommentedCode.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(11, 13) + .WithMessage(RemoveCommentedCodeAnalyzer.MessageFormat); await VerifyCSharpDiagnosticAsync(test, expected); } - [Fact(Skip ="Skipped until SourceCodeKind.Interactive can be set on CSharpParseOptions on the analyzer.")] + [Fact] public async Task RemovesCommentedMultilineCodePreservingRegularComments() { var test = @" @@ -103,7 +96,7 @@ class Foo await VerifyCSharpFixAsync(test, fixtest); } - [Fact(Skip ="Skipped until SourceCodeKind.Interactive can be set on CSharpParseOptions on the analyzer.")] + [Fact] public async Task RemovesNonPerfectIfCommentedCode() { var test = @" diff --git a/test/CSharp/CodeCracker.Test/Style/RemoveTrailingWhitespaceTests.cs b/test/CSharp/CodeCracker.Test/Style/RemoveTrailingWhitespaceTests.cs index 6237919e8..2130d04d4 100644 --- a/test/CSharp/CodeCracker.Test/Style/RemoveTrailingWhitespaceTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/RemoveTrailingWhitespaceTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -94,14 +95,9 @@ public async Task StringWithTrailingWhitespaceDoesNotCreateDiagnostic() public static DiagnosticResult CreateDiagnostic(int line, int column) { - var diagnostic = new DiagnosticResult - { - Id = DiagnosticId.RemoveTrailingWhitespace.ToDiagnosticId(), - Message = RemoveTrailingWhitespaceAnalyzer.MessageFormat, - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", line, column) } - }; - return diagnostic; + return new DiagnosticResult(DiagnosticId.RemoveTrailingWhitespace.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(line, column) + .WithMessage(RemoveTrailingWhitespaceAnalyzer.MessageFormat); } [Fact] @@ -139,5 +135,21 @@ class Foo { }"; class Foo { }"; await VerifyCSharpFixAsync(source, expected, formatBeforeCompare: false); } + + [Fact] + public async Task PragmaWithTrailingSpace() + { + const string source = @" +#pragma warning disable CC0072 +#pragma warning restore CC0072 +"; + + const string expected = @" +#pragma warning disable CC0072 +#pragma warning restore CC0072 +"; + + await VerifyCSharpFixAsync(source, expected, formatBeforeCompare: false); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Style/StringFormatTests.cs b/test/CSharp/CodeCracker.Test/Style/StringFormatTests.cs index b6d7bff6e..fdd944a07 100644 --- a/test/CSharp/CodeCracker.Test/Style/StringFormatTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/StringFormatTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -174,13 +175,9 @@ void Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormat.ToDiagnosticId(), - Message = StringFormatAnalyzer.MessageFormat.ToString(), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringFormat.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(11, 25) + .WithMessage(StringFormatAnalyzer.MessageFormat.ToString()); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -200,13 +197,9 @@ void Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormat.ToDiagnosticId(), - Message = StringFormatAnalyzer.MessageFormat.ToString(), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringFormat.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 25) + .WithMessage(StringFormatAnalyzer.MessageFormat.ToString()); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -404,13 +397,9 @@ void Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormat.ToDiagnosticId(), - Message = StringFormatAnalyzer.MessageFormat.ToString(), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringFormat.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(11, 25) + .WithMessage(StringFormatAnalyzer.MessageFormat.ToString()); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -609,5 +598,54 @@ void Foo() }"; await VerifyCSharpFixAsync(source, expected); } + + [Fact] + public async Task FixWithLineBreaksAndConditionalExpression() + { + var source = @" +var foo = 1; +var goo = string.Format(""{0}"", foo == 2 ? +10 : 15);".WrapInCSharpMethod(); + var expected = @" +var foo = 1; +var goo = $""{(foo == 2 ? 10 : 15)}"";".WrapInCSharpMethod(); + await VerifyCSharpFixAsync(source, expected); + } + + [Fact] + public async Task FixWithLineBreaksAndStringConcat() + { + var source = @" +var foo = 1; +var baz = string.Format(""{0}"", foo + + ""baz"" + + ""boo"");".WrapInCSharpMethod(); + var expected = @" +var foo = 1; +var baz = $""{foo + ""baz"" + ""boo""}"";".WrapInCSharpMethod(); + await VerifyCSharpFixAsync(source, expected); + } + + [Fact] + public async Task NestedStringFormatCreatesDiagnostic() + { + var source = @"var foo = string.Format(""{0}"", string.Format(""{0}"", 1 ) );".WrapInCSharpMethod(); + var expected1 = new DiagnosticResult(DiagnosticId.StringFormat.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 23) + .WithMessage(StringFormatAnalyzer.MessageFormat.ToString()); + var expected2 = new DiagnosticResult(DiagnosticId.StringFormat.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 44) + .WithMessage(StringFormatAnalyzer.MessageFormat.ToString()); + await VerifyCSharpDiagnosticAsync(source, expected1, expected2); + } + + [Fact] + public async Task FixAllStringInterpolations() + { +var foo = $"{$"{1}"}"; + var source = @"var foo = string.Format(""{0}"", string.Format(""{0}"", 1 ) );".WrapInCSharpMethod(); + var expected = @"var foo = $""{$""{1}""}"";".WrapInCSharpMethod(); + await VerifyCSharpFixAllAsync(source, expected); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Style/SwitchToAutoPropTests.cs b/test/CSharp/CodeCracker.Test/Style/SwitchToAutoPropTests.cs index 65adf11b6..ca9c498ef 100644 --- a/test/CSharp/CodeCracker.Test/Style/SwitchToAutoPropTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/SwitchToAutoPropTests.cs @@ -1,6 +1,7 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -278,13 +279,9 @@ public int Id set { id = value; } } ".WrapInCSharpClass(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.SwitchToAutoProp.ToDiagnosticId(), - Message = string.Format(SwitchToAutoPropAnalyzer.MessageFormat.ToString(), "Id"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 9) } - }; + var expected = new DiagnosticResult(DiagnosticId.SwitchToAutoProp.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 9) + .WithMessage(string.Format(SwitchToAutoPropAnalyzer.MessageFormat.ToString(), "Id")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -323,7 +320,7 @@ public int Id [Fact] public async Task FixSimplePropWithMultipleFieldsIntoAutoProp() { - var source = @" + const string source = @" private int x = 42, y; //comment 1 public int X { @@ -331,7 +328,7 @@ public int X set { x = value; } }"; - var expected = @" + const string expected = @" private int y; //comment 1 public int X { get; set; } = 42;"; @@ -341,7 +338,7 @@ public int X [Fact] public async Task FixSimplePropWithVaribleDeclarationNotFirstIntoAutoProp() { - var source = @" + const string source = @" private int x, y = 42; //comment 1 public int Y { @@ -349,45 +346,90 @@ public int Y set { y = value; } }"; - var expected = @" + const string expected = @" private int x; //comment 1 public int Y { get; set; } = 42;"; await VerifyCSharpFixAsync(source.WrapInCSharpClass(), expected.WrapInCSharpClass()); } - - [Fact(Skip = "Skip until the FixAll has been fixed.")] - public async Task FixSimplePropWithMultipleFieldsIntoAutoPropAll() + [Fact] + public async Task FixSimplePropWithMultipleFieldsIntoAutoPropAllInDoc() { var source = @" private int x = 42, y; private int z = int.MaxValue; - public int X { get { return x; } set { x = value; } } - public int Y { get { return y; } set { y = value; } } - public int Z { get { return z; } set { z = value; } - }"; - - var expected = @" - public int X { get; set; } = 42; + }".WrapInCSharpClass(); + var expected = @"public int X { get; set; } = 42; public int Y { get; set; } - public int Z { get; set; } = int.MaxValue;"; + public int Z { get; set; } = int.MaxValue;".WrapInCSharpClass(); + await VerifyCSharpFixAllAsync(source, expected); + } - await VerifyCSharpFixAllAsync(source.WrapInCSharpClass(), expected.WrapInCSharpClass()); + [Fact] + public async Task FixSimplePropWithMultipleFieldsIntoAutoPropAllInSolution() + { + var source1 = @" + void Foo() + { + var other = new OtherType(); + other.z = 1; + } + public int x = 42, y; + public int X + { + get { return x; } + set { x = value; } + } + public int Y + { + get { return y; } + set { y = value; } + }".WrapInCSharpClass(); + var source2 = @" + void Foo() + { + var type = new TypeName(); + type.x = 1; + type.y = 2; + } + public int z = int.MaxValue; + public int Z + { + get { return z; } + set { z = value; } + }".WrapInCSharpClass("OtherType"); + var expected1 = @" + void Foo() + { + var other = new OtherType(); + other.Z = 1; + } + public int X { get; set; } = 42; + public int Y { get; set; }".WrapInCSharpClass(); + var expected2 = @" + void Foo() + { + var type = new TypeName(); + type.X = 1; + type.Y = 2; + } + public int Z { get; set; } = int.MaxValue;".WrapInCSharpClass("OtherType"); + await VerifyCSharpFixAllAsync(new[] { source1, source2 }, new[] { expected1, expected2 }); } [Fact] @@ -447,5 +489,449 @@ public void Foo() ".WrapInCSharpClass("OtherType"); await VerifyCSharpFixAllAsync(new[] { source1, source2 }, new[] { expected1, expected2 }); } + + [Fact] + public async Task FixPropIntoAutoPropAndFixKeepingXMLComments() + { + const string source = @" + public class Foo + { + private int x = 10; + /// + /// comment 1 + /// + public int X + { + get { return x; } + set { x = value; } + } + }"; + + const string expected = @" + public class Foo + { + /// + /// comment 1 + /// + public int X { get; set; } = 10; + }"; + await VerifyCSharpFixAllAsync(source, expected); + } + + [Fact] + public async Task FixPropIntoAutoPropWithThisInSet() + { + var source = @" + private string text; + public string Text + { + get + { + return text; + } + + set + { + this.text = value; + } + }" +.WrapInCSharpClass(); + + var expected = @"public string Text{ get; set; }" +.WrapInCSharpClass(); + await VerifyCSharpFixAllAsync(source, expected); + } + + [Fact] + public async Task FixPropIntoAutoPropWithThisInGet() + { + var source = @" + private string text; + public string Text + { + get + { + return this.text; + } + + set + { + text = value; + } + } +".WrapInCSharpClass(); + + var expected = @"public string Text{ get; set; } +".WrapInCSharpClass(); + await VerifyCSharpFixAllAsync(source, expected); + } + + [Fact] + public async Task FixExplicitPropertyWithReferenceOnSameTime() + { + const string source = @" +interface IFoo +{ + int P { get; set; } +} +class Foo : IFoo +{ + public Foo() + { + p = 1; + } + private int p; + int IFoo.P + { + get + { + return p; + } + set + { + p = value; + } + } +}"; + const string expected = @" +interface IFoo +{ + int P { get; set; } +} +class Foo : IFoo +{ + public Foo() + { + ((IFoo)this).P = 1; + } + int IFoo.P { get; set; } +}"; + await VerifyCSharpFixAllAsync(source, expected); + } + + [Fact] + public async Task FixExplicitPropertyWithReferenceOnDifferentType() + { + const string source = @" +interface IFoo +{ + int P { get; set; } +} +class Foo : IFoo +{ + public int p; + int IFoo.P + { + get + { + return p; + } + set + { + p = value; + } + } +} +class Bar +{ + static void Baz() + { + var foo = new Foo(); + foo.p = 1; + } +}"; + const string expected = @" +interface IFoo +{ + int P { get; set; } +} +class Foo : IFoo +{ + int IFoo.P { get; set; } +} +class Bar +{ + static void Baz() + { + var foo = new Foo(); + ((IFoo)foo).P = 1; + } +}"; + await VerifyCSharpFixAllAsync(source, expected); + } + + [Fact] + public async Task FixAllExplicitPropertyWithReferenceOnDifferentNamespace() + { + const string source1 = @" +using Ns1; +namespace Ns1 +{ + interface IFoo + { + int P { get; set; } + } +} +namespace Ns2 +{ + class Foo : IFoo + { + public int p; + int IFoo.P + { + get + { + return p; + } + set + { + p = value; + } + } + } +}"; + const string source2 = @" +using Ns2; +namespace Ns3 +{ + class Bar + { + static void Baz() + { + var foo = new Foo(); + foo.p = 1; + } + } +}"; + const string expected1 = @" +using Ns1; +namespace Ns1 +{ + interface IFoo + { + int P { get; set; } + } +} +namespace Ns2 +{ + class Foo : IFoo + { + int IFoo.P { get; set; } + } +}"; + const string expected2 = @" +using Ns2; +namespace Ns3 +{ + class Bar + { + static void Baz() + { + var foo = new Foo(); + ((Ns1.IFoo)foo).P = 1; + } + } +}"; + await VerifyCSharpFixAllAsync(new[] { source1, source2 }, new[] { expected1, expected2 }); + } + + [Fact] + public async Task FixAllExplicitPropertyWithReferenceOnDifferentNamespaceWithImportedNs() + { + const string source1 = @" +using Ns1; +namespace Ns1 +{ + interface IFoo + { + int P { get; set; } + } +} +namespace Ns2 +{ + class Foo : IFoo + { + public int p; + int IFoo.P + { + get + { + return p; + } + set + { + p = value; + } + } + } +}"; + const string source2 = @" +using Ns1; +using Ns2; +namespace Ns3 +{ + class Bar + { + static void Baz() + { + var foo = new Foo(); + foo.p = 1; + } + } +}"; + const string expected1 = @" +using Ns1; +namespace Ns1 +{ + interface IFoo + { + int P { get; set; } + } +} +namespace Ns2 +{ + class Foo : IFoo + { + int IFoo.P { get; set; } + } +}"; + const string expected2 = @" +using Ns1; +using Ns2; +namespace Ns3 +{ + class Bar + { + static void Baz() + { + var foo = new Foo(); + ((IFoo)foo).P = 1; + } + } +}"; + await VerifyCSharpFixAllAsync(new[] { source1, source2 }, new[] { expected1, expected2 }); + } + + [Fact] + public async Task FixAllWithNonPublicProperty() + { + const string source1 = @" +namespace Ns1 +{ + class Foo + { + public int p; + private int P + { + get + { + return p; + } + set + { + p = value; + } + } + } +}"; + const string source2 = @" +using Ns1; +namespace Ns2 +{ + class Bar + { + static void Baz() + { + var foo = new Foo(); + foo.p = 1; + } + } +}"; + const string expected1 = @" +namespace Ns1 +{ + class Foo + { + public int P { get; set; } + } +}"; + const string expected2 = @" +using Ns1; +namespace Ns2 +{ + class Bar + { + static void Baz() + { + var foo = new Foo(); + foo.P = 1; + } + } +}"; + await VerifyCSharpFixAllAsync(new[] { source1, source2 }, new[] { expected1, expected2 }); + } + + [Fact] + public async Task FixKeepsStaticModifier() + { + const string source = @" + private int y; + public virtual int Y + { + get { return y; } + set { y = value; } + }"; + const string expected = @"public virtual int Y { get; set; }"; + await VerifyCSharpFixAsync(source.WrapInCSharpClass(), expected.WrapInCSharpClass()); + } + + [Fact] + public async Task FixKeepsVirtualModifier() + { + const string source = @" + private static int y; + public static int Y + { + get { return y; } + set { y = value; } + }"; + const string expected = @"public static int Y { get; set; }"; + await VerifyCSharpFixAsync(source.WrapInCSharpClass(), expected.WrapInCSharpClass()); + } + + [Fact] + public async Task FixUpdatesDerivedClassesCorrectly() + { + const string source = @" +class Point +{ + protected int x; + public virtual int X + { + get + { + return x; + } + set + { + x = value; + } + } +} +class NewPoint : Point +{ + public override int X => (x - 15); +}"; + const string expected = @" +class Point +{ + public virtual int X { get; set; } +} +class NewPoint : Point +{ + public override int X => (base.X - 15); +}"; + await VerifyCSharpFixAsync(source, expected); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Style/TaskNameASyncTests.cs b/test/CSharp/CodeCracker.Test/Style/TaskNameASyncTests.cs index 919a54fe6..4e2378b30 100644 --- a/test/CSharp/CodeCracker.Test/Style/TaskNameASyncTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/TaskNameASyncTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -7,6 +8,32 @@ namespace CodeCracker.Test.CSharp.Style { public class TaskNameAsyncTests : CodeFixVerifier { + [Fact] + public async Task TaskNameAsyncMethodEqualsNameMethodInterface() + { + const string source = @" + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + public interface IBar + { + Task Foo(); + } + + public class Bar : IBar + { + public Task Foo() + { } + } + }"; + var expected = new DiagnosticResult(DiagnosticId.TaskNameAsync.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(8, 26) + .WithMessage(string.Format(TaskNameAsyncAnalyzer.MessageFormat, "FooAsync")); + + await VerifyCSharpDiagnosticAsync(source, expected); + } + [Fact] public async Task TaskNameAsyncMethodCorrect() { @@ -70,13 +97,9 @@ public class Foo Task Test() {}; } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.TaskNameAsync.ToDiagnosticId(), - Message = string.Format(TaskNameAsyncAnalyzer.MessageFormat, "TestAsync"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 18) } - }; + var expected = new DiagnosticResult(DiagnosticId.TaskNameAsync.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(8, 18) + .WithMessage(string.Format(TaskNameAsyncAnalyzer.MessageFormat, "TestAsync")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -175,6 +198,184 @@ public interface IFoo public Task TestAsync() } + }"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task IgnoreMethodFromImplicitlyImplementedInterface() + { + const string source = @" +using System.Threading.Tasks; +public interface IBar +{ + Task Foo(); +} +public class Bar : IBar +{ + public Task Foo() + { + throw new System.NotImplementedException(); + } +}"; + //we still get the diagnostic for the interface itself + var expected = new DiagnosticResult(DiagnosticId.TaskNameAsync.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(5, 10) + .WithMessage(string.Format(TaskNameAsyncAnalyzer.MessageFormat, "FooAsync")); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task IgnoreMethodFromExplicitlyImplementedInterface() + { + const string source = @" +using System.Threading.Tasks; +public interface IBar +{ + Task Foo(); +} +public class Bar : IBar +{ + Task IBar.Foo() + { + throw new System.NotImplementedException(); + } +}"; + //we still get the diagnostic for the interface itself + var expected = new DiagnosticResult(DiagnosticId.TaskNameAsync.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(5, 10) + .WithMessage(string.Format(TaskNameAsyncAnalyzer.MessageFormat, "FooAsync")); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task ChangeTaskNameWithAsyncNotAtTheEndWithUpperCaseLetter() + { + const string source = @" + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + public class Foo + { + public methodTest() + { + await TestAsyncFoo(); + } + + public Task TestAsyncFoo() + { + return true; + } + } + + }"; + const string fixtest = @" + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + public class Foo + { + public methodTest() + { + await TestFooAsync(); + } + + public Task TestFooAsync() + { + return true; + } + } + + }"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task ChangeTaskNameWithAsyncNotAtTheEndWithUnderline() + { + const string source = @" + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + public class Foo + { + public methodTest() + { + await TestAsync_Foo(); + } + + public Task TestAsync_Foo() + { + return true; + } + } + + }"; + const string fixtest = @" + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + public class Foo + { + public methodTest() + { + await Test_FooAsync(); + } + + public Task Test_FooAsync() + { + return true; + } + } + + }"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task ChangeTaskNameWithAsyncNotAtTheEndWithDigit() + { + const string source = @" + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + public class Foo + { + public methodTest() + { + await TestAsync0Foo(); + } + + public Task TestAsync0Foo() + { + return true; + } + } + + }"; + const string fixtest = @" + using System.Threading.Tasks; + + namespace ConsoleApplication1 + { + public class Foo + { + public methodTest() + { + await Test0FooAsync(); + } + + public Task Test0FooAsync() + { + return true; + } + } + }"; await VerifyCSharpFixAsync(source, fixtest); } diff --git a/test/CSharp/CodeCracker.Test/Style/TernaryOperatorTests.cs b/test/CSharp/CodeCracker.Test/Style/TernaryOperatorTests.cs index 9940a298c..42447196b 100644 --- a/test/CSharp/CodeCracker.Test/Style/TernaryOperatorTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/TernaryOperatorTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -7,22 +8,6 @@ namespace CodeCracker.Test.CSharp.Style { public class TernaryOperatorWithAssignmentTests : CodeFixVerifier { - private const string source = @" - namespace ConsoleApplication1 - { - class TypeName - { - public int Foo() - { - var something = true; - if (something) - return 1; - else - return 2; - } - } - }"; - [Fact] public async Task WhenUsingIfWithoutElseAnalyzerDoesNotCreateDiagnostic() { @@ -42,110 +27,6 @@ public int Foo() await VerifyCSharpHasNoDiagnosticsAsync(sourceWithoutElse); } - [Fact] - public async Task WhenUsingIfWithElseButWithBlockWith2StatementsOnIfAnalyzerDoesNotCreateDiagnostic() - { - const string sourceWithoutElse = @" - namespace ConsoleApplication1 - { - class TypeName - { - public int Foo() - { - var something = true; - if (something) - { - string a = null; - return 1; - } - else - { - return 2; - } - } - } - }"; - await VerifyCSharpHasNoDiagnosticsAsync(sourceWithoutElse); - } - - [Fact] - public async Task WhenUsingIfWithElseButWithBlockWith2StatementsOnElseAnalyzerDoesNotCreateDiagnostic() - { - const string sourceWithoutElse = @" - namespace ConsoleApplication1 - { - class TypeName - { - public int Foo() - { - var something = true; - if (something) - { - return 1; - } - else - { - string a = null; - return 2; - } - } - } - }"; - await VerifyCSharpHasNoDiagnosticsAsync(sourceWithoutElse); - } - - [Fact] - public async Task WhenUsingIfWithElseButWithoutReturnOnElseAnalyzerDoesNotCreateDiagnostic() - { - const string sourceWithoutElse = @" - namespace ConsoleApplication1 - { - class TypeName - { - public int Foo() - { - var something = true; - if (something) - { - return 2; - } - else - { - string a = null; - } - return 1; - } - } - }"; - await VerifyCSharpHasNoDiagnosticsAsync(sourceWithoutElse); - } - - [Fact] - public async Task WhenUsingIfWithElseButWithoutReturnOnIfAnalyzerDoesNotCreateDiagnostic() - { - const string sourceWithoutElse = @" - namespace ConsoleApplication1 - { - class TypeName - { - public int Foo() - { - var something = true; - if (something) - { - string a = null; - } - else - { - return 2; - } - return 1; - } - } - }"; - await VerifyCSharpHasNoDiagnosticsAsync(sourceWithoutElse); - } - [Fact] public async Task TwoIfsInARowAnalyzerDoesNotCreateDiagnostic() { @@ -171,20 +52,6 @@ public int Foo() await VerifyCSharpHasNoDiagnosticsAsync(sourceWithoutElse); } - [Fact] - public async Task WhenUsingIfAndElseWithDirectReturnAnalyzerCreatesDiagnostic() - { - var expected = new DiagnosticResult - { - Id = DiagnosticId.TernaryOperator_Return.ToDiagnosticId(), - Message = "You can use a ternary operator.", - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 9, 17) } - }; - - await VerifyCSharpDiagnosticAsync(source, expected); - } - [Fact] public async Task WhenUsingIfAndElseWithAssignmentChangeToTernaryFix() { @@ -221,7 +88,7 @@ public int Foo() } } }"; - await VerifyCSharpFixAsync(source, fixtest, 0); + await VerifyCSharpFixAsync(source, fixtest); } [Fact] @@ -260,7 +127,7 @@ public void Foo() } } }"; - await VerifyCSharpFixAsync(source, fixtest, 0); + await VerifyCSharpFixAsync(source, fixtest, allowNewCompilerDiagnostics: true); } [Fact] @@ -348,7 +215,7 @@ public int Foo() } } }"; - await VerifyCSharpFixAsync(source, fixtest, 0); + await VerifyCSharpFixAsync(source, fixtest); } [Fact] @@ -398,6 +265,151 @@ public int Foo() }"; await VerifyCSharpFixAllAsync(new string[] { source, source.Replace("TypeName", "TypeName1") }, new string[] { fixtest, fixtest.Replace("TypeName", "TypeName1") }); } + + [Fact] + public async Task WhenUsingIfAndElseWithAssignmentAnalyzerCreatesDiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + class TypeName + { + public int Foo() + { + var something = true; + string a; + if (something) + { + a = ""a""; + } + else + { + a = ""b""; + } + } + } + }"; + var expected = new DiagnosticResult(DiagnosticId.TernaryOperator_Assignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 17) + .WithMessage("You can use a ternary operator."); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task FixConsidersAssignmentType() + { + const string source = @" +class Base { } +class A : Base { } +class B : Base { } +class Test2 +{ + public static void Foo() + { + var something = true; + Base b; + if (something) + b = new A(); + else + b = new B(); + } +} +"; + const string fixtest = @" +class Base { } +class A : Base { } +class B : Base { } +class Test2 +{ + public static void Foo() + { + var something = true; + Base b; + b = something ? (Base)new A() : new B(); + } +} +"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task WhenUsingIfAndElseWithAssignmentOfMethodResultChangeToTernaryFixGetsArgsApplied() + { + var source = @" + int Method(int a) => a; + + public void Foo() + { + var something = true; + int a; + if (something) + { + a = Method(1); + } + else + { + a = Method(2); + } + }".WrapInCSharpClass(); + var fixtest = @" + int Method(int a) => a; + + public void Foo() + { + var something = true; + int a; + a = Method(something?1:2); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task WhenUsingIfAndElseWithAssignmentOfMethodResultWithComplexArgumentEvaluationChangeToTernaryFixGetsArgsApplied() + { + var source = @" + int Method(int a) => a; + + public void Foo() + { + var something = true; + int a; + if (something) + { + a = Method(1); + } + else + { + a = Method(2 + 2); + } + }".WrapInCSharpClass(); + var fixtest = @" + int Method(int a) => a; + + public void Foo() + { + var something = true; + int a; + a = Method(something?1:2 + 2); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task WhenUsingIfAndElseWithAssignmentToAnInterfaceVariableAFittingCastIsInserted() + { + var source = @" + System.Collections.Generic.IEnumerable e= null; + if (true) + e = new int[10]; + else + e = new System.Collections.Generic.List(); + ".WrapInCSharpMethod(); + var fixtest = @" + System.Collections.Generic.IEnumerable e= null; + e = true ? (System.Collections.Generic.IEnumerable)new int[10] : new System.Collections.Generic.List(); + ".WrapInCSharpMethod(); + await VerifyCSharpFixAsync(source, fixtest); + } } public class TernaryOperatorWithReturnTests : CodeFixVerifier @@ -433,7 +445,7 @@ public int Foo() } } }"; - await VerifyCSharpFixAsync(source, fixtest, 0); + await VerifyCSharpFixAsync(source, fixtest); } [Fact] @@ -467,7 +479,119 @@ class TypeName } } }"; - await VerifyCSharpFixAsync(source, fixtest, 0); + await VerifyCSharpFixAsync(source, fixtest, allowNewCompilerDiagnostics: true); + } + + [Fact] + public async Task FixWhenThereIsNumericImplicitConversion() + { + var source = @" +static double OnReturn() +{ + var condition = true; + double aDouble = 2; + var bInteger = 3; + if (condition) + return aDouble; + else + return bInteger; +}".WrapInCSharpClass(); + var fixtest = @" +static double OnReturn() +{ + var condition = true; + double aDouble = 2; + var bInteger = 3; + return condition ? aDouble : bInteger; +}".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenThereIsImplicitConversionWithZeroAndEnum() + { + var source = @" +enum FooBar +{ + one, two +} +static FooBar OnReturn() +{ + var condition = true; + var fooBar = FooBar.one; + if (condition) + return 0; + else + return fooBar; +}".WrapInCSharpClass(); + var fixtest = @" +enum FooBar +{ + one, two +} +static FooBar OnReturn() +{ + var condition = true; + var fooBar = FooBar.one; + return condition ? 0 : fooBar; +}".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenThereIsImplicitConversionWithEnumAndZero() + { + var source = @" +enum FooBar +{ + one, two +} +static FooBar OnReturn() +{ + var condition = true; + var fooBar = FooBar.one; + if (condition) + return fooBar; + else + return 0; +}".WrapInCSharpClass(); + var fixtest = @" +enum FooBar +{ + one, two +} +static FooBar OnReturn() +{ + var condition = true; + var fooBar = FooBar.one; + return condition ? fooBar : 0; +}".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningAnObject() + { + var source = @" +static object OnReturn() +{ + var condition = true; + var aInt = 2; + var anObj = new object(); + if (condition) + return anObj; + else + return aInt; +}".WrapInCSharpClass(); + var fixtest = @" +static object OnReturn() +{ + var condition = true; + var aInt = 2; + var anObj = new object(); + return condition ? anObj : aInt; +}".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); } [Fact] @@ -510,9 +634,9 @@ public int Foo() } [Fact] - public async Task WhenUsingIfAndElseWithAssignmentAnalyzerCreatesDiagnostic() + public async Task WhenUsingIfWithElseButWithoutReturnOnIfAnalyzerDoesNotCreateDiagnostic() { - const string source = @" + const string sourceWithoutElse = @" namespace ConsoleApplication1 { class TypeName @@ -520,27 +644,732 @@ class TypeName public int Foo() { var something = true; - string a; if (something) { - a = ""a""; + string a = null; } else { - a = ""b""; + return 2; } + return 1; } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.TernaryOperator_Assignment.ToDiagnosticId(), - Message = "You can use a ternary operator.", - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + await VerifyCSharpHasNoDiagnosticsAsync(sourceWithoutElse); + } - await VerifyCSharpDiagnosticAsync(source, expected); + [Fact] + public async Task WhenUsingIfWithElseButWithoutReturnOnElseAnalyzerDoesNotCreateDiagnostic() + { + const string sourceWithoutElse = @" + namespace ConsoleApplication1 + { + class TypeName + { + public int Foo() + { + var something = true; + if (something) + { + return 2; + } + else + { + string a = null; + } + return 1; + } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(sourceWithoutElse); + } + + [Fact] + public async Task WhenUsingIfWithElseButWithBlockWith2StatementsOnElseAnalyzerDoesNotCreateDiagnostic() + { + const string sourceWithoutElse = @" + namespace ConsoleApplication1 + { + class TypeName + { + public int Foo() + { + var something = true; + if (something) + { + return 1; + } + else + { + string a = null; + return 2; + } + } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(sourceWithoutElse); + } + + [Fact] + public async Task WhenUsingIfWithElseButWithBlockWith2StatementsOnIfAnalyzerDoesNotCreateDiagnostic() + { + const string sourceWithoutElse = @" + namespace ConsoleApplication1 + { + class TypeName + { + public int Foo() + { + var something = true; + if (something) + { + string a = null; + return 1; + } + else + { + return 2; + } + } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(sourceWithoutElse); + } + + [Fact] + public async Task WhenUsingIfAndElseWithDirectReturnAnalyzerCreatesDiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + class TypeName + { + public int Foo() + { + var something = true; + if (something) + return 1; + else + return 2; + } + } + }"; + var expected = new DiagnosticResult(DiagnosticId.TernaryOperator_Return.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(9, 17) + .WithMessage("You can use a ternary operator."); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task FixConsidersReturnType() + { + const string source = @" +class Base { } +class A : Base { } +class B : Base { } +class Test +{ + public static Base Foo() + { + var something = true; + if (something) + return new A(); + else + return new B(); + } +} +"; + const string fixtest = @" +class Base { } +class A : Base { } +class B : Base { } +class Test +{ + public static Base Foo() + { + var something = true; + return something ? (Base)new A() : new B(); + } +} +"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task WhenReturnStatementContainsMethodCallAnalyzerCreatesDiagnostic() + { + var source = @" + private int Method(int i) => i; + + public int Foo() + { + if (true) + return Method(1); + else + return Method(2); + }".WrapInCSharpClass(); + var expected = new DiagnosticResult(DiagnosticId.TernaryOperator_Return.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(13, 17) + .WithMessage("You can use a ternary operator."); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task FixWhenReturningWithMethodWithSingleDifferentArgumentGetsArgsApplied() + { + var source = @" + private int Method(int i) => i; + + public int Foo() + { + if (true) + return Method(1); + else + return Method(2); + }".WrapInCSharpClass(); + var fixtest = @" + private int Method(int i) => i; + + public int Foo() + { + return Method(true?1:2); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithMethodWithMultipleArgumentsWhereSingleDifferentGetsArgsApplied() + { + var source = @" + private int Method(int i, string t) => i; + + public int Foo() + { + if (true) + return Method(1, ""hello""); + else + return Method(2, ""hello""); + }".WrapInCSharpClass(); + var fixtest = @" + private int Method(int i, string t) => i; + + public int Foo() + { + return Method(true?1:2, ""hello""); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithMethodWithMultipleArgumentsWhereMultipleDifferentGetsArgsNotApplied() + { + var source = @" + private int Method(int i, string t) => i; + + public int Foo() + { + if (true) + return Method(1, ""hello1""); + else + return Method(2, ""hello2""); + }".WrapInCSharpClass(); + var fixtest = @" + private int Method(int i, string t) => i; + + public int Foo() + { + return true?Method(1,""hello1""):Method(2, ""hello2""); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithMethodArgumentsGetCastedWhenGetsArgsApplied() + { + var source = @" + class Base { } + class A : Base { } + class B : Base { } + + private int Method(Base b, string t) => 1; + + public int Foo() + { + if (true) + return Method(new A(), ""hello""); + else + return Method(new B(), ""hello""); + }".WrapInCSharpClass(); + var fixtest = @" + class Base { } + class A : Base { } + class B : Base { } + + private int Method(Base b, string t) => 1; + + public int Foo() + { + return Method(true?(Base)new A():new B(),""hello""); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithPrefixedMethodGetsArgsApplied() + { + var source = @" + private int Method(int a) => a; + + public int Foo() + { + if (true) + return this.Method(1); + else + return this.Method(2); + }".WrapInCSharpClass(); + var fixtest = @" + private int Method(int a) => a; + + public int Foo() + { + return this.Method(true?1:2); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithMethodOfPropertyGetsArgsApplied() + { + var source = @" + class A { + private int Method(int a) => a; + } + + public int Foo() + { + var a=new A(); + if (true) + return a.Method(1); + else + return a.Method(2); + }".WrapInCSharpClass(); + var fixtest = @" + class A { + private int Method(int a) => a; + } + + public int Foo() + { + var a=new A(); + return a.Method(true?1:2); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithMethodOfDifferentPropertyGetsArgsNotApplied() + { + var source = @" + class A { + public int Method(int a) => a; + } + A Prop1 { get { return new A(); } } + A Prop2 { get { return new A(); } } + + public int Foo() + { + if (true) + return this.Prop1.Method(1); + else + return this.Prop2.Method(2); + }".WrapInCSharpClass(); + var fixtest = @" + class A { + public int Method(int a) => a; + } + A Prop1 { get { return new A(); } } + A Prop2 { get { return new A(); } } + + public int Foo() + { + return true?this.Prop1.Method(1):this.Prop2.Method(2); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithMethodOfSameOverloadGetsArgsApplied() + { + var source = @" + int Method(int a)=>a; + int Method(string a)=>1; + + public int Foo() + { + if (true) + return Method(1); + else + return Method(2); + }".WrapInCSharpClass(); + var fixtest = @" + int Method(int a)=>a; + int Method(string a)=>1; + + public int Foo() + { + return Method(true?1:2); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithMethodOfDifferentOverloadGetsArgsNotApplied() + { + var source = @" + int Method(int a)=>a; + int Method(string a)=>1; + + public int Foo() + { + if (true) + return Method(1); + else + return Method(""2""); + }".WrapInCSharpClass(); + var fixtest = @" + int Method(int a)=>a; + int Method(string a)=>1; + + public int Foo() + { + return true?Method(1):Method(""2""); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithMethodOfDifferentOverloadButCastingPossibleGetsArgsNotApplied() + { + var source = @" + public class A { } + public class B:A { } + void Method(A a) { }; + void Method(B b) { }; + + public int Foo() + { + if (true) + return Method(new A()); + else + return Method(new B()); + }".WrapInCSharpClass(); + var fixtest = @" + public class A { } + public class B:A { } + void Method(A a) { }; + void Method(B b) { }; + + public int Foo() + { + return true?Method(new A()):Method(new B()); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithMethodNestedInMemberAccessGetsArgsNotApplied() + { + var source = @" + class A { + public int Prop { get; } + } + + A GetA(int i) => new A(); + + public int Foo() + { + if (true) + return GetA(1).Prop; + else + return GetA(2).Prop; + }".WrapInCSharpClass(); + var fixtest = @" + class A { + public int Prop { get; } + } + + A GetA(int i) => new A(); + + public int Foo() + { + return true?GetA(1).Prop:GetA(2).Prop; + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithMethodParamOverloadAndNumerOfArgsAreEqualGetsApplied() + { + var source = @" + private int M(params int[] args) { } + + public int Foo() + { + if (true) + return M(1,1); + else + return M(1,2); + }".WrapInCSharpClass(); + var fixtest = @" + private int M(params int[] args) { } + + public int Foo() + { + return M(1,true?1:2); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithMethodParamOverloadAndNumerOfArgsAreDifferentGetsNotApplied() + { + var source = @" + private int M(params int[] args) { } + + public int Foo() + { + if (true) + return M(1,1); + else + return M(1,2,3); + }".WrapInCSharpClass(); + var fixtest = @" + private int M(params int[] args) { } + + public int Foo() + { + return true?M(1,1):M(1,2,3); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithMethodOfDynamicObjGetsArgsNotApplied() + { + // Calls on dynamic objects get dispatched during runtime. + // Therefore the semantic would be changed if we apply to arguments + // and casting is involved: + // d.M(new A()) else d.M(new B()) -> d.M(cond?(Base)new A():new B()); + // is not the same as cond?d.M(new A()): d.M(new B()) on dynamic objects. + var source = @" + public class Base {} + public class A: Base {} + public class B: Base {} + + public int Foo() + { + dynamic d = new object(); + if (true) + return d.M(new A()); + else + return d.M(new B()); + }".WrapInCSharpClass(); + var fixtest = @" + public class Base {} + public class A: Base {} + public class B: Base {} + + public int Foo() + { + dynamic d = new object(); + return true?d.M(new A()):d.M(new B()); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithMethodOfDynamicObjGetsArgsNeverApplied() + { + // arguments on dynamic method calls are never applied even if it would be save. + // see comments above for why dynamic is dangerous. + var source = @" + public int Foo() + { + dynamic d = new object(); + if (true) + return d.M(1,1); + else + return d.M(1,2); + }".WrapInCSharpClass(); + var fixtest = @" + public int Foo() + { + dynamic d = new object(); + return true?d.M(1,1):d.M(1,2); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithConstructorGetsArgsApplied() + { + var source = @" + public System.Collections.Generic.List Foo() + { + if (true) + return new System.Collections.Generic.List(1); + else + return new System.Collections.Generic.List(2); + }".WrapInCSharpClass(); + var fixtest = @" + public System.Collections.Generic.List Foo() + { + return new System.Collections.Generic.List(true?1:2); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithConstructorWithIdenticalInitializerGetsArgsApplied() + { + var source = @" + public new System.Collections.Generic.List Foo() + { + if (true) + return new System.Collections.Generic.List(1) { 1 }; + else + return new System.Collections.Generic.List(2) { 1 }; + }".WrapInCSharpClass(); + var fixtest = @" + public new System.Collections.Generic.List Foo() + { + return new System.Collections.Generic.List(true?1:2) { 1 }; + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithConstructorWithDifferentInitializerGetsArgsNotApplied() + { + var source = @" + public System.Collections.Generic.List Foo() + { + if (true) + return new System.Collections.Generic.List(1) { 1 }; + else + return new System.Collections.Generic.List(2) { 1, 2 }; + }".WrapInCSharpClass(); + var fixtest = @" + public System.Collections.Generic.List Foo() + { + return true?new System.Collections.Generic.List(1) { 1 } : new System.Collections.Generic.List(2) { 1, 2 }; + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithConstructorWithDifferentOverloadsGetArgsNotApplied() + { + var source = @" + public class A + { + public A(int i) { } + public A(string s) { } + } + public A Foo() + { + if (true) + return new A(1); + else + return new A(""1""); + }".WrapInCSharpClass(); + var fixtest = @" + public class A + { + public A(int i) { } + public A(string s) { } + } + public A Foo() + { + return true?new A(1):new A(""1""); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithConstructorOfDifferentObjectsGetArgsNotApplied() + { + var source = @" + public class A + { + public A(int i) { } + } + public class B + { + public B(int i) { } + } + + public Object Foo() + { + if (true) + return new A(1); + else + return new B(2); + }".WrapInCSharpClass(); + var fixtest = @" + public class A + { + public A(int i) { } + } + public class B + { + public B(int i) { } + } + + public Object Foo() + { + return true?(object)new A(1):new B(2); + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task WhenReturnTypeIsAnInterfaceAFittingCastIsInserted() + { + var source = @" + IComparable GetComparable() + { + if (true) + return 1; + else + return ""1""; + }".WrapInCSharpClass(); + var fixtest = @" + IComparable GetComparable() + { + return true?(IComparable)1 : ""1""; + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixWhenReturningWithReturnTypeIsExplicitConvertable() + { + var source = @" + double GetNumber() + { + if (true) + return 1; + else + return 1.1; + }".WrapInCSharpClass(); + var fixtest = @" + double GetNumber() + { + return true?(double)1:1.1; + }".WrapInCSharpClass(); + await VerifyCSharpFixAsync(source, fixtest); } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Style/UnnecessaryParenthesisTests.cs b/test/CSharp/CodeCracker.Test/Style/UnnecessaryParenthesisTests.cs index 5e98fbf54..31462d393 100644 --- a/test/CSharp/CodeCracker.Test/Style/UnnecessaryParenthesisTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/UnnecessaryParenthesisTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -11,13 +12,9 @@ public class UnnecessaryParenthesisTests : CodeFixVerifier + { + private static DiagnosticResult CreateUnnecessaryToStringInStringConcatenationDiagnosticResult(int expectedRow, int expectedColumn) + { + return new DiagnosticResult(DiagnosticId.UnnecessaryToStringInStringConcatenation.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(expectedRow, expectedColumn) + .WithMessage("Unnecessary '.ToString()' call in string concatenation."); + } + + [Fact] + public async Task InstantiatingAnObjectAndCallToStringInsideAStringConcatenationShouldGenerateDiagnosticResult() + { + const string source = @"var foo = ""a"" + new object().ToString();"; + + var expected = CreateUnnecessaryToStringInStringConcatenationDiagnosticResult(1, 29); + + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task InstantiatingAnStringBuilderAndCallToStringInsideAStringConcatenationShouldGenerateDiagnosticResult() + { + var source = @"var foo = ""a"" + new System.Text.StringBuilder().ToString();".WrapInCSharpMethod(); + + var expected = CreateUnnecessaryToStringInStringConcatenationDiagnosticResult(10, 60); + + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task CallToStringForAnInstantiatedObjectInsideAStringConcatenationShouldGenerateDiagnosticResult() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + class AuxClass + { + public override string ToString() + { + return ""Test""; + } + } + + class TypeName + { + public void Foo() + { + var auxClass = new AuxClass(); + + var bar = ""a"" + new AuxClass().ToString(); + var foo = ""a"" + auxClass.ToString(); + var far = ""a"" + new AuxClass().ToString() + auxClass.ToString() + new int().ToString(""C""); + } + } + }"; + + var expected1 = CreateUnnecessaryToStringInStringConcatenationDiagnosticResult(20, 47); + var expected2 = CreateUnnecessaryToStringInStringConcatenationDiagnosticResult(21, 41); + var expected3 = CreateUnnecessaryToStringInStringConcatenationDiagnosticResult(22, 47); + var expected4 = CreateUnnecessaryToStringInStringConcatenationDiagnosticResult(22, 69); + + var expected = new DiagnosticResult[] { expected1, expected2, expected3, expected4 }; + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async Task CallToStringOnStringExpressionsShouldGenerateDiagnosticResult() + { + const string test = @"var t1 = (true ? ""1"" : ""2"") + new object().ToString();"; + + var expected = CreateUnnecessaryToStringInStringConcatenationDiagnosticResult(1, 43); + + await VerifyCSharpDiagnosticAsync(test, expected); + } + + [Fact] + public async Task CallToStringFollowedByACallToAStringMethodShouldNotGenerateDiagnosticResult() + { + const string source = @"var salary = ""salary: "" + 1000.ToString().Trim();"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task CallToLambdaNamedToStringShouldNotGenerateDiagnosticResult() + { + var source = @" + Func ToString = () => ""Dummy""; + var t = 1 + ToString(); + ".WrapInCSharpMethod(); + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task CallToStringInsideAStringConcatenationWithAFormatParameterShouldNotGenerateDiagnosticResult() + { + const string source = @"var salary = ""salary: "" + 1000.ToString(""C"");"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + + [Fact] + public async Task CallToStringOutsideAStringConcatenationWithoutParameterShouldNotGenerateDiagnosticResult() + { + const string source = @"var value = 1000.ToString();"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task AStringConcatinationShouldNotBeRemovedIfOtherOverloadsTakePrecedence_NumericAddition_RightSide() + { + const string source = @"var value = 1 + 2.ToString();"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task AStringConcatinationShouldNotBeRemovedIfOtherOverloadsTakePrecedence_NumericAddition_LeftSide() + { + const string source = @"var value = 2.ToString() + 1;"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task AStringConcatinationShouldNotBeRemovedIfOtherOverloadsTakePrecedence_NumericAddition_WithExpression() + { + const string source = @"var value = (1 + 1) + 2.ToString();"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task AStringConcatinationShouldNotBeRemovedIfOtherOverloadsTakePrecedence_NumericAddition_Double() + { + const string source = @"var value = (true ? 1.1 : 0.99) + 2.ToString();"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task AStringConcatinationShouldNotBeRemovedIfOtherOverloadsTakePrecedence_NumericAddition_DateTime() + { + const string source = @"var value = new System.DateTime(2000, 1, 1) + 2.ToString();"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task AStringConcatinationShouldNotBeRemovedIfOtherOverloadsTakePrecedence_CompilerGeneratedEnumOperator() + { + const string source = @"var value = System.AttributeTargets.Assembly + System.AttributeTargets.Module.ToString();"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task AStringConcatinationShouldNotBeRemovedIfOtherOverloadsTakePrecedence_UnderlyingTypeDoesntHaveAddOperatorOverload() + { + const string source = @"var value = new System.Random() + new System.Random().ToString();"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task AStringConcatinationShouldNotBeRemovedIfOtherOverloadsTakePrecedence_UserDefinedOperator() + { + const string source = @" + namespace A + { + public class C1 + { + public static string operator +(C1 c, object o) => ""Dummy""; + } + + public class C2 + { + public void M() + { + var t = new C1().ToString() + ""a""; + } + } + } +"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task AStringConcatinationShouldNotBeRemovedIfOtherOverloadsTakePrecedence_DelegateCombination() + { + var source = @" + var ea1 = new System.EventHandler((o, e) => { }); + var ea2 = new System.EventHandler((o, e) => { }); + var t = ea1 + ea2.ToString(); + ".WrapInCSharpMethod(); + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task AStringConcatinationShouldNotBeRemovedIfTheTypesOfTheOperationAreNotResovable_ToStringReceiver() + { + const string source = @"var t = new UndefinedType().ToString() + ""a"""; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task AStringConcatinationShouldNotBeRemovedIfTheTypesOfTheOperationAreNotResovable_OtherSide() + { + const string source = @"var t = 1.ToString() + new UndefinedType();"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task AStringConcatinationShouldNotBeRemovedIfTheTypesOfTheOperationAreNotResovable_SyntaxError() + { + const string source = @"var t = new System.Random().ToString() + new ThisIsAnSyntaxError"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task FixReplacesToStringCallInAStringConcatenationWithAVariable() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public async Task Foo() + { + var text = ""def""; + var a = ""abc"" + text.ToString(); + Console.Log(a); + } + } + }"; + + const string expected = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public async Task Foo() + { + var text = ""def""; + var a = ""abc"" + text; + Console.Log(a); + } + } + }"; + await VerifyCSharpFixAsync(test, expected); + } + + + [Fact] + public async Task FixReplacesToStringCallInAStringConcatenation() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public async Task Foo() + { + var a = ""abc"" + ""def"".ToString(); + Console.Log(a); + } + } + }"; + + const string expected = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public async Task Foo() + { + var a = ""abc"" + ""def""; + Console.Log(a); + } + } + }"; + await VerifyCSharpFixAsync(test, expected); + } + + [Fact] + public async Task FixReplacesToStringCallInAStringConcatenationWithAnObject() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public async Task Foo() + { + var foo = ""a"" + new object().ToString(); + Console.Log(foo); + } + } + }"; + + const string expected = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public async Task Foo() + { + var foo = ""a"" + new object(); + Console.Log(foo); + } + } + }"; + await VerifyCSharpFixAsync(test, expected); + } + } +} \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Style/UseEmptyStringTest.cs b/test/CSharp/CodeCracker.Test/Style/UseEmptyStringTest.cs index ba6433a12..84eb26fdf 100644 --- a/test/CSharp/CodeCracker.Test/Style/UseEmptyStringTest.cs +++ b/test/CSharp/CodeCracker.Test/Style/UseEmptyStringTest.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -193,8 +194,8 @@ public async Task FixAllInSolutionChangeMethodToStringEmpty() public async Task TwoEmptyStringsGenerateTwoDiagnostics() { var test = "var s = string.Empty + string.Empty;".WrapInCSharpMethod(); - var expected1 = CreateEmptyStringDiagnosticResult(10, 25); - var expected2 = CreateEmptyStringDiagnosticResult(10, 40); + var expected1 = CreateEmptyStringDiagnosticResult(10, 21); + var expected2 = CreateEmptyStringDiagnosticResult(10, 36); await VerifyCSharpDiagnosticAsync(test, expected1, expected2); } @@ -208,17 +209,9 @@ public async Task IgnoreAttribute() private static DiagnosticResult CreateEmptyStringDiagnosticResult(int expectedRow, int expectedColumn) { - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseEmptyString.ToDiagnosticId(), - Message = "Use \"\" instead of 'string.Empty'", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", expectedRow, expectedColumn) } - }; - - var t = string.Empty; - - return expected; + return new DiagnosticResult(DiagnosticId.UseEmptyString.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(expectedRow, expectedColumn) + .WithMessage("Use \"\" instead of 'string.Empty'"); } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Style/UseStringEmptyTests.cs b/test/CSharp/CodeCracker.Test/Style/UseStringEmptyTests.cs index e59b7560e..9e492b404 100644 --- a/test/CSharp/CodeCracker.Test/Style/UseStringEmptyTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/UseStringEmptyTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -43,16 +44,31 @@ public void Foo() } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseStringEmpty.ToDiagnosticId(), - Message = "Use 'String.Empty' instead of \"\"", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.UseStringEmpty.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(10, 25) + .WithMessage("Use 'String.Empty' instead of \"\""); await VerifyCSharpDiagnosticAsync(test, expected); } + [Fact] + public async Task WhenHasStringInParameterShouldNotRaiseDiagnostic() + { + const string test = @" + using System; + + namespace ConsoleApplication1 + { + class TypeName + { + public void Foo(string name = "") + { + } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(test); + + } + [Fact] public async Task MethodNotUsingStringEmpty() { @@ -72,13 +88,9 @@ public void test() } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseStringEmpty.ToDiagnosticId(), - Message = "Use 'String.Empty' instead of \"\"", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 12, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.UseStringEmpty.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(12, 21) + .WithMessage("Use 'String.Empty' instead of \"\""); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -164,30 +176,22 @@ public async Task FixAllInDocChangeMethodToStringEmpty() public async Task FixAllInSolutionChangeMethodToStringEmpty() { var test1 = @"var s = """" + """";".WrapInCSharpMethod(); - var test2 = @"var s = """" + """";".WrapInCSharpMethod(typeName:"AnotherType"); + var test2 = @"var s = """" + """";".WrapInCSharpMethod(typeName: "AnotherType"); var expected1 = @"var s = string.Empty + string.Empty;".WrapInCSharpMethod(); - var expected2 = @"var s = string.Empty + string.Empty;".WrapInCSharpMethod(typeName:"AnotherType"); - await VerifyCSharpFixAllAsync(new[] { test1, test2 },new[] { expected1, expected2 }); + var expected2 = @"var s = string.Empty + string.Empty;".WrapInCSharpMethod(typeName: "AnotherType"); + await VerifyCSharpFixAllAsync(new[] { test1, test2 }, new[] { expected1, expected2 }); } [Fact] public async Task TwoEmptyStringsGenerateTwoDiagnostics() { var test = @"var s = """" + """";".WrapInCSharpMethod(); - var expected1 = new DiagnosticResult - { - Id = DiagnosticId.UseStringEmpty.ToDiagnosticId(), - Message = "Use 'String.Empty' instead of \"\"", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 25) } - }; - var expected2 = new DiagnosticResult - { - Id = DiagnosticId.UseStringEmpty.ToDiagnosticId(), - Message = "Use 'String.Empty' instead of \"\"", - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 30) } - }; + var expected1 = new DiagnosticResult(DiagnosticId.UseStringEmpty.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(10, 21) + .WithMessage("Use 'String.Empty' instead of \"\""); + var expected2 = new DiagnosticResult(DiagnosticId.UseStringEmpty.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(10, 26) + .WithMessage("Use 'String.Empty' instead of \"\""); await VerifyCSharpDiagnosticAsync(test, expected1, expected2); } diff --git a/test/CSharp/CodeCracker.Test/Usage/AbstractClassShouldNotHavePublicCtorTests.cs b/test/CSharp/CodeCracker.Test/Usage/AbstractClassShouldNotHavePublicCtorTests.cs index 6d804ca61..6bcc08346 100644 --- a/test/CSharp/CodeCracker.Test/Usage/AbstractClassShouldNotHavePublicCtorTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/AbstractClassShouldNotHavePublicCtorTests.cs @@ -1,6 +1,7 @@ -using System.Threading.Tasks; -using CodeCracker.CSharp.Usage; +using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using System.Threading.Tasks; using Xunit; namespace CodeCracker.Test.CSharp.Usage @@ -18,13 +19,9 @@ abstract class Foo public Foo() { /* .. */ } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.AbstractClassShouldNotHavePublicCtors.ToDiagnosticId(), - Message = "Constructor should not be public.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.AbstractClassShouldNotHavePublicCtors.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(4, 17) + .WithMessage("Constructor should not be public."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -66,6 +63,26 @@ private Foo() { /* .. */ } await VerifyCSharpHasNoDiagnosticsAsync(test); } + [Fact] + public async Task IgnoresCtorOfStructNestedInAbstractClasses() + { + const string test = @" + public abstract class C + { + public struct S + { + private int x; + + public S(int x) + { + this.x = x; + } + } + }"; + + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + [Fact] public async Task FixReplacesPublicWithProtectedModifierInAbstractClasses() { diff --git a/test/CSharp/CodeCracker.Test/Usage/ArgumentExceptionTests.cs b/test/CSharp/CodeCracker.Test/Usage/ArgumentExceptionTests.cs index 5c84ff9e5..8248b1cc6 100644 --- a/test/CSharp/CodeCracker.Test/Usage/ArgumentExceptionTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/ArgumentExceptionTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -16,13 +17,9 @@ public async Task Foo(int a, int b) throw new ArgumentException(""message"", ""c""); }"); - var expected = new DiagnosticResult - { - Id = DiagnosticId.ArgumentException.ToDiagnosticId(), - Message = "Type argument 'c' is not in the argument list.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 56) } - }; + var expected = new DiagnosticResult(DiagnosticId.ArgumentException.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(11, 56) + .WithMessage("Type argument 'c' is not in the argument list."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -36,13 +33,9 @@ public TypeName(int a, int b) throw new ArgumentException(""message"", ""c""); }"); - var expected = new DiagnosticResult - { - Id = DiagnosticId.ArgumentException.ToDiagnosticId(), - Message = "Type argument 'c' is not in the argument list.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 56) } - }; + var expected = new DiagnosticResult(DiagnosticId.ArgumentException.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(11, 56) + .WithMessage("Type argument 'c' is not in the argument list."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -145,13 +138,9 @@ public string RejectsEverythingProperty } "); - var expected = new DiagnosticResult - { - Id = DiagnosticId.ArgumentException.ToDiagnosticId(), - Message = "Type argument 'c' is not in the argument list.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 12, 62) } - }; + var expected = new DiagnosticResult(DiagnosticId.ArgumentException.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(12, 62) + .WithMessage("Type argument 'c' is not in the argument list."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -187,13 +176,9 @@ public async Task WhenThrowingArgumentExceptionInGetPropertyWithIndexersArgument } "); - var expected = new DiagnosticResult - { - Id = DiagnosticId.ArgumentException.ToDiagnosticId(), - Message = "Type argument 'c' is not in the argument list.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 62) } - }; + var expected = new DiagnosticResult(DiagnosticId.ArgumentException.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(11, 62) + .WithMessage("Type argument 'c' is not in the argument list."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -225,13 +210,9 @@ public async Task WhenThrowingArgumentExceptionInLambdaArgumentNameShouldBeInPar Action action = (p) => { throw new ArgumentException(""message"", ""paramName""); }; "); - var expected = new DiagnosticResult - { - Id = DiagnosticId.ArgumentException.ToDiagnosticId(), - Message = "Type argument 'paramName' is not in the argument list.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 9, 82) } - }; + var expected = new DiagnosticResult(DiagnosticId.ArgumentException.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(9, 82) + .WithMessage("Type argument 'paramName' is not in the argument list."); await VerifyCSharpDiagnosticAsync(test, expected); } diff --git a/test/CSharp/CodeCracker.Test/Usage/CallExtensionMethodAsExtensionTests.cs b/test/CSharp/CodeCracker.Test/Usage/CallExtensionMethodAsExtensionTests.cs index 5fdcc0c5f..07d98a122 100644 --- a/test/CSharp/CodeCracker.Test/Usage/CallExtensionMethodAsExtensionTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/CallExtensionMethodAsExtensionTests.cs @@ -1,5 +1,7 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -8,13 +10,32 @@ namespace CodeCracker.Test.CSharp.Usage public class CallExtensionMethodAsExtensionTests : CodeFixVerifier { + [Fact] + public async Task IgnoreWhenMissingArguments() + { + const string source = @" +using System.Linq; +class Foo +{ + public static void N() + { + Exts.M(); + } +} +static class Exts +{ + public static void M(this string s) => s.ToString(); +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + [Fact] public async Task WhenCallExtensionMethodAsExtensionHasNoDiagnostics() { const string source = @" using System.Linq; namespace ConsoleApplication1 - { + { public class Foo { public void Bar() @@ -28,12 +49,12 @@ public void Bar() } [Fact] - public async Task WhenCallExtensionMethodAsStaticMenthodTriggerAFix() + public async Task WhenCallExtensionMethodAsStaticMethodTriggerAFix() { const string source = @" using System.Linq; namespace ConsoleApplication1 - { + { public class Foo { public void Bar() @@ -44,23 +65,62 @@ public void Bar() } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.CallExtensionMethodAsExtension.ToDiagnosticId(), - Message = "Do not call 'Any' method of class 'Enumerable' as a static method", - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 33) } - }; + var expected = new DiagnosticResult(DiagnosticId.CallExtensionMethodAsExtension.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 33) + .WithMessage("Do not call 'Any' method of class 'Enumerable' as a static method"); await VerifyCSharpDiagnosticAsync(source, expected); } + [Fact] + public async Task WhenCallExtensionMethodAsStaticMethodTriggerAFixWithCSharp5() + { + const string source = @" + using System.Linq; + namespace ConsoleApplication1 + { + public class Foo + { + public void Bar() + { + var source = new int[] { 1, 2, 3 }; + Enumerable.Any(source, x => x > 1); + } + } + }"; + var expected = new DiagnosticResult(DiagnosticId.CallExtensionMethodAsExtension.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 33) + .WithMessage("Do not call 'Any' method of class 'Enumerable' as a static method"); + await VerifyCSharpDiagnosticAsync(source, expected, LanguageVersion.CSharp5); + } + + [Fact] + public async Task CreatesDiagnosticWhenExtensionClassInSameTree() + { + const string source = @" +class Foo +{ + public static void Bar() + { + C.M(""""); + } +} +public static class C +{ + public static string M(this string s) => s; +}"; + var expected = new DiagnosticResult(DiagnosticId.CallExtensionMethodAsExtension.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 9) + .WithMessage("Do not call 'M' method of class 'C' as a static method"); + await VerifyCSharpDiagnosticAsync(source, expected, LanguageVersion.CSharp5); + } + [Fact] public async Task WhenCallExtensionMethodWithFullNamespaceAsStaticMenthodTriggerAFix() { const string source = @" namespace ConsoleApplication1 - { + { public class Foo { public void Bar() @@ -71,13 +131,9 @@ public void Bar() } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.CallExtensionMethodAsExtension.ToDiagnosticId(), - Message = "Do not call 'Any' method of class 'Enumerable' as a static method", - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 9, 33) } - }; + var expected = new DiagnosticResult(DiagnosticId.CallExtensionMethodAsExtension.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(9, 33) + .WithMessage("Do not call 'Any' method of class 'Enumerable' as a static method"); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -88,7 +144,7 @@ public async Task WhenCallExtensionMethodAsStaticMenthodShouldFix() const string source = @" using System.Linq; namespace ConsoleApplication1 - { + { public class Foo { public void Bar() @@ -102,7 +158,7 @@ public void Bar() const string expected = @" using System.Linq; namespace ConsoleApplication1 - { + { public class Foo { public void Bar() @@ -116,6 +172,40 @@ public void Bar() await VerifyCSharpFixAsync(source, expected); } + [Fact] + public async Task WhenCallExtensionMethodAsStaticMethodShouldFixWithReturnStatement() + { + const string source = @" + using System.Linq; + namespace ConsoleApplication1 + { + public class Foo + { + public bool Bar() + { + var source = new int[] { 1, 2, 3 }; + return Enumerable.Any(source, x => x > 1); + } + } + }"; + + const string expected = @" + using System.Linq; + namespace ConsoleApplication1 + { + public class Foo + { + public bool Bar() + { + var source = new int[] { 1, 2, 3 }; + return source.Any(x => x > 1); + } + } + }"; + + await VerifyCSharpFixAsync(source, expected); + } + [Fact] public async Task WhenCallExtensionMethodWithFullNamespaceAndNotImportedShouldImport() { @@ -125,7 +215,7 @@ public async Task WhenCallExtensionMethodWithFullNamespaceAndNotImportedShouldIm using System.Collections.Generic; namespace ConsoleApplication1 - { + { public class Foo { public void Bar() @@ -143,7 +233,7 @@ public void Bar() using System.Linq; namespace ConsoleApplication1 - { + { public class Foo { public void Bar() @@ -179,7 +269,7 @@ public void Bar() using System.Linq; namespace ConsoleApplication1 - { + { public class Foo { public void Bar() @@ -253,13 +343,31 @@ public void Bar() } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.CallExtensionMethodAsExtension.ToDiagnosticId(), - Message = "Do not call 'Any' method of class 'Enumerable' as a static method", - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 37) } - }; + var expected = new DiagnosticResult(DiagnosticId.CallExtensionMethodAsExtension.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(11, 37) + .WithMessage("Do not call 'Any' method of class 'Enumerable' as a static method"); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + + [Fact] + public async Task WhenCallExtensionMethodWithoutInvocationStatement() + { + const string source = @" + using System.Linq; + namespace ConsoleApplication1 + { + public class Foo + { + static int[] source = new int[] { 1, 2, 3 }; + public static void Bar() => Enumerable.Any(source); + } + }"; + + var expected = new DiagnosticResult(DiagnosticId.CallExtensionMethodAsExtension.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(8, 51) + .WithMessage("Do not call 'Any' method of class 'Enumerable' as a static method"); + await VerifyCSharpDiagnosticAsync(source, expected); } } diff --git a/test/CSharp/CodeCracker.Test/Usage/DisposableFieldNotDisposedTests.cs b/test/CSharp/CodeCracker.Test/Usage/DisposableFieldNotDisposedTests.cs index 200e0862a..2337557a8 100644 --- a/test/CSharp/CodeCracker.Test/Usage/DisposableFieldNotDisposedTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/DisposableFieldNotDisposedTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -7,7 +8,6 @@ namespace CodeCracker.Test.CSharp.Usage { public class DisposableFieldNotDisposedTests : CodeFixVerifier { - [Fact] public async Task FieldNotDisposableDoesNotCreateDiagnostic() { @@ -22,6 +22,53 @@ class TypeName await VerifyCSharpHasNoDiagnosticsAsync(source); } + [Fact] + public async Task WhenUsingTheDisposablePatternItDoesNotCreateDiagnostic() + { + const string source = @" +using System; +using System.IO; +public class A : IDisposable +{ + private MemoryStream disposableField = new MemoryStream(); + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + private void Dispose(bool disposing) + { + if (disposing) + if (disposableField != null) + disposableField.Dispose(); + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task WhenUsingTheDisposablePatternWithNullPropagationItDoesNotCreateDiagnostic() + { + const string source = @" +using System; +using System.IO; +public class A : IDisposable +{ + private MemoryStream disposableField = new MemoryStream(); + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + private void Dispose(bool disposing) + { + if (disposing) + disposableField?.Dispose(); + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + [Fact] public async Task WhenAFieldThatImplementsIDisposableIsAssignedThroughAMethodCallCreatesDiagnostic() { @@ -39,13 +86,9 @@ class D : IDisposable public void Dispose() { } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), - Message = string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 23) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(7, 23) + .WithMessage(string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -85,20 +128,12 @@ class D : IDisposable public void Dispose() { } } }"; - var expected1 = new DiagnosticResult - { - Id = DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), - Message = string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field1"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 23) } - }; - var expected2 = new DiagnosticResult - { - Id = DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), - Message = string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field2"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 41) } - }; + var expected1 = new DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(7, 23) + .WithMessage(string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field1")); + var expected2 = new DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(7, 41) + .WithMessage(string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field2")); await VerifyCSharpDiagnosticAsync(source, expected1, expected2); } @@ -123,13 +158,9 @@ class D : IDisposable public void Dispose() { } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), - Message = string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 23) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(7, 23) + .WithMessage(string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -153,13 +184,9 @@ class D : IDisposable public void Dispose() { } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), - Message = string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 23) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(7, 23) + .WithMessage(string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -187,6 +214,30 @@ public void Dispose() { } await VerifyCSharpHasNoDiagnosticsAsync(source); } + [Fact] + public async Task WhenAFieldThatImplementsIDisposableIsDisposedWithThisDoesNotCreateDiagnostic() + { + const string source = @" + using System; + namespace ConsoleApplication1 + { + class TypeName : IDisposable + { + private D field = D.Create(); + public void Dispose() + { + this.field.Dispose(); + } + } + class D : IDisposable + { + public static D Create() => new D(); + public void Dispose() { } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + [Fact] public async Task WhenAFieldThatImplementsIDisposableIsDisposedThroughImplicitImplementationDoesNotCreateDiagnostic() { @@ -227,13 +278,9 @@ struct D : IDisposable public void Dispose() { } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), - Message = string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 23) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(7, 23) + .WithMessage(string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -258,13 +305,9 @@ class D : IDisposable public void Dispose() { } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), - Message = string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 23) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(8, 23) + .WithMessage(string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -290,13 +333,9 @@ public void Dispose() { } public void Dispose(bool arg) { } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), - Message = string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 23) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(7, 23) + .WithMessage(string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -322,13 +361,9 @@ class D : IDisposable public void Dispose() { } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), - Message = string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 23) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(7, 23) + .WithMessage(string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -348,13 +383,9 @@ class D : IDisposable public void Dispose() { } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), - Message = string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 23) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(7, 23) + .WithMessage(string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -375,13 +406,9 @@ class D : IDisposable public void Dispose() { } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), - Message = string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 33) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(7, 33) + .WithMessage(string.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -738,5 +765,36 @@ public void Dispose() { } }"; await VerifyCSharpHasNoDiagnosticsAsync(source); } + + [Fact] + public async Task AlreadyDisposedDoesNotCreateDiagnostic() + { + const string source = @" +class TypeName : System.IDisposable +{ + private D field = new D(); + public void Dispose() => field.Dispose(); +} +class D : System.IDisposable +{ + public void Dispose() { } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task StaticFieldDoesNotCreateDiagnostic() + { + const string source = @" +static class TypeName +{ + private static D d = new D(); +} +class D : System.IDisposable +{ + public void Dispose() { } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Usage/DisposableVariableNotDisposedTests.cs b/test/CSharp/CodeCracker.Test/Usage/DisposableVariableNotDisposedTests.cs index 51093f22a..590f108de 100644 --- a/test/CSharp/CodeCracker.Test/Usage/DisposableVariableNotDisposedTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/DisposableVariableNotDisposedTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -7,6 +8,108 @@ namespace CodeCracker.Test.CSharp.Usage { public class DisposableVariableNotDisposedTests : CodeFixVerifier { + + [Fact] + public async Task FixAssignmentWithTernary() + { + const string source = @" +public class CSharpClass +{ + struct ConsoleColorContext : System.IDisposable + { + public void Dispose() + { + throw new NotImplementedException(); + } + } + + void Foo() + { + ConsoleColorContext? color = null; + var x = color.HasValue ? new ConsoleColorContext(color.Value) : null; + } +} +"; + const string fixtest = @" +public class CSharpClass +{ + struct ConsoleColorContext : System.IDisposable + { + public void Dispose() + { + throw new NotImplementedException(); + } + } + + void Foo() + { + ConsoleColorContext? color = null; + using(var x = color.HasValue ? new ConsoleColorContext(color.Value) : null) + { + } + } +} +"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixAssignmentWithCast() + { + var source = @"var m = (System.IDisposable)new System.IO.MemoryStream();".WrapInCSharpMethod(); + var fixtest = @"using (var m = (System.IDisposable)new System.IO.MemoryStream()) +{ +}".WrapInCSharpMethod(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixADisposableDeclarationWithoutDisposeWithParenthese() + { + var source = @"var m = ((new System.IO.MemoryStream()));".WrapInCSharpMethod(); + var fixtest = @"using (var m = ((new System.IO.MemoryStream()))) +{ +}".WrapInCSharpMethod(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task IgnoreWhenDisposedWithElvisOperator() + { + var source = @" +var m = new System.IO.MemoryStream(); +m?.Dispose(); +".WrapInCSharpMethod(); + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IgnoresDisposableObjectsCreatedDirectParentIsNotAnUsingStatement() + { + var source = "using (((new System.IO.MemoryStream()))) { }".WrapInCSharpMethod(); + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IgnoresDisposableObjectsBeingCreatedWithTernaryInUsingStatement() + { + const string source = @" +namespace CSharpNamespace +{ + public class DisposableClass : System.IDisposable { } + + public class ActualClass + { + public void Method() + { + using(true ? new DisposableClass() : null) + { } + } + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + [Fact] public async Task VariableNotCreatedDoesNotCreateDiagnostic() { @@ -21,17 +124,43 @@ public async Task VariableNotDisposableDoesNotCreateDiagnostic() await VerifyCSharpHasNoDiagnosticsAsync(source); } + [Fact] + public async Task ReturnOnExpressionBodiedMembersDoNotCreateDiagnostic() + { + var source = @"public static System.IO.MemoryStream Foo() => new System.IO.MemoryStream();".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IteratorWithDirectReturnDoesNotCreateDiagnostic() + { + var source = @" +public System.Collections.Generic.IEnumerable Foo() +{ + yield return new System.IO.MemoryStream(); +}".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IteratorWithIndirectReturnDoesNotCreateDiagnostic() + { + var source = @" +public System.Collections.Generic.IEnumerable Foo() +{ + var disposable = new System.IO.MemoryStream(); + yield return disposable; +}".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + [Fact] public async Task DisposableVariableCreatesDiagnostic() { var source = "new System.IO.MemoryStream();".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), - Message = string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(10, 13) + .WithMessage(string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -45,15 +174,10 @@ public async Task IgnoresDisposableObjectsCreatedWithUsingStatement() [Fact] public async Task DisposableVariableDeclaredWithAnotherVariableCreatesOnlyOneDiagnostic() { - var source = "System.IO.MemoryStream a, b = new System.IO.MemoryStream();".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), - Message = string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 47) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(10, 43) + .WithMessage(string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -117,6 +241,183 @@ void System.IDisposable.Dispose() { } await VerifyCSharpHasNoDiagnosticsAsync(source); } + [Fact] + public async Task IgnoredWhenPassedIntoParenthesizedLambdaExpression() + { + const string source = @" +class Container +{ + static void Foo() + { + var container = new Container(); + container.Register(() => new System.IO.MemoryStream()); + } + void Register(System.Func f) { } +} +"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IgnoredWhenPassedIntoParenthesizedLambdaExpressionWithBlock() + { + const string source = @" +class Container +{ + static void Foo() + { + var container = new Container(); + container.Register(() => { + var memoryStream = new System.IO.MemoryStream(); + return memoryStream; + }); + } + void Register(System.Func f) { } +} +"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IgnoredWhenPassedIntoSimpleLambdaExpression() + { + const string source = @" +class Container +{ + static void Foo() + { + var container = new Container(); + container.Register(i => new System.IO.MemoryStream()); + } + void Register(System.Func f) { } +} +"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IgnoredWhenPassedIntoAnonymousDelegate() + { + const string source = @" +class Container +{ + static void Foo() + { + var container = new Container(); + container.Register(delegate () { + var memoryStream = new System.IO.MemoryStream(); + return memoryStream; + }); + } + void Register(System.Func f) { } +} +"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task WhenPassedIntoParenthesizedLambdaExpressionWithoutBlockCreatesDiagnostic() + { + const string source = @" +class Container +{ + static void Foo() + { + var container = new Container(); + container.Register(() => new System.IO.MemoryStream()); + } + void Register(System.Action f) { } +} +"; + var expected = new DiagnosticResult(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(7, 34) + .WithMessage(string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream")); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task WhenPassedIntoParenthesizedLambdaExpressionCreatesDiagnostic() + { + const string source = @" +class Container +{ + static void Foo() + { + var container = new Container(); + container.Register(() => { + var memoryStream = new System.IO.MemoryStream(); + }); + } + void Register(System.Action f) { } +} +"; + var expected = new DiagnosticResult(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(8, 32) + .WithMessage(string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream")); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task WhenPassedIntoSimpleLambdaExpressionCreatesDiagnostic() + { + const string source = @" +class Container +{ + static void Foo() + { + var container = new Container(); + container.Register(i => { + var memoryStream = new System.IO.MemoryStream(); + }); + } + void Register(System.Action f) { } +} +"; + var expected = new DiagnosticResult(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(8, 32) + .WithMessage(string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream")); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task WhenPassedIntoAnonymousDelegateCreatesDiagnostic() + { + const string source = @" +class Container +{ + static void Foo() + { + var container = new Container(); + container.Register(delegate () { + var memoryStream = new System.IO.MemoryStream(); + }); + } + void Register(System.Action f) { } +} +"; + var expected = new DiagnosticResult(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(8, 32) + .WithMessage(string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream")); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task NoFixForParenthesizedLambdaExpression() + { + const string source = @" +class Container +{ + static void Foo() + { + var container = new Container(); + container.Register(() => new System.IO.MemoryStream()); + } + void Register(System.Action f) { } +} +"; + await VerifyCSharpHasNoFixAsync(source); + } + [Fact] public async Task PassedToConstructorDoesNotCreateDiagnostic() { @@ -144,13 +445,9 @@ void System.IDisposable.Dispose() { } public async Task DisposableVariablePassedAsParamCreatesDiagnostic() { var source = "string.Format(\"\", new System.IO.MemoryStream());".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), - Message = string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 35) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(10, 31) + .WithMessage(string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -159,13 +456,9 @@ public async Task DisposableVariableCallsIncorrectDisposeCreatesDiagnostic() { var source = @"var m = new System.IO.MemoryStream(); m.Dispose(true);".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), - Message = string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(10, 21) + .WithMessage(string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -187,13 +480,9 @@ void System.IDisposable.Dispose() { } public void Dispose() { } } "; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), - Message = string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "Disposable"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 33) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(6, 33) + .WithMessage(string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "Disposable")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -239,13 +528,9 @@ void System.IDisposable.Dispose() { } void IOtherDisposable.Dispose() { } } "; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), - Message = string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "Disposable"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 33) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(6, 33) + .WithMessage(string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "Disposable")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -311,10 +596,10 @@ public async Task FixADisposableDeclarationWithoutDispose() public async Task FixADisposableDeclarationWithoutDisposeWithStatementsAfter() { var source = @"var m = new System.IO.MemoryStream(); -Console.WriteLine(m);".WrapInCSharpMethod(); +m.Flush();".WrapInCSharpMethod(); var fixtest = @"using (var m = new System.IO.MemoryStream()) { - Console.WriteLine(m); + m.Flush(); }".WrapInCSharpMethod(); await VerifyCSharpFixAsync(source, fixtest); } @@ -325,14 +610,14 @@ public async Task FixADisposableDeclarationWithoutDisposeInsideBlock() var source = @"if (DateTime.Now.Second % 2 == 0) { var m = new System.IO.MemoryStream(); - Console.WriteLine(m); + m.Flush(); } Console.WriteLine(1);".WrapInCSharpMethod(); var fixtest = @"if (DateTime.Now.Second % 2 == 0) { using (var m = new System.IO.MemoryStream()) { - Console.WriteLine(m); + m.Flush(); } } Console.WriteLine(1);".WrapInCSharpMethod(); @@ -398,11 +683,11 @@ public async Task FixAssignmentWithStatementsAfter() { var source = @"System.IO.MemoryStream m; m = new System.IO.MemoryStream(); -Console.WriteLine(m);".WrapInCSharpMethod(); +m.Flush();".WrapInCSharpMethod(); var fixtest = @"System.IO.MemoryStream m; using (m = new System.IO.MemoryStream()) { - Console.WriteLine(m); + m.Flush(); }".WrapInCSharpMethod(); await VerifyCSharpFixAsync(source, fixtest); } @@ -648,7 +933,7 @@ public async Task FixAssignmentInsideBlock() { System.IO.MemoryStream m; m = new System.IO.MemoryStream(); - Console.WriteLine(m); + m.Flush(); } Console.WriteLine(1);".WrapInCSharpMethod(); var fixtest = @"if (DateTime.Now.Second % 2 == 0) @@ -656,7 +941,7 @@ public async Task FixAssignmentInsideBlock() System.IO.MemoryStream m; using (m = new System.IO.MemoryStream()) { - Console.WriteLine(m); + m.Flush(); } } Console.WriteLine(1);".WrapInCSharpMethod(); @@ -670,7 +955,7 @@ public async Task FixAssignmentInsideBlockWithDifferentScopeInDeclarationAndAssi if (DateTime.Now.Second % 2 == 0) { m = new System.IO.MemoryStream(); - Console.WriteLine(m); + m.Flush(); } Console.WriteLine(1);".WrapInCSharpMethod(); var fixtest = @"System.IO.MemoryStream m; @@ -678,7 +963,7 @@ public async Task FixAssignmentInsideBlockWithDifferentScopeInDeclarationAndAssi { using (m = new System.IO.MemoryStream()) { - Console.WriteLine(m); + m.Flush(); } } Console.WriteLine(1);".WrapInCSharpMethod(); @@ -692,7 +977,7 @@ public async Task FixAssignmentInsideBlockWithDifferentScopeInDeclarationAndAssi if (DateTime.Now.Second % 2 == 0) { m = new System.IO.MemoryStream(); - Console.WriteLine(m); + m.Flush(); } m.Flush(); Console.WriteLine(1);".WrapInCSharpMethod(); @@ -700,7 +985,7 @@ public async Task FixAssignmentInsideBlockWithDifferentScopeInDeclarationAndAssi if (DateTime.Now.Second % 2 == 0) { m = new System.IO.MemoryStream(); - Console.WriteLine(m); + m.Flush(); } m.Flush(); Console.WriteLine(1); @@ -721,7 +1006,7 @@ void Foo() if (DateTime.Now.Second % 2 == 0) { m = new Disposable(); - Console.WriteLine(m); + m.Push(); } m.Flush(); Console.WriteLine(1); @@ -731,6 +1016,7 @@ class Disposable : IDisposable { void IDisposable.Dispose() { } public void Flush() { } + public void Push() { } } "; const string fixtest = @" @@ -743,7 +1029,7 @@ void Foo() if (DateTime.Now.Second % 2 == 0) { m = new Disposable(); - Console.WriteLine(m); + m.Push(); } m.Flush(); Console.WriteLine(1); @@ -754,6 +1040,7 @@ class Disposable : IDisposable { void IDisposable.Dispose() { } public void Flush() { } + public void Push() { } } "; await VerifyCSharpFixAsync(source, fixtest); @@ -803,6 +1090,25 @@ void IDisposable.Dispose() { } await VerifyCSharpFixAsync(source, fixtest); } + [Fact] + public async Task AfterFixCommentsArePreserved() + { + + var source = @" +// comment +var mem = new System.IO.MemoryStream(); +var b = mem.ReadByte(); +".WrapInCSharpMethod(); + + var fixtest = @" +// comment +using (var mem = new System.IO.MemoryStream()) +{ + var b = mem.ReadByte(); +} +".WrapInCSharpMethod(); + await VerifyCSharpFixAsync(source, fixtest); + } [Fact] public async Task ExplicitlyDisposedObjectDoesNotCreateDiagnostic() @@ -844,7 +1150,7 @@ void Foo() if (DateTime.Now.Second % 2 == 0) { m = new Disposable(); - Console.WriteLine(m); + m.Push(); } m.Flush(); if (DateTime.Now.Second % 3 == 0) @@ -857,6 +1163,7 @@ class Disposable : IDisposable { void IDisposable.Dispose() { } public void Flush() { } + public void Push() { } } "; const string fixtest = @" @@ -869,7 +1176,7 @@ void Foo() if (DateTime.Now.Second % 2 == 0) { m = new Disposable(); - Console.WriteLine(m); + m.Push(); } m.Flush(); if (DateTime.Now.Second % 3 == 0) @@ -882,6 +1189,7 @@ class Disposable : IDisposable { void IDisposable.Dispose() { } public void Flush() { } + public void Push() { } } "; await VerifyCSharpFixAsync(source, fixtest); @@ -902,7 +1210,7 @@ void Foo() if (DateTime.Now.Second % 4 == 0) { m = new Disposable(); - Console.WriteLine(m); + m.Push(); } m.Flush(); } @@ -916,6 +1224,7 @@ class Disposable : IDisposable { void IDisposable.Dispose() { } public void Flush() { } + public void Push() { } } "; const string fixtest = @" @@ -930,7 +1239,7 @@ void Foo() if (DateTime.Now.Second % 4 == 0) { m = new Disposable(); - Console.WriteLine(m); + m.Push(); } m.Flush(); } @@ -944,6 +1253,7 @@ class Disposable : IDisposable { void IDisposable.Dispose() { } public void Flush() { } + public void Push() { } } "; await VerifyCSharpFixAsync(source, fixtest); @@ -1120,6 +1430,135 @@ public System.IDisposable Method() await VerifyCSharpHasNoDiagnosticsAsync(source); } + + [Fact] + public async Task ChainingFixesCorrectly() + { + var source = @" +new System.IO.MemoryStream().Flush(); +".WrapInCSharpMethod(); + var fixtest = @"using (var memoryStream = new System.IO.MemoryStream()) +{ + memoryStream.Flush(); +}".WrapInCSharpMethod(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task ChainingFixesCorrectlyWhenThereIsANameConflict() + { + var source = @" +var memoryStream = 0; +new System.IO.MemoryStream().Flush(); +".WrapInCSharpMethod(); + var fixtest = @" +var memoryStream = 0; +using (var memoryStream1 = new System.IO.MemoryStream()) +{ + memoryStream1.Flush(); +}".WrapInCSharpMethod(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task ChainingFixesCorrectlyWhenThereIsAssignment() + { + var source = @"//comment +var length = new System.IO.MemoryStream().Length; +".WrapInCSharpMethod(); + var fixtest = @"using (var memoryStream = new System.IO.MemoryStream()) +{ + //comment + var length = memoryStream.Length; +}".WrapInCSharpMethod(); + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task ChainingFixesCorrectlyWhenPassedAsArgument() + { + const string source = @" +class Foo +{ + static void Bar(long i) { } + static void Baz() + { + Bar(new System.IO.MemoryStream().Length); + } +}"; + const string fixtest = @" +class Foo +{ + static void Bar(long i) { } + static void Baz() + { + using (var memoryStream = new System.IO.MemoryStream()) + { + Bar(memoryStream.Length); + } } +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task IgnoreWhenPassedIntoMethod() + { + const string source = @" +using System.IO; +class Foo +{ + void Bar() + { + var m = new MemoryStream(); + Baz(m); + } + void Baz(MemoryStream m) { } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IgnoreWhenAssignedToField() + { + const string source = @" +using System.IO; +class HoldsDisposable +{ + public MemoryStream Ms { get; set; } } +class Foo +{ + void Bar() + { + var m = new MemoryStream(); + var h = new HoldsDisposable(); + h.Ms = m; + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IgnoreWhenAssignedToFieldByNullCoalescingOperator() + { + const string source = @" +using System.IO; +public class Test : IDisposable +{ + private IDisposable _stream; + public void Update() + { + _stream = _stream ?? new MemoryStream(); + } + + public void Dispose() + { + _stream.Dispose(); + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + } +} \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Usage/DisposablesShouldCallSuppressFinalizeTests.cs b/test/CSharp/CodeCracker.Test/Usage/DisposablesShouldCallSuppressFinalizeTests.cs index b99348543..437626d9d 100644 --- a/test/CSharp/CodeCracker.Test/Usage/DisposablesShouldCallSuppressFinalizeTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/DisposablesShouldCallSuppressFinalizeTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -7,6 +8,16 @@ namespace CodeCracker.Test.CSharp.Usage { public class DisposablesShouldCallSuppressFinalizeTests : CodeFixVerifier { + [Fact] + public async void AlreadyCallsSuppressFinalizeWithArrowMethod() + { + const string source = @" + public class MyType : System.IDisposable + { + public void Dispose() => System.GC.SuppressFinalize(this); + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } [Fact] public async void AlreadyCallsSuppressFinalize() @@ -16,7 +27,7 @@ public class MyType : System.IDisposable { public void Dispose() { - GC.SuppressFinalize(this); + System.GC.SuppressFinalize(this); } }"; await VerifyCSharpHasNoDiagnosticsAsync(source); @@ -27,10 +38,10 @@ public async void DoNotWarnIfStructImplmentsIDisposableWithNoSuppressFinalizeCal { const string test = @" public struct MyType : System.IDisposable - { - public void Dispose() - { - } + { + public void Dispose() + { + } }"; await VerifyCSharpHasNoDiagnosticsAsync(test); @@ -41,27 +52,84 @@ public async void WarningIfClassImplmentsIDisposableWithNoSuppressFinalizeCall() { const string test = @" public class MyType : System.IDisposable - { - public void Dispose() - { - } + { + public void Dispose() + { + } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposablesShouldCallSuppressFinalize.ToDiagnosticId(), - Message = "'MyType' should call GC.SuppressFinalize inside the Dispose method.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 33) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposablesShouldCallSuppressFinalize.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(4, 33) + .WithMessage("'MyType' should call GC.SuppressFinalize inside the Dispose method."); await VerifyCSharpDiagnosticAsync(test, expected); } + [Fact] + public async void DoNotWarnIfClassImplementsIDisposableWithSuppressFinalizeCallInFinally() + { + const string test = @" + public class MyType : System.IDisposable + { + public void Dispose() + { + try + { + } + finally + { + System.GC.SuppressFinalize(this); + } + } + }"; + + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + [Fact] + public async void DoNotWarnIfClassImplementsIDisposableWithSuppressFinalizeCallInIf() + { + const string test = @" + public class MyType : System.IDisposable + { + public void Dispose() + { + if (true) + { + System.GC.SuppressFinalize(this); + } + } + }"; + + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + [Fact] + public async void DoNotWarnIfClassImplementsIDisposableWithSuppressFinalizeCallInElse() + { + const string test = @" + public class MyType : System.IDisposable + { + public void Dispose() + { + if (true) + { + } + else + { + System.GC.SuppressFinalize(this); + } + } + }"; + + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + [Fact] public async Task NoWarningIfClassImplementsDisposableCallsSuppressFinalizeAndCallsDisposeWithThis() { const string source = @" + using System; public class MyType : System.IDisposable { public void Dispose() @@ -78,6 +146,7 @@ public void Dispose() public async Task NoWarningIfClassImplementsDisposableCallsSuppressFinalize() { const string source = @" + using System; public class MyType : System.IDisposable { public void Dispose() @@ -95,13 +164,13 @@ public async void NoWarningIfClassImplmentsIDisposableButDoesNotContainsAPublicC { const string test = @" public class MyType : System.IDisposable - { - private MyType() + { + private MyType() { } - public void Dispose() - { + public void Dispose() + { } ~MyType() {} @@ -118,9 +187,9 @@ public async void NoWarningIfClassIsAPrivateNestedType() public class MyType { private class MyNestedType : System.IDisposable - { - public void Dispose() - { + { + public void Dispose() + { } ~MyType() {} @@ -139,9 +208,9 @@ public class MyType private class MyType { public class MyNestedType : System.IDisposable - { - public void Dispose() - { + { + public void Dispose() + { } ~MyType() {} @@ -157,7 +226,7 @@ public async void NoWarningIfStructDoesNotImplementsIDisposable() { const string test = @" public struct MyType - { + { }"; await VerifyCSharpHasNoDiagnosticsAsync(test); @@ -168,10 +237,10 @@ public async void NoWarningIfClassIsSealedWithNoUserDefinedFinalizer() { const string test = @" public sealed class MyType : System.IDisposable - { - public void Dispose() - { - } + { + public void Dispose() + { + } }" ; @@ -183,21 +252,17 @@ public async void WarningIfSealedClassHaveUserDefinedFinalizerImplmentsIDisposab { const string test = @" public sealed class MyType : System.IDisposable - { - public void Dispose() - { + { + public void Dispose() + { } - ~MyType() {} + ~MyType() {} }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposablesShouldCallSuppressFinalize.ToDiagnosticId(), - Message = "'MyType' should call GC.SuppressFinalize inside the Dispose method.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 33) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposablesShouldCallSuppressFinalize.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(4, 33) + .WithMessage("'MyType' should call GC.SuppressFinalize inside the Dispose method."); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -206,8 +271,8 @@ public void Dispose() public async void NoWarningIfClassDoesNotImplementsIDisposable() { const string test = @" - public class MyType - { + public class MyType + { }"; await VerifyCSharpHasNoDiagnosticsAsync(test); @@ -218,22 +283,22 @@ public class MyType public async void WhenClassImplementsIDisposableCallSuppressFinalize() { const string source = @" + using System; public class MyType : System.IDisposable - { - public void Dispose() - { - var x = 123; - } + { + public void Dispose() + { + } }"; const string fixtest = @" + using System; public class MyType : System.IDisposable - { - public void Dispose() - { - var x = 123; + { + public void Dispose() + { GC.SuppressFinalize(this); - } + } }"; await VerifyCSharpFixAsync(source, fixtest, 0); @@ -243,34 +308,31 @@ public void Dispose() public async void WhenClassHasParametrizedDisposeMethod() { const string source = @" + using System; public class MyType : System.IDisposable - { - public void Dispose() - { + { + public void Dispose() + { Dispose(true); - } - + } protected virtual void Dispose(bool disposing) { - } }"; const string fixtest = @" + using System; public class MyType : System.IDisposable - { - public void Dispose() - { + { + public void Dispose() + { Dispose(true); GC.SuppressFinalize(this); - } - + } protected virtual void Dispose(bool disposing) { - } }"; - await VerifyCSharpFixAsync(source, fixtest, 0); } @@ -278,24 +340,23 @@ protected virtual void Dispose(bool disposing) public async void WhenClassExplicitImplementsOfIDisposableCallSuppressFinalize() { const string source = @" - public class MyType : System.IDisposable - { - public void IDisposable.Dispose() - { - var x = 123; - } + using System; + public class MyType : IDisposable + { + void IDisposable.Dispose() + { + } }"; const string fixtest = @" - public class MyType : System.IDisposable - { - public void IDisposable.Dispose() - { - var x = 123; + using System; + public class MyType : IDisposable + { + void IDisposable.Dispose() + { GC.SuppressFinalize(this); - } + } }"; - await VerifyCSharpFixAsync(source, fixtest, 0); } @@ -303,35 +364,179 @@ public void IDisposable.Dispose() public async void WhenClassHasParametrizedDisposeMethodAndExplicitlyImplementsIDisposable() { const string source = @" + using System; public class MyType : System.IDisposable - { - public void IDisposable.Dispose() - { + { + void IDisposable.Dispose() + { Dispose(true); - } + } protected virtual void Dispose(bool disposing) { - + } }"; const string fixtest = @" + using System; public class MyType : System.IDisposable - { - public void IDisposable.Dispose() - { + { + void IDisposable.Dispose() + { Dispose(true); GC.SuppressFinalize(this); - } + } + + protected virtual void Dispose(bool disposing) + { + + } + }"; + await VerifyCSharpFixAsync(source, fixtest, 0); + } + + [Fact] + public async void AddsSystemGCWhenSystemIsNotImported() + { + const string source = @" + public class MyType : System.IDisposable + { + void IDisposable.Dispose() + { + Dispose(true); + } + protected virtual void Dispose(bool disposing) + { + + } + }"; + + const string fixtest = @" + public class MyType : System.IDisposable + { + void IDisposable.Dispose() + { + Dispose(true); + System.GC.SuppressFinalize(this); + } + protected virtual void Dispose(bool disposing) + { + } + }"; + await VerifyCSharpFixAsync(source, fixtest, 0); + } + + [Fact] + public async void CallingSystemGCSupressFinalizeShouldNotGenerateDiags() + { + const string source = @" + public class MyType : System.IDisposable + { + void IDisposable.Dispose() + { + Dispose(true); + System.GC.SuppressFinalize(this); + } + protected virtual void Dispose(bool disposing) + { + + } + }"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async void CallingGCSupressFinalizeWithAliasShouldNotGenerateDiags() + { + const string source = @"using A = System; + public class MyType : System.IDisposable + { + void IDisposable.Dispose() + { + Dispose(true); + A.GC.SuppressFinalize(this); + } protected virtual void Dispose(bool disposing) { - + + } + }"; + + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async void UseSystemGCWhenSystemNamespaceWasNotImportedInCurrentContext() + { + const string source = @" + namespace A + { + using System; + } + namespace B + { + class Foo : System.IDisposable + { + public void Dispose() + { + } } }"; + const string fixtest = @" + namespace A + { + using System; + } + namespace B + { + class Foo : System.IDisposable + { + public void Dispose() + { + System.GC.SuppressFinalize(this); + } + } + }"; + + await VerifyCSharpFixAsync(source, fixtest, 0); } + + [Fact] + public async void CallSupressWhenUsingExpressionBodiedMethod() + { + const string source = @" + using System; + using System.IO; + + public class MyType : System.IDisposable + { + MemoryStream memory; + + public virtual void Dispose() => memory.Dispose(); + }"; + + const string fixtest = @" + using System; + using System.IO; + + public class MyType : System.IDisposable + { + MemoryStream memory; + + public virtual void Dispose() + { + memory.Dispose(); + GC.SuppressFinalize(this); + } + }"; + + await VerifyCSharpFixAsync(source, fixtest, 0); + } + } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Usage/IPAddressAnalyzerTests.cs b/test/CSharp/CodeCracker.Test/Usage/IPAddressAnalyzerTests.cs index 72a1c5086..ca83c566c 100644 --- a/test/CSharp/CodeCracker.Test/Usage/IPAddressAnalyzerTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/IPAddressAnalyzerTests.cs @@ -4,6 +4,7 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; using Xunit; namespace CodeCracker.Test.CSharp.Usage @@ -61,14 +62,26 @@ public async Task IfIsOtherTypeParseMethodDoesNotCreateDiagnostic() await VerifyCSharpHasNoDiagnosticsAsync(test); } + [Fact] + public async Task IfParseIdentifierFoundAndParameterIsNotStringLiteralDoesNotCreatesDiagnostic() + { + var test = string.Format(TestCode, @"var ip = ""; ""System.Net.IPAddress.Parse(ip)"); + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + [Fact] + public async Task IfAbbreviateParseIdentifierFoundAndParameterIsNotStringLiteralDoesNotCreatesDiagnostic() + { + var test = string.Format(TestCode, @"var ip = ""; ""IPAddress.Parse(ip)"); + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + - private static DiagnosticResult CreateDiagnosticResult(int line, int column, Action getErrorMessageAction) { - return new DiagnosticResult { - Id = DiagnosticId.IPAddress.ToDiagnosticId(), - Message = GetErrorMessage(getErrorMessageAction), - Severity = DiagnosticSeverity.Error, - Locations = new[] {new DiagnosticResultLocation("Test0.cs", line, column)} - }; + private static DiagnosticResult CreateDiagnosticResult(int line, int column, Action getErrorMessageAction) { + return new DiagnosticResult(DiagnosticId.IPAddress.ToDiagnosticId(), DiagnosticSeverity.Error) + .WithLocation(line, column) + .WithMessage(GetErrorMessage(getErrorMessageAction)); } private static string GetErrorMessage(Action action) diff --git a/test/CSharp/CodeCracker.Test/Usage/IfReturnTrueTests.cs b/test/CSharp/CodeCracker.Test/Usage/IfReturnTrueTests.cs index 702af37c7..27b4ad1f0 100644 --- a/test/CSharp/CodeCracker.Test/Usage/IfReturnTrueTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/IfReturnTrueTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -153,13 +154,9 @@ public int Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.IfReturnTrue.ToDiagnosticId(), - Message = "You should return directly.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 9, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.IfReturnTrue.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(9, 17) + .WithMessage("You should return the boolean directly."); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -186,13 +183,9 @@ public int Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.IfReturnTrue.ToDiagnosticId(), - Message = "You should return directly.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 9, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.IfReturnTrue.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(9, 17) + .WithMessage("You should return the boolean directly."); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -282,4 +275,4 @@ public bool Foo() await VerifyCSharpFixAsync(source, fixtest, 0); } } -} \ No newline at end of file +} diff --git a/test/CSharp/CodeCracker.Test/Usage/JsonNetAnalyzerTests.cs b/test/CSharp/CodeCracker.Test/Usage/JsonNetAnalyzerTests.cs index aad8d95a6..32b49198b 100644 --- a/test/CSharp/CodeCracker.Test/Usage/JsonNetAnalyzerTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/JsonNetAnalyzerTests.cs @@ -2,11 +2,12 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; using Xunit; namespace CodeCracker.Test.CSharp.Usage { - public class JsonNetTests : CodeFixVerifier + public class JsonNetAnalyzerTests : CodeFixVerifier { private const string TestCode = @" using System; @@ -26,86 +27,79 @@ public Person() [Fact] public async Task IfDeserializeObjectIdentifierFoundAndJsonTextIsIncorrectCreatesDiagnostic() { - var test = string.Format(TestCode, @"Newtonsoft.Json.JsonConvert.DeserializeObject(""foo"")"); + var test = string.Format(TestCode, @"Newtonsoft.Json.JsonConvert.DeserializeObject(""foo"");"); await VerifyCSharpDiagnosticAsync(test, CreateDiagnosticResult(11, 67)); } [Fact] public async Task IfAbbreviatedDeserializeObjectIdentifierFoundAndJsonTextIsIncorrectCreatesDiagnostic() { - var test = string.Format(TestCode, @"JsonConvert.DeserializeObject(""foo"")"); + var test = string.Format(TestCode, @"JsonConvert.DeserializeObject(""foo"");"); await VerifyCSharpDiagnosticAsync(test, CreateDiagnosticResult(11,51)); } [Fact] public async Task IfDeserializeObjectIdentifierFoundAndJsonTextIsCorrectDoesNotCreatesDiagnostic() { - var test = string.Format(TestCode, @"Newtonsoft.Json.JsonConvert.DeserializeObject(""{""name"":""foo""}"")"); + var test = string.Format(TestCode, @"Newtonsoft.Json.JsonConvert.DeserializeObject(""{""name"":""foo""}"");"); await VerifyCSharpHasNoDiagnosticsAsync(test); } [Fact] public async Task IfAbbreviateDeserializeObjectIdentifierFoundAndJsonTextIsCorrectDoesNotCreatesDiagnostic() { - var test = string.Format(TestCode, @"JsonConvert.DeserializeObject(""{""name"":""foo""}"")"); + var test = string.Format(TestCode, @"JsonConvert.DeserializeObject(""{""name"":""foo""}"");"); await VerifyCSharpHasNoDiagnosticsAsync(test); } [Fact] public async Task IfJObjectParseIdentifierFoundAndJsonTextIsIncorrectCreatesDiagnostic() { - var test = string.Format(TestCode, @"Newtonsoft.Json.Linq.JObject.Parse(""foo"")"); + var test = string.Format(TestCode, @"Newtonsoft.Json.Linq.JObject.Parse(""foo"");"); await VerifyCSharpDiagnosticAsync(test, CreateDiagnosticResult(11, 48)); } [Fact] public async Task IfAbbreviatedJObjectParseIdentifierFoundAndJsonTextIsIncorrectCreatesDiagnostic() { - var test = string.Format(TestCode, @"JObject.Parse(""foo"")"); + var test = string.Format(TestCode, @"JObject.Parse(""foo"");"); await VerifyCSharpDiagnosticAsync(test, CreateDiagnosticResult(11, 27)); } [Fact] public async Task IfJObjectParseIdentifierFoundAndJsonTextIsCorrectDoesNotCreatesDiagnostic() { - var test = string.Format(TestCode, @"JObject.Parse(""{""name"":""foo""}"")"); + var test = string.Format(TestCode, @"JObject.Parse(""{""name"":""foo""}"");"); await VerifyCSharpHasNoDiagnosticsAsync(test); } // [Fact] public async Task IfJArrayParseIdentifierFoundAndJsonTextIsIncorrectCreatesDiagnostic() { - var test = string.Format(TestCode, @"Newtonsoft.Json.Linq.JArray.Parse(""foo"")"); + var test = string.Format(TestCode, @"Newtonsoft.Json.Linq.JArray.Parse(""foo"");"); await VerifyCSharpDiagnosticAsync(test, CreateDiagnosticResult(11, 47)); } [Fact] public async Task IfAbbreviatedJArrayParseIdentifierFoundAndJsonTextIsIncorrectCreatesDiagnostic() { - var test = string.Format(TestCode, @"JArray.Parse(""foo"")"); + var test = string.Format(TestCode, @"JArray.Parse(""foo"");"); await VerifyCSharpDiagnosticAsync(test, CreateDiagnosticResult(11, 26)); } [Fact] public async Task IfJArrayParseIdentifierFoundAndJsonTextIsCorrectDoesNotCreatesDiagnostic() { - var test = string.Format(TestCode, @"JArray.Parse(""{""name"":""foo""}"")"); + var test = string.Format(TestCode, @"JArray.Parse(""{""name"":""foo""}"");"); await VerifyCSharpHasNoDiagnosticsAsync(test); } private static DiagnosticResult CreateDiagnosticResult(int line, int column) { - return new DiagnosticResult { - Id = DiagnosticId.JsonNet.ToDiagnosticId(), - Message = "Error parsing boolean value. Path '', line 0, position 0.", - Severity = DiagnosticSeverity.Error, - Locations = new[] {new DiagnosticResultLocation("Test0.cs", line, column)} - }; + return new DiagnosticResult(DiagnosticId.JsonNet.ToDiagnosticId(), DiagnosticSeverity.Error) + .WithLocation(line, column) + .WithMessage("Unexpected end when reading JSON. Path '', line 1, position 3."); } - - protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() - { - return new JsonNetAnalyzer(); - } + protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() => new JsonNetAnalyzer(); } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Usage/NoPrivateReadonlyFieldTest.cs b/test/CSharp/CodeCracker.Test/Usage/NoPrivateReadonlyFieldTest.cs index 2c707711a..f1fb1e715 100644 --- a/test/CSharp/CodeCracker.Test/Usage/NoPrivateReadonlyFieldTest.cs +++ b/test/CSharp/CodeCracker.Test/Usage/NoPrivateReadonlyFieldTest.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Xunit; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; namespace CodeCracker.Test.CSharp.Usage { @@ -10,14 +11,12 @@ public class NoPrivateReadonlyFieldTests : CodeFixVerifier { protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() => new NoPrivateReadonlyFieldAnalyzer(); - static DiagnosticResult CreateExpectedDiagnosticResult(int line, int column, string fieldName = "i") => - new DiagnosticResult - { - Id = DiagnosticId.NoPrivateReadonlyField.ToDiagnosticId(), - Message = string.Format(NoPrivateReadonlyFieldAnalyzer.Message, fieldName), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", line, column) } - }; + static DiagnosticResult CreateExpectedDiagnosticResult(int line, int column, string fieldName = "i") + { + return new DiagnosticResult(DiagnosticId.NoPrivateReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(line, column) + .WithMessage(string.Format(NoPrivateReadonlyFieldAnalyzer.Message, fieldName)); + } [Fact] public async Task PrivateFieldWithAssignmentOnDeclarationCreatesNoDiagnostic() diff --git a/test/CSharp/CodeCracker.Test/Usage/ReadOnlyComplexTypesTests.cs b/test/CSharp/CodeCracker.Test/Usage/ReadOnlyComplexTypesTests.cs new file mode 100644 index 000000000..d6fe05fb9 --- /dev/null +++ b/test/CSharp/CodeCracker.Test/Usage/ReadOnlyComplexTypesTests.cs @@ -0,0 +1,441 @@ +using CodeCracker.CSharp.Usage; +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace CodeCracker.Test.CSharp.Usage +{ + public class ReadOnlyComplexTypesTests : CodeFixVerifier + { + [Fact] + public async Task FieldWithAssignmentOnDeclarationAlreadyReadonlyDoesNotCreateDiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + class TypeName + { + private readonly int i = 1; + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task ConstantFieldDoesNotCreateDiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + class TypeName + { + private const int i = 1; + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task dateTimeDoesNotCreateDiagnostics() + { + const string source1 = @" +namespace codeCrackerConsole +{ + public class MyClass + { + private readonly DateTime dt = new DateTime(1, 1, 2015); + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source1); + } + [Fact] + public async Task protectedFieldDoesNotCreateDiagnostics() + { + const string source = @" + namespace ConsoleApplication1 + { +public class MyClass + { + protected MyStruct myStruct = default(MyStruct); + private struct MyStruct + { + public int Value; + } + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task publicFieldDoesNotCreateDiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + public class MyClass + { + public MyStruct myStruct = default(MyStruct); + private struct MyStruct + { + public int Value; + } + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task publicFieldWithClassDoesNotCreateDiagnostics() + { + const string source = @" + namespace ConsoleApplication1 + { + public class MyClass + { + public MyStruct myStruct = new MyStruct(); + private struct MyStruct + { + public int Value; + } + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task readOnlyVarDoesNotCreateDiagnostics() + { + const string source = @" + namespace ConsoleApplication1 + { + public class MyClass + { + readonly var s = ""; + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task readOnlyFieldDoesNotCreateDiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + public class MyClass + { + readonly string s; + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task primitiveTypesDoesNotCreateDiagnostics() + { + const string source = @" + namespace ConsoleApplication1 + { + class test + { + private byte b = new byte(); + private sbyte s = new sbyte(); + private int i = new int(); + private uint u = new uint(); + private short ss = new short(); + public ushort us = new ushort(); + public long l = new long(); + public ulong ul = new ulong(); + public float fl = new float(); + public double d = new double(); + public char c = new char(); + public bool bo = new bool(); + public object o = new object(); + public string st = ""; + public decimal dc = new decimal(); + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task enumDoesNotCreateDiagnostics() + { + const string source = @" + namespace ConsoleApplication1 + { + public class MyClass + { + private test testEnum; + public enum test + { + test1 = 1, + test2 = 2 + } + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + [Fact] + public async Task IgnoreOut() + { + const string source = @" +public class C +{ + private string field = ""; + private static void Foo(out string bar) => bar = ""; + public void Baz() => Foo(out field); +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task IgnoreRef() + { + const string source = @" +public class C +{ + private string field = ""; + private static void Foo(ref string bar) => bar = ""; + public void Baz() => Foo(ref field); +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task IgnoreAssignmentToFieldsInOtherTypes() + { + const string source1 = @" +class TypeName1 +{ + public int i; +}"; + const string source2 = @" +class TypeName2 +{ + public TypeName2() + { + var t = new TypeName1(); + t.i = 1; + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source1, source2 }); + } + + [Fact] + public async Task FieldWithoutAssignmentDoesNotCreateDiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + class TypeName + { + private int i; + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task FieldWithoutAssignmentInAStructDoesNotCreateDiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + struct TypeName + { + private int i; + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task PublicFieldWithAssignmentOnDeclarationDoesNotCreateDiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + class TypeName + { + public int i = 1; + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + + [Fact] + public async Task structNoDiagnostic() + { + const string source1 = @" + namespace ConsoleApplication1 + { + public class MyClass + { + private MyStruct myStruct; + private struct MyStruct + { + public int Value; + } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source1 }); + } + + [Fact] + public async Task structWithNullValue() + { + const string source1 = @" + namespace ConsoleApplication1 + { + public class MyClass + { + private MyStruct myStruct = null; + private struct MyStruct + { + public int Value; + } + } + }"; + const string source2 = @" + namespace ConsoleApplication1 + { + public class MyClass + { + private readonly MyStruct myStruct = null; + private struct MyStruct + { + public int Value; + } + } + }"; + await VerifyCSharpFixAsync(source1, source2, 0); + } + [Fact] + public async Task structDefaultCreateWithoutReadOnlyDeclarationSameClass() + { + const string source1 = @" + namespace ConsoleApplication1 + { + public class MyClass + { + private MyStruct myStruct = default(MyStruct); + private struct MyStruct + { + public int Value; + } + } + }"; + const string source2 = @" + namespace ConsoleApplication1 + { + public class MyClass + { + private readonly MyStruct myStruct = default(MyStruct); + private struct MyStruct + { + public int Value; + } + } + }"; + await VerifyCSharpFixAsync(source1, source2, 0); + } + + [Fact] + public async Task structCreateWithoutReadonlyDeclaration() + { + const string source1 = @" + namespace ConsoleApplication1 + { + public class MyClass + { + private MyStruct myStruct = new MyStruct(); + } + private struct MyStruct + { + public int Value; + } + }"; + const string source2 = @" + namespace ConsoleApplication1 + { + public class MyClass + { + private readonly MyStruct myStruct = new MyStruct(); + } + private struct MyStruct + { + public int Value; + } + }"; + await VerifyCSharpFixAsync(source1, source2, 0); + } + + [Fact] + public async Task structDefaultCreateWithoutReadonlyDeclaration() + { + const string source1 = @" + namespace ConsoleApplication1 + { + public class MyClass + { + private MyStruct myStruct = default(MyStruct); + } + private struct MyStruct + { + public int Value; + } + }"; + const string source2 = @" + namespace ConsoleApplication1 + { + public class MyClass + { + private readonly MyStruct myStruct = default(MyStruct); + } + private struct MyStruct + { + public int Value; + } + }"; + await VerifyCSharpFixAsync(source1, source2, 0); + } + [Fact] + public async Task enumerationsDoesNotCreateDiagnostic() + { + const string source = @" + public class EnumTest + { + enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat }; + + static void Main() + { + int x = (int)Days.Sun; + int y = (int)Days.Fri; + int z = x + y; + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task privateEnumerationsDoesNotCreateDiagnostic() + { + const string source = @" + public class EnumTest + { + enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat }; + private Days enumDays; + static void Main() + { + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + } +} diff --git a/test/CSharp/CodeCracker.Test/Usage/ReadonlyFieldTests.cs b/test/CSharp/CodeCracker.Test/Usage/ReadonlyFieldTests.cs index 0d50c52bd..8639541c5 100644 --- a/test/CSharp/CodeCracker.Test/Usage/ReadonlyFieldTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/ReadonlyFieldTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -7,6 +8,100 @@ namespace CodeCracker.Test.CSharp.Usage { public class ReadonlyFieldTests : CodeFixVerifier { + [Fact] + public async Task IgnorePreIncrement() + { + const string source = @" +class TypeName +{ + static int counter = 1; + static void Main() => ++counter; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task IgnoreOut() + { + const string source = @" +public class C +{ + private string field = ""; + private static void Foo(out string bar) => bar = ""; + public void Baz() => Foo(out field); +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task IgnoreRef() + { + const string source = @" +public class C +{ + private string field = ""; + private static void Foo(ref string bar) => bar = ""; + public void Baz() => Foo(ref field); +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task IgnorePostIncrement() + { + const string source = @" +class TypeName +{ + static int counter = 1; + static void Main() => counter++; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task IgnorePreDecrement() + { + const string source = @" +class TypeName +{ + static int counter = 1; + static void Main() => --counter; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task IgnorePostDecrement() + { + const string source = @" +class TypeName +{ + static int counter = 1; + static void Main() => counter--; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source }); + } + + [Fact] + public async Task IgnoreAssignmentToFieldsInOtherTypes() + { + const string source1 = @" +class TypeName1 +{ + public int i; +}"; + const string source2 = @" +class TypeName2 +{ + public TypeName2() + { + var t = new TypeName1(); + t.i = 1; + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(new[] { source1, source2 }); + } + [Fact] public async Task FieldWithoutAssignmentDoesNotCreateDiagnostic() { @@ -88,13 +183,9 @@ class TypeName private int i = 1; } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "i"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 25) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "i")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -109,13 +200,9 @@ class TypeName int i = 1; } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "i"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 17) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "i")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -131,20 +218,12 @@ class TypeName private int j = 1; } }"; - var expected1 = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "i"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 25) } - }; - var expected2 = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "j"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 25) } - }; + var expected1 = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 25) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "i")); + var expected2 = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(7, 25) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "j")); await VerifyCSharpDiagnosticAsync(source, expected1, expected2); } @@ -165,34 +244,18 @@ class TypeName2 private int l = 1; } }"; - var expected1 = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "i"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 25) } - }; - var expected2 = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "j"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 25) } - }; - var expected3 = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "k"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 25) } - }; - var expected4 = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "l"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 12, 25) } - }; + var expected1 = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 25) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "i")); + var expected2 = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(7, 25) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "j")); + var expected3 = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(11, 25) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "k")); + var expected4 = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(12, 25) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "l")); await VerifyCSharpDiagnosticAsync(source, new[] { expected1, expected2, expected3, expected4 }); } @@ -210,13 +273,9 @@ class TypeName } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "i"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 29) } - }; + var expected = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(8, 29) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "i")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -231,16 +290,27 @@ struct TypeName private int i = 1; } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "i"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 25) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "i")); await VerifyCSharpDiagnosticAsync(source, expected); } + [Fact] + public async Task FieldWithAddAssignmentDoesNotCreateDiagnostic() + { + const string source = @" +class TypeName +{ + private int i = 0; + public void Foo() + { + i += 0; + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + [Fact] public async Task FieldWithAssignmentDoesNotCreateDiagnostic() { @@ -249,7 +319,7 @@ namespace ConsoleApplication1 { class TypeName { - private int i; + private int i = 0; public void Foo() { i = 0; @@ -265,9 +335,9 @@ public async Task FieldWithAssignmentInAStructDoesNotCreateDiagnostic() const string source = @" namespace ConsoleApplication1 { - class TypeName + struct TypeName { - private int i; + private int i = 0; public void Foo() { i = 0; @@ -292,13 +362,9 @@ class TypeName } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "i"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 25) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "i")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -317,13 +383,9 @@ class TypeName } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "i"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 25) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "i")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -382,13 +444,9 @@ class TypeName private int i, j = 1; } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "j"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 28) } - }; + var expected = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 28) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "j")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -638,13 +696,9 @@ static TypeName() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "i"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 32) } - }; + var expected = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 32) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "i")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -659,13 +713,9 @@ class TypeName private static int i = 1; } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.ReadonlyField.ToDiagnosticId(), - Message = string.Format(ReadonlyFieldAnalyzer.Message, "i"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 32) } - }; + var expected = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 32) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "i")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -720,5 +770,208 @@ public void Foo() }"; await VerifyCSharpHasNoDiagnosticsAsync(source1, source2); } + + [Fact] + public async Task FieldsAssignedOnLambdaDoesNotCreateDiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + internal class Test + { + private string _value; + + public Test() + { + Func factory = () => _value ?? (_value = ""Hello""); + } + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task FieldsAssignedOnLambdaWithInitializerDoesNotCreateDiagnostic() + { + const string source = @" +using System; +class C +{ + private readonly Action set; + private int i = 0; + + public C() + { + set = () => i = 1; + } + + public void Modify() + { + set(); + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task VariableInitializerDoesNotCreateDiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + class A + { + public int X; + + public A() + { + X = 5; + } + } + + static void B() + { + var c = new A { X = 7 }; + } + }"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task UserDefinedStructFieldDoesNotCreateDiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + public class MyClass + { + private MyStruct myStruct = default(MyStruct); + + private struct MyStruct + { + public int Value; + } + } + } + "; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task DateTimeFieldInitializedOnDeclarationDoesNotCreateDiagnostic() + { + const string source = @" + using System; + + namespace ConsoleApplication1 + { + public class MyClass + { + private DateTime date = new DateTime(2008, 5, 1, 8, 30, 52); + } + } + "; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task EnumerationFieldInitializedOnDeclarationCreatesADiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + public class MyClass + { + private VehicleType car = VehicleType.Car; + + private enum VehicleType + { + None = 0, + Car = 1, + Truck = 2 + } + } + } + "; + + var expected = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 33) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "car")); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task ReferenceTypeFieldInitializedInConstructorCreatesADiagnostic() + { + const string source = @" + namespace ConsoleApplication1 + { + public class Person + { + private string name; + + public Person(string name) + { + this.name = name; + } + } + } + "; + + var expected = new DiagnosticResult(DiagnosticId.ReadonlyField.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(6, 28) + .WithMessage(string.Format(ReadonlyFieldAnalyzer.Message, "name")); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task IgnoreWhenConstructorIsTheLastMember() + { + const string source = @" +class Test +{ + private int value; + public int Value + { + get { return value; } + set { this.value = value; } + } + public void Foo() + { + value = 1; + } + public Test() + { + value = 8; + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task ComplexTypeDoesNotCreateDiagnosticAsync() + { + const string source = @" +class C +{ + private S s; + + public C() + { + s = default(S); + } + + public void M1() + { + s.Value = 1; + } + + public struct S + { + public int Value; + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Usage/RedundantFieldAssignmentTests.cs b/test/CSharp/CodeCracker.Test/Usage/RedundantFieldAssignmentTests.cs index 97caea241..f24ccfc5b 100644 --- a/test/CSharp/CodeCracker.Test/Usage/RedundantFieldAssignmentTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/RedundantFieldAssignmentTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -170,13 +171,9 @@ class TypeName { private int i = 0; }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), - Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", 0), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(4, 17) + .WithMessage(string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", 0)); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -188,13 +185,9 @@ class TypeName { private int i = default(int); }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), - Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "default(int)"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 17) } - }; + var expected = new DiagnosticResult(DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(4, 17) + .WithMessage(string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "default(int)")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -206,13 +199,9 @@ class TypeName { private string s = null; }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), - Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "s", "null"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 20) } - }; + var expected = new DiagnosticResult(DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(4, 20) + .WithMessage(string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "s", "null")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -224,13 +213,9 @@ class TypeName { private long i = 0L; }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), - Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "0L"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 18) } - }; + var expected = new DiagnosticResult(DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(4, 18) + .WithMessage(string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "0L")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -242,13 +227,9 @@ class TypeName { private long i = 0; }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), - Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "0"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 18) } - }; + var expected = new DiagnosticResult(DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(4, 18) + .WithMessage(string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "0")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -260,13 +241,9 @@ class TypeName { private System.IntPtr i = System.IntPtr.Zero; }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), - Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "System.IntPtr.Zero"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 27) } - }; + var expected = new DiagnosticResult(DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(4, 27) + .WithMessage(string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "System.IntPtr.Zero")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -279,13 +256,9 @@ class TypeName { private IntPtr i = IntPtr.Zero; }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), - Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "IntPtr.Zero"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 5, 20) } - }; + var expected = new DiagnosticResult(DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(5, 20) + .WithMessage(string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "IntPtr.Zero")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -297,13 +270,9 @@ class TypeName { private System.UIntPtr i = System.UIntPtr.Zero; }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), - Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "System.UIntPtr.Zero"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 28) } - }; + var expected = new DiagnosticResult(DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(4, 28) + .WithMessage(string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "System.UIntPtr.Zero")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -315,13 +284,9 @@ class TypeName { private System.DateTime d = System.DateTime.MinValue; }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), - Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "d", "System.DateTime.MinValue"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 29) } - }; + var expected = new DiagnosticResult(DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(4, 29) + .WithMessage(string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "d", "System.DateTime.MinValue")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -334,13 +299,9 @@ class TypeName { private E e = 0; }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), - Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "e", "0"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 5, 15) } - }; + var expected = new DiagnosticResult(DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(5, 15) + .WithMessage(string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "e", "0")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -353,13 +314,9 @@ class TypeName { private E e = 0.0; }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), - Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "e", "0.0"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 5, 15) } - }; + var expected = new DiagnosticResult(DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(5, 15) + .WithMessage(string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "e", "0.0")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -371,13 +328,9 @@ class TypeName { private bool b = false; }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), - Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "b", "false"), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 18) } - }; + var expected = new DiagnosticResult(DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(4, 18) + .WithMessage(string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "b", "false")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -389,13 +342,9 @@ class TypeName { private int i, j, k = 0; }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), - Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "k", 0), - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 23) } - }; + var expected = new DiagnosticResult(DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(4, 23) + .WithMessage(string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "k", 0)); await VerifyCSharpDiagnosticAsync(source, expected); } diff --git a/test/CSharp/CodeCracker.Test/Usage/RegexTests.cs b/test/CSharp/CodeCracker.Test/Usage/RegexTests.cs index 6ffda204c..8feee163a 100644 --- a/test/CSharp/CodeCracker.Test/Usage/RegexTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/RegexTests.cs @@ -1,6 +1,7 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; using System; using System.Threading.Tasks; using Xunit; @@ -76,13 +77,9 @@ public async Task Foo() message = e.Message; } - var expected = new DiagnosticResult - { - Id = DiagnosticId.Regex.ToDiagnosticId(), - Message = message, - Severity = DiagnosticSeverity.Error, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 64) } - }; + var expected = new DiagnosticResult(DiagnosticId.Regex.ToDiagnosticId(), DiagnosticSeverity.Error) + .WithLocation(11, 64) + .WithMessage(message); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -116,13 +113,9 @@ public async Task Foo() message = e.Message; } - var expected = new DiagnosticResult - { - Id = DiagnosticId.Regex.ToDiagnosticId(), - Message = message, - Severity = DiagnosticSeverity.Error, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 33) } - }; + var expected = new DiagnosticResult(DiagnosticId.Regex.ToDiagnosticId(), DiagnosticSeverity.Error) + .WithLocation(11, 33) + .WithMessage(message); await VerifyCSharpDiagnosticAsync(source, expected); } diff --git a/test/CSharp/CodeCracker.Test/Usage/RemovePrivateMethodNeverUsedAnalyzerTest.cs b/test/CSharp/CodeCracker.Test/Usage/RemovePrivateMethodNeverUsedAnalyzerTest.cs index be1178c7b..ee42e29a9 100644 --- a/test/CSharp/CodeCracker.Test/Usage/RemovePrivateMethodNeverUsedAnalyzerTest.cs +++ b/test/CSharp/CodeCracker.Test/Usage/RemovePrivateMethodNeverUsedAnalyzerTest.cs @@ -1,10 +1,61 @@ using CodeCracker.CSharp.Usage; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using Xunit; namespace CodeCracker.Test.CSharp.Usage { public class RemovePrivateMethodNeverUsedAnalyzerTest : CodeFixVerifier { + + [Theory] + [InlineData("Fact")] + [InlineData("ContractInvariantMethod")] + [InlineData("System.Diagnostics.Contracts.ContractInvariantMethod")] + [InlineData("DataMember")] + public async void DoesNotGenerateDiagnosticsWhenMethodAttributeIsAnException(string value) + { + var source = @" +class Foo +{ + [" + value + @"] + private void PrivateFoo() { } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Theory] + [InlineData("[Obsolete, Fact]")] + [InlineData("[Obsolete]\n[Fact]")] + public async void DoesNotGenerateDiagnosticsWhenMethodAttributeIsAnExceptionAndMixedWithOtherAttributes(string value) + { + var source = @" +class Foo +{ + " + value + @" + private void PrivateFoo() { } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async void GenerateDiagnosticsOnNotIgnoredAttributes() + { + const string source = @" +class Foo +{ + [Obsolete] + private void PrivateFoo() { } +}"; + const string fixtest = @" +class Foo +{ +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + + [Fact] public async void DoesNotGenerateDiagnostics() { @@ -359,5 +410,78 @@ bool System.IEquatable.Equals(Foo other) }"; await VerifyCSharpHasNoDiagnosticsAsync(source); } + + // see https://msdn.microsoft.com/en-us/library/53b8022e(v=vs.110).aspx + [Fact] + public async void WinFormsPropertyDefaultValueDefinitionMethodsShouldBeIgnored() + { + var source = @" +public int PropertyXXX { + get; + set; +} + +private bool ShouldSerializePropertyXXX() => true; + +private void ResetPropertyXXX() { }; +".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + private static DiagnosticResult CreateDiagnosticResult(int line, int column) + { + return new DiagnosticResult(DiagnosticId.RemovePrivateMethodNeverUsed.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(line, column) + .WithMessage(RemovePrivateMethodNeverUsedAnalyzer.Message); + } + + [Fact] + public async void WinFormsPropertyDefaultValueDefinitionMethodsMustHaveCorrectSignature() + { + var source = @" +public int Property1 { get; set; } +public int Property2 { get; set; } +public int Property3 { get; set; } + +private int ShouldSerializeProperty1() => 1; +private bool ShouldSerializeProperty2(int i) => true; +private void ShouldSerializeProperty3() { }; + +private bool ResetProperty1() => true; +private void ResetProperty2(int i) { }; +".WrapInCSharpClass(); + var result1 = CreateDiagnosticResult(13, 1); + var result2 = CreateDiagnosticResult(14, 1); + var result3 = CreateDiagnosticResult(15, 1); + var result4 = CreateDiagnosticResult(17, 1); + var result5 = CreateDiagnosticResult(18, 1); + await VerifyCSharpDiagnosticAsync(source, new DiagnosticResult[] { result1, result2, result3, result4, result5 }); + } + + [Fact] + public async void WinFormsPropertyDefaultValueDefinitionMethodsMustHaveCorrespondingProperty() + { + var source = @" +private bool ShouldSerializePropertyXXX() => true; + +private void ResetPropertyXXX() { }; +".WrapInCSharpClass(); + var result1 = CreateDiagnosticResult(9, 1); + var result2 = CreateDiagnosticResult(11, 1); + await VerifyCSharpDiagnosticAsync(source, new DiagnosticResult[] { result1, result2 }); + } + + [Fact] + public async void WinFormsPropertyDefaultValueDefinitionMethodsMustHaveASuffix() + { + var source = @" +private bool ShouldSerialize() => true; + +private void ResetProperty() { }; +".WrapInCSharpClass(); + var result1 = CreateDiagnosticResult(9, 1); + var result2 = CreateDiagnosticResult(11, 1); + await VerifyCSharpDiagnosticAsync(source, new DiagnosticResult[] { result1, result2 }); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Usage/RemoveRedundantElseClauseTests.cs b/test/CSharp/CodeCracker.Test/Usage/RemoveRedundantElseClauseTests.cs index 82904f6f5..82a32d51a 100644 --- a/test/CSharp/CodeCracker.Test/Usage/RemoveRedundantElseClauseTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/RemoveRedundantElseClauseTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -120,13 +121,9 @@ public async Task CreateDiagnosticsWhenEmptyElse() { var test = @"if(1 == 2){ return 1; } else { }".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.RemoveRedundantElseClause.ToDiagnosticId(), - Message = "Remove redundant else", - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 41) } - }; + var expected = new DiagnosticResult(DiagnosticId.RemoveRedundantElseClause.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 37) + .WithMessage("Remove redundant else"); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -136,13 +133,9 @@ public async Task CreateDiagnosticsWhenEmptyElseWithoutBlockOnIf() { var test = @"if(1 == 2) return 1; else { }".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.RemoveRedundantElseClause.ToDiagnosticId(), - Message = "Remove redundant else", - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 38) } - }; + var expected = new DiagnosticResult(DiagnosticId.RemoveRedundantElseClause.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 34) + .WithMessage("Remove redundant else"); await VerifyCSharpDiagnosticAsync(test, expected); } diff --git a/test/CSharp/CodeCracker.Test/Usage/RethrowExceptionTests.cs b/test/CSharp/CodeCracker.Test/Usage/RethrowExceptionTests.cs index 39affb535..f32efd7e2 100644 --- a/test/CSharp/CodeCracker.Test/Usage/RethrowExceptionTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/RethrowExceptionTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -12,7 +13,7 @@ namespace ConsoleApplication1 { class TypeName { - public async Task Foo() + public void Foo() { try { } catch (System.Exception ex) @@ -27,13 +28,9 @@ public async Task Foo() [Fact] public async Task WhenThrowingOriginalExceptionAnalyzerCreatesDiagnostic() { - var expected = new DiagnosticResult - { - Id = DiagnosticId.RethrowException.ToDiagnosticId(), - Message = "Don't throw the same exception you caught, you lose the original stack trace.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 12, 21) } - }; + var expected = new DiagnosticResult(DiagnosticId.RethrowException.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(12, 21) + .WithMessage("Throwing the same exception that was caught will lose the original stack trace."); await VerifyCSharpDiagnosticAsync(sourceWithUsingSystem, expected); } @@ -48,7 +45,7 @@ namespace ConsoleApplication1 { class TypeName { - public async Task Foo() + public void Foo() { try { } catch (System.Exception ex) @@ -70,7 +67,7 @@ namespace ConsoleApplication1 { class TypeName { - public async Task Foo() + public void Foo() { try { } catch (System.Exception ex) @@ -80,7 +77,7 @@ public async Task Foo() } } }"; - await VerifyCSharpFixAsync(sourceWithUsingSystem, fixtest, 1); + await VerifyCSharpFixAsync(sourceWithUsingSystem, fixtest, 1, allowNewCompilerDiagnostics: true); } [Fact] @@ -92,7 +89,7 @@ namespace ConsoleApplication1 { class TypeName { - public async Task Foo() + public void Foo() { try { } catch (System.Exception ex) @@ -124,4 +121,4 @@ public async Task Foo() await VerifyCSharpHasNoDiagnosticsAsync(fixtest); } } -} \ No newline at end of file +} diff --git a/test/CSharp/CodeCracker.Test/Usage/SimplifyRedundantBooleanComparisonsTests.cs b/test/CSharp/CodeCracker.Test/Usage/SimplifyRedundantBooleanComparisonsTests.cs index 7b0f25909..e5fe06e4d 100644 --- a/test/CSharp/CodeCracker.Test/Usage/SimplifyRedundantBooleanComparisonsTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/SimplifyRedundantBooleanComparisonsTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -9,40 +10,37 @@ public class SimplifyRedundantBooleanComparisonsTests : CodeFixVerifier { [Theory] - [InlineData("if (foo == true) {}", 21)] - [InlineData("if (true == foo) {}", 21)] - [InlineData("var fee = (foo == true);", 28)] - [InlineData("var fee = (true == foo);", 28)] - [InlineData("if (foo == false) {}", 21)] - [InlineData("if (false == foo) {}", 21)] - [InlineData("var fee = (foo == false);", 28)] - [InlineData("var fee = (false == foo);", 28)] - [InlineData("if (foo != true) {}", 21)] - [InlineData("if (true != foo) {}", 21)] - [InlineData("var fee = (foo != true);", 28)] - [InlineData("var fee = (true != foo);", 28)] - [InlineData("if (foo != false) {}", 21)] - [InlineData("if (false != foo) {}", 21)] - [InlineData("var fee = (foo != false);", 28)] - [InlineData("var fee = (false != true);", 28)] + [InlineData("if (foo == true) {}", 17)] + [InlineData("if (true == foo) {}", 17)] + [InlineData("var fee = (foo == true);", 24)] + [InlineData("var fee = (true == foo);", 24)] + [InlineData("if (foo == false) {}", 17)] + [InlineData("if (false == foo) {}", 17)] + [InlineData("var fee = (foo == false);", 24)] + [InlineData("var fee = (false == foo);", 24)] + [InlineData("if (foo != true) {}", 17)] + [InlineData("if (true != foo) {}", 17)] + [InlineData("var fee = (foo != true);", 24)] + [InlineData("var fee = (true != foo);", 24)] + [InlineData("if (foo != false) {}", 17)] + [InlineData("if (false != foo) {}", 17)] + [InlineData("var fee = (foo != false);", 24)] + [InlineData("var fee = (false != true);", 24)] public async Task WhenComparingWithBoolAnalyzerCreatesDiagnostic(string sample, int column) { sample = "bool foo; " + sample; // add declaration of foo column += 10; // adjust column for added declaration var test = sample.WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.SimplifyRedundantBooleanComparisons.ToDiagnosticId(), - Message = "You can remove this comparison.", - Severity = DiagnosticSeverity.Info, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, column) } - }; + var expected = new DiagnosticResult(DiagnosticId.SimplifyRedundantBooleanComparisons.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, column) + .WithMessage("You can remove this comparison."); await VerifyCSharpDiagnosticAsync(test, expected); } + [Theory] [InlineData("if (foo == 0) {}")] [InlineData("if (0 == foo) {}")] @@ -102,5 +100,32 @@ public async Task FixRemovesRedundantComparisons(string original, string result) await VerifyCSharpFixAsync(test, fixtest); } + + [Fact] + public async Task FixWithoutThrowingAnyException() + { + const string test = @" +struct ProjectCompilation {} +class Foo +{ + public bool Comp(bool obj) + { + if (obj is ProjectCompilation == false) return false; + return true; + } +}"; + + const string fixtest = @" +struct ProjectCompilation {} +class Foo +{ + public bool Comp(bool obj) + { + if (!(obj is ProjectCompilation)) return false; + return true; + } +}"; + await VerifyCSharpFixAsync(test, fixtest); + } } } \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Usage/StringFormatArgsTests.cs b/test/CSharp/CodeCracker.Test/Usage/StringFormatArgsTests.cs index 8899710b7..ebd90f4c7 100644 --- a/test/CSharp/CodeCracker.Test/Usage/StringFormatArgsTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/StringFormatArgsTests.cs @@ -3,10 +3,11 @@ using Xunit; using Microsoft.CodeAnalysis.Diagnostics; using CodeCracker.CSharp.Usage; +using Microsoft.CodeAnalysis.Testing; namespace CodeCracker.Test.CSharp.Usage { - public class StringFormatTests : CodeFixVerifier + public class StringFormatArgsTests : CodeFixVerifier { protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() => new StringFormatArgsAnalyzer(); @@ -79,30 +80,32 @@ public async Task IgnoresMethodsCalledWithIncorrectParameterTypes() } [Fact] - public async Task MethodsWithLessParametersCreatesDiagnostic() + public async Task NoParametersCreatesError() + { + var source = @"var result = string.Format(""{0}"");".WrapInCSharpMethod(); + var expected = new DiagnosticResult(DiagnosticId.StringFormatArgs_InvalidArgs.ToDiagnosticId(), DiagnosticSeverity.Error) + .WithLocation(10, 26) + .WithMessage(StringFormatArgsAnalyzer.InvalidArgsReferenceMessage); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task LessParametersCreatesError() { var source = @"var result = string.Format(""one {0} two {1}"", ""a"");".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormatArgs.ToDiagnosticId(), - Message = StringFormatArgsAnalyzer.IncorrectNumberOfArgsMessage, - Severity = DiagnosticSeverity.Error, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 30) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringFormatArgs_InvalidArgs.ToDiagnosticId(), DiagnosticSeverity.Error) + .WithLocation(10, 26) + .WithMessage(StringFormatArgsAnalyzer.InvalidArgsReferenceMessage); await VerifyCSharpDiagnosticAsync(source, expected); } [Fact] - public async Task MethodsWithMoreParametersCreatesDiagnostic() + public async Task MoreArgumentsCreatesWarning() { var source = @"var result = string.Format(""one {0} two {1}"", ""a"", ""b"", ""c"");".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormatArgs.ToDiagnosticId(), - Message = StringFormatArgsAnalyzer.IncorrectNumberOfArgsMessage, - Severity = DiagnosticSeverity.Error, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 30) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringFormatArgs_ExtraArgs.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(10, 26) + .WithMessage(StringFormatArgsAnalyzer.IncorrectNumberOfArgsMessage); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -128,16 +131,12 @@ public async Task MethodWithParamtersReferencingSingleAndFormatSpecifiersArgumen } [Fact] - public async Task MethodWithMultibleParamtersReferencingSingleArgumentCreatesDiagnostic() + public async Task TwoParametersReferencingSamePlaceholderCreatesWarning() { var source = @"var result = string.Format(""one {0} two {0}"", ""a"", ""b"");".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormatArgs.ToDiagnosticId(), - Message = StringFormatArgsAnalyzer.IncorrectNumberOfArgsMessage, - Severity = DiagnosticSeverity.Error, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 30) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringFormatArgs_ExtraArgs.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(10, 26) + .WithMessage(StringFormatArgsAnalyzer.IncorrectNumberOfArgsMessage); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -163,47 +162,45 @@ public async Task IgnoreVerbatimStringWithCorrectNumberOfHoles() } [Fact] - public async Task VerbatimStringWithIncorrectNumberOfHolesCreatesDiagnostic() + public async Task VerbatimStringWithMissingArgCreatesError() { var source = @" var noun = ""Giovanni""; var s = string.Format(@""This {0} is """"{1}""""."", noun);".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormatArgs.ToDiagnosticId(), - Message = StringFormatArgsAnalyzer.IncorrectNumberOfArgsMessage, - Severity = DiagnosticSeverity.Error, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 12, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringFormatArgs_InvalidArgs.ToDiagnosticId(), DiagnosticSeverity.Error) + .WithLocation(12, 25) + .WithMessage(StringFormatArgsAnalyzer.InvalidArgsReferenceMessage); await VerifyCSharpDiagnosticAsync(source, expected); } [Fact] - public async Task MethodWithInvalidArgumentReferenceCreatesDiagnostic() + public async Task InvalidArgumentReferenceCreatesError() { var source = @"var result = string.Format(""one {1}"", ""a"");".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormatArgs.ToDiagnosticId(), - Message = StringFormatArgsAnalyzer.InvalidArgsReferenceMessage, - Severity = DiagnosticSeverity.Error, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 30) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringFormatArgs_InvalidArgs.ToDiagnosticId(), DiagnosticSeverity.Error) + .WithLocation(10, 26) + .WithMessage(StringFormatArgsAnalyzer.InvalidArgsReferenceMessage); await VerifyCSharpDiagnosticAsync(source, expected); } [Fact] - public async Task MethodWithVeryInvalidArgumentReferenceCreatesDiagnostic() + public async Task NonIntegerPlaceholderCreatesError() { var source = @"var result = string.Format(""one {notZero}"", ""a"");".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormatArgs.ToDiagnosticId(), - Message = StringFormatArgsAnalyzer.InvalidArgsReferenceMessage, - Severity = DiagnosticSeverity.Error, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 30) } - }; + var expected = new DiagnosticResult(DiagnosticId.StringFormatArgs_InvalidArgs.ToDiagnosticId(), DiagnosticSeverity.Error) + .WithLocation(10, 26) + .WithMessage(StringFormatArgsAnalyzer.InvalidArgsReferenceMessage); + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task UnusedArgsCreatesWarning() + { + var source = @"string.Format(""{0}{1}{3}{5}"", ""a"", ""b"", ""c"", ""d"", ""e"", ""f"");".WrapInCSharpMethod(); + var expected = new DiagnosticResult(DiagnosticId.StringFormatArgs_ExtraArgs.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(10, 13) + .WithMessage(StringFormatArgsAnalyzer.IncorrectNumberOfArgsMessage); await VerifyCSharpDiagnosticAsync(source, expected); } } diff --git a/test/CSharp/CodeCracker.Test/Usage/UnusedParametersTests.cs b/test/CSharp/CodeCracker.Test/Usage/UnusedParametersTests.cs index b7857b7b8..2bee868b0 100644 --- a/test/CSharp/CodeCracker.Test/Usage/UnusedParametersTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/UnusedParametersTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -20,6 +21,30 @@ public void Foo() await VerifyCSharpHasNoDiagnosticsAsync(source); } + [Fact] + public async Task UsedParameterDoesNotCreateDiagnostic2() + { + const string source = @" +using System.Globalization; +using System.Reflection; + +namespace ClassLibrary1 +{ + public class Class1 + { + protected Class1() { } + + public static void SetDefaultThreadCulture(CultureInfo currentCulture, CultureInfo currentUICulture) + { + typeof(CultureInfo).InvokeMember(""s_userDefaultCulture"", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.SetField, null, null, new object[] { currentCulture }, CultureInfo.InvariantCulture); + typeof(CultureInfo).InvokeMember(""s_userDefaultUICulture"", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.SetField, null, null, new object[] { currentUICulture }, CultureInfo.InvariantCulture); + } + } +} +"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + [Fact] public async Task UsedParameterDoesNotCreateDiagnostic() { @@ -34,6 +59,39 @@ public int Foo(int a) await VerifyCSharpHasNoDiagnosticsAsync(source); } + [Fact] + public async Task UsedParameterWithVerbatimIdentifierDoesNotCreateDiagnostic() + { + var source = @" +public int Foo(int @a) +{ + return a; +}".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task UsedParameterUsedWithVerbatimIdentifierDoesNotCreateDiagnostic() + { + var source = @" +public int Foo(int a) +{ + return @a; +}".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task UsedParameterWithVerbatimIdentifierUsedWithVerbatimIdentifierDoesNotCreateDiagnostic() + { + var source = @" +public int Foo(int @a) +{ + return @a; +}".WrapInCSharpClass(); + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + [Fact] public async Task MethodWithoutStatementsCreatesDiagnostic() { @@ -273,13 +331,9 @@ await VerifyCSharpDiagnosticAsync(source, public static DiagnosticResult CreateDiagnosticResult(string parameterName, int line, int column) { - return new DiagnosticResult - { - Id = DiagnosticId.UnusedParameters.ToDiagnosticId(), - Message = string.Format(UnusedParametersAnalyzer.Message, parameterName), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", line, column) } - }; + return new DiagnosticResult(DiagnosticId.UnusedParameters.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(line, column) + .WithMessage(string.Format(UnusedParametersAnalyzer.Message, parameterName)); } [Fact] @@ -312,6 +366,134 @@ public int Foo(int a) await VerifyCSharpFixAsync(source, fixtest); } + [Fact] + public async Task FixParams() + { + const string source = @" +class TypeName +{ + public void IsReferencing() + { + Foo(1, 2, 3, 4); + } + public void Foo(int a, int b, params int[] c) + { + a = b; + } +}"; + const string fixtest = @" +class TypeName +{ + public void IsReferencing() + { + Foo(1, 2); + } + public void Foo(int a, int b) + { + a = b; + } +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixParamsWhenNotInUse() + { + const string source = @" +class TypeName +{ + public void IsReferencing() + { + Foo(1, 2); + } + public void Foo(int a, int b, params int[] c) + { + a = b; + } +}"; + const string fixtest = @" +class TypeName +{ + public void IsReferencing() + { + Foo(1, 2); + } + public void Foo(int a, int b) + { + a = b; + } +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixAllInSameClass() + { + const string source = @" +class TypeName +{ + public void IsReferencing() + { + Foo(1, 2, 3, 4); + } + public void Foo(int a, int b, params int[] c) + { + a = 1; + } +}"; + const string fixtest = @" +class TypeName +{ + public void IsReferencing() + { + Foo(1); + } + public void Foo(int a) + { + a = 1; + } +}"; + await VerifyCSharpFixAllAsync(source, fixtest); + } + + [Fact] + public async Task FixAllInDifferentClass() + { + const string source1 = @" +class TypeName +{ + public void IsReferencing() + { + Referenced.Foo(1, 2, 3, 4); + } +}"; + const string source2 = @" +class Referenced +{ + public static void Foo(int a, int b, params int[] c) + { + a = 1; + } +}"; + const string fix1 = @" +class TypeName +{ + public void IsReferencing() + { + Referenced.Foo(1); + } +}"; + const string fix2 = @" +class Referenced +{ + public static void Foo(int a) + { + a = 1; + } +}"; + await VerifyCSharpFixAllAsync(new[] { source1, source2 }, new[] { fix1, fix2 }); + } + [Fact] public async Task FixWhenTheParametersHasReferenceOnDifferentClass() { @@ -538,8 +720,194 @@ bool TryParse(string input, out int output, out int out2) await VerifyCSharpDiagnosticAsync(source, CreateDiagnosticResult("out2", 4, 49)); } + [Fact] + public async Task CallWithUnusedParameterExtensionMethodNoDiagnostic() + { + const string source = @" +static class C +{ + private static void Bar() + { + """".Foo(); + } + private static void Foo(this string s) + { + s += """"; } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } -} + [Fact] + public async Task CallWithUnusedParameterExtensionMethodCreateDiagnostic() + { + const string source = @" +static class C +{ + private static void Bar() + { + """".Foo(1); + } + private static void Foo(this string s, int i) + { + s += """"; + } +}"; + await VerifyCSharpDiagnosticAsync(source,CreateDiagnosticResult("i", 8, 44)); + } + [Fact] + public async Task CallWithUnusedParameterExtensionMethodFix() + { + const string source = @" +static class C +{ + private static void Bar() + { + """".Foo(1); + } + private static void Foo(this string s, int i) + { + s += """"; + } +}"; + + const string fixtest = @" +static class C +{ + private static void Bar() + { + """".Foo(); + } + private static void Foo(this string s) + { + s += """"; + } +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + [Fact] + public async Task CallWithUnusedTwoParameterExtensionMethodFix() + { + const string source = @" +static class C +{ + private static void Bar() + { + """".Foo(1,""a""); + } + private static void Foo(this string s, int i, string j) + { + s += i.ToString(); + } +}"; + + const string fixtest = @" +static class C +{ + private static void Bar() + { + """".Foo(1); + } + private static void Foo(this string s, int i) + { + s += i.ToString(); + } +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixAllWithNamedParametersInSameClass() + { + const string source = @" +public static class C +{ + public static void IsReferencing() + { + Foo(b: 2, a: 1); + } + public static void Foo(int a, int b) + { + a = 1; + } +}"; + const string fixtest = @" +public static class C +{ + public static void IsReferencing() + { + Foo(a: 1); + } + public static void Foo(int a) + { + a = 1; + } +}"; + await VerifyCSharpFixAllAsync(source, fixtest); + } + + [Fact] + public async Task WhenUsedAsMethodGroupDoesNotCreateDiagnostic() + { + const string source = @" +public class TypeName +{ + static void FireHandler(System.Func getInt) => getInt?.Invoke(1); + static void Init() => FireHandler(OnConfigFileChanged); + static int OnConfigFileChanged(int i) + { + return 1; + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task ExpressionBodiedMethodCreatesDiagnostic() + { + const string source = @" +public class TypeName +{ + static int Foo(int i) => 1; +}"; + await VerifyCSharpDiagnosticAsync(source, CreateDiagnosticResult("i", 4, 20)); + } + + [Fact] + public async Task FixExpressionBodiedMethod() + { + const string source = @" +public class TypeName +{ + static int Foo(int i) => 1; +}"; + const string fixtest = @" +public class TypeName +{ + static int Foo() => 1; +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + /// + /// Virtual methods should be ignored by the analyzer, because variables don't need + /// to be actually used by the base class and still serve a legit purpose. + /// + [Fact] + public async Task VirtualMethodsShouldBeIgnored() + { + + const string source = @" +public class BaseClass +{ + protected virtual void PreProcess(string data) + { + // no real action in base class + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + } +} \ No newline at end of file diff --git a/test/CSharp/CodeCracker.Test/Usage/UriAnalyzerTests.cs b/test/CSharp/CodeCracker.Test/Usage/UriAnalyzerTests.cs index 8a947ea9a..e48b9a1f8 100644 --- a/test/CSharp/CodeCracker.Test/Usage/UriAnalyzerTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/UriAnalyzerTests.cs @@ -3,6 +3,7 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; using Xunit; namespace CodeCracker.Test.CSharp.Usage @@ -93,13 +94,9 @@ public void Test() { private static DiagnosticResult CreateDiagnosticResult(int line, int column, Action getErrorMessageAction) { - return new DiagnosticResult - { - Id = DiagnosticId.Uri.ToDiagnosticId(), - Message = GetErrorMessage(getErrorMessageAction), - Severity = DiagnosticSeverity.Error, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", line, column) } - }; + return new DiagnosticResult(DiagnosticId.Uri.ToDiagnosticId(), DiagnosticSeverity.Error) + .WithLocation(line, column) + .WithMessage(GetErrorMessage(getErrorMessageAction)); } private static string GetErrorMessage(Action action) diff --git a/test/CSharp/CodeCracker.Test/Usage/VirtualMethodOnConstructorTests.cs b/test/CSharp/CodeCracker.Test/Usage/VirtualMethodOnConstructorTests.cs index fd76931c3..1c3800de0 100644 --- a/test/CSharp/CodeCracker.Test/Usage/VirtualMethodOnConstructorTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/VirtualMethodOnConstructorTests.cs @@ -2,6 +2,7 @@ using CodeCracker.CSharp.Usage; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; using Xunit; namespace CodeCracker.Test.CSharp.Usage { @@ -11,22 +12,19 @@ public class VirtualMethodOnConstructorTests : DiagnosticVerifier { public async Task IfVirtualMethodFoundInConstructorCreatesDiagnostic() { const string test = @" public class Person -{{ +{ public Person(string foo) - {{ + { DoFoo(foo); - }} + } public virtual void DoFoo(string foo) - {{ - }} -}}"; - var expected = new DiagnosticResult { - Id = DiagnosticId.VirtualMethodOnConstructor.ToDiagnosticId(), - Message = VirtualMethodOnConstructorAnalyzer.Message, - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 3) } - }; + { + } +}"; + var expected = new DiagnosticResult(DiagnosticId.VirtualMethodOnConstructor.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(6, 3) + .WithMessage(VirtualMethodOnConstructorAnalyzer.Message); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -35,22 +33,19 @@ public virtual void DoFoo(string foo) public async Task IfVirtualMethodWithThisFoundInConstructorCreatesDiagnostic() { const string test = @" public class Person -{{ +{ public Person(string foo) - {{ + { this.DoFoo(foo); - }} + } public virtual void DoFoo(string foo) - {{ - }} -}}"; - var expected = new DiagnosticResult { - Id = DiagnosticId.VirtualMethodOnConstructor.ToDiagnosticId(), - Message = VirtualMethodOnConstructorAnalyzer.Message, - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 3) } - }; + { + } +}"; + var expected = new DiagnosticResult(DiagnosticId.VirtualMethodOnConstructor.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(6, 3) + .WithMessage(VirtualMethodOnConstructorAnalyzer.Message); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -60,20 +55,19 @@ public virtual void DoFoo(string foo) public async Task IfVirtualMethodFoundFromOtherClassInConstructorDoNotCreateDiagnostic() { const string test = @" public class Book -{{ +{ public virtual void DoFoo(string foo) - {{ - }} -}} + { + } +} public class Person -{{ +{ public Person(string foo) - {{ + { var b = new Book(); b.DoFoo(foo); - }} -}} -"; + } +}"; await VerifyCSharpHasNoDiagnosticsAsync(test); } @@ -81,16 +75,15 @@ public Person(string foo) public async Task IfVirtualMethodNotFoundInConstructorDoNotCreateDiagnostic() { const string test = @" public class Person -{{ +{ public Person(string foo) - {{ + { DoFoo(foo); - }} - + } public void DoFoo(string foo) - {{ - }} -}}"; + { + } +}"; await VerifyCSharpHasNoDiagnosticsAsync(test); } @@ -98,34 +91,26 @@ public void DoFoo(string foo) public async Task IfManyVirtualMethodFoundInConstructorCreatesDiagnostics() { const string test = @" public class Person -{{ +{ public Person(string foo) - {{ + { DoFoo(foo); DoFoo2(foo); - }} + } public virtual void DoFoo(string foo) - {{ - }} + { + } public virtual void DoFoo2(string foo) - {{ - }} -}}"; - var expected = new DiagnosticResult { - Id = DiagnosticId.VirtualMethodOnConstructor.ToDiagnosticId(), - Message = VirtualMethodOnConstructorAnalyzer.Message, - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 3) } - }; - var expected2 = new DiagnosticResult { - Id = DiagnosticId.VirtualMethodOnConstructor.ToDiagnosticId(), - Message = VirtualMethodOnConstructorAnalyzer.Message, - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 3) } - }; - - + { + } +}"; + var expected = new DiagnosticResult(DiagnosticId.VirtualMethodOnConstructor.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(6, 3) + .WithMessage(VirtualMethodOnConstructorAnalyzer.Message); + var expected2 = new DiagnosticResult(DiagnosticId.VirtualMethodOnConstructor.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(7, 3) + .WithMessage(VirtualMethodOnConstructorAnalyzer.Message); await VerifyCSharpDiagnosticAsync(test, expected, expected2); } @@ -137,12 +122,26 @@ protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() { public async Task IfNameOfFoundInConstructorDoesNotCreateDiagnostic() { const string test = @" public class Person -{{ +{ public Person(string name) - {{ + { throw new System.ArgumentOutOfRangeException(nameof(name), """"); - }} -}}"; + } +}"; + await VerifyCSharpHasNoDiagnosticsAsync(test); + } + + [Fact] + public async Task IgnoreParameters() { + const string test = @" +using System; +class Foo +{ + public Foo(Func bar) + { + bar(); + } +}"; await VerifyCSharpHasNoDiagnosticsAsync(test); } } diff --git a/test/CSharp/CodeCracker.Test/packages.config b/test/CSharp/CodeCracker.Test/packages.config deleted file mode 100644 index 8bb09d28f..000000000 --- a/test/CSharp/CodeCracker.Test/packages.config +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/Common/CodeCracker.Test.Common/CSharpCodeFixVerifier`2+Test.cs b/test/Common/CodeCracker.Test.Common/CSharpCodeFixVerifier`2+Test.cs new file mode 100644 index 000000000..00993b8c5 --- /dev/null +++ b/test/Common/CodeCracker.Test.Common/CSharpCodeFixVerifier`2+Test.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace CodeCracker.Test +{ + public static partial class CSharpCodeFixVerifier + { + public class Test : CodeFixTest + { + public override string Language => LanguageNames.CSharp; + + protected override string DefaultFileExt => "cs"; + + protected override CompilationOptions CreateCompilationOptions() + => new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true); + + protected override IEnumerable GetDiagnosticAnalyzers() + { + yield return new TAnalyzer(); + } + + protected override IEnumerable GetCodeFixProviders() + { + yield return new TCodeFix(); + } + } + } +} diff --git a/test/Common/CodeCracker.Test.Common/CSharpCodeFixVerifier`2.cs b/test/Common/CodeCracker.Test.Common/CSharpCodeFixVerifier`2.cs new file mode 100644 index 000000000..7e64e1b0c --- /dev/null +++ b/test/Common/CodeCracker.Test.Common/CSharpCodeFixVerifier`2.cs @@ -0,0 +1,49 @@ +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace CodeCracker.Test +{ + public static partial class CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + public static DiagnosticResult Diagnostic() + => CSharpCodeFixVerifier.Diagnostic(); + + public static DiagnosticResult Diagnostic(string diagnosticId) + => CSharpCodeFixVerifier.Diagnostic(diagnosticId); + + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => new DiagnosticResult(descriptor); + + public static Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test { TestCode = source }; + test.ExpectedDiagnostics.AddRange(expected); + return test.RunAsync(); + } + + public static Task VerifyCodeFixAsync(string source, string fixedSource) + => VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + + public static Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) + => VerifyCodeFixAsync(source, new[] { expected }, fixedSource); + + public static Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + return test.RunAsync(); + } + } +} diff --git a/test/Common/CodeCracker.Test.Common/ChangeCulture.cs b/test/Common/CodeCracker.Test.Common/ChangeCulture.cs index 635ee1d5d..afa4b3536 100644 --- a/test/Common/CodeCracker.Test.Common/ChangeCulture.cs +++ b/test/Common/CodeCracker.Test.Common/ChangeCulture.cs @@ -1,24 +1,38 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Globalization; +using System.Threading; namespace CodeCracker.Test { public class ChangeCulture : IDisposable { + private readonly CultureInfo originalCulture; + private readonly CultureInfo originalUICulture; + private readonly CultureInfo originalDefaultCulture; + private readonly CultureInfo originalDefaultUICulture; + public ChangeCulture(string cultureName) { - System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo(cultureName); - System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo(cultureName); + originalCulture = Thread.CurrentThread.CurrentCulture; + originalUICulture = Thread.CurrentThread.CurrentUICulture; + originalDefaultCulture = CultureInfo.DefaultThreadCurrentCulture; + originalDefaultUICulture = CultureInfo.DefaultThreadCurrentUICulture; + + Thread.CurrentThread.CurrentCulture = + Thread.CurrentThread.CurrentUICulture = + CultureInfo.DefaultThreadCurrentCulture = + CultureInfo.DefaultThreadCurrentUICulture = + CultureInfo.GetCultureInfo(cultureName); + } public void Dispose() { - System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture; - System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture; + Thread.CurrentThread.CurrentCulture = originalCulture; + Thread.CurrentThread.CurrentUICulture = originalUICulture; + CultureInfo.DefaultThreadCurrentCulture = originalDefaultCulture; + CultureInfo.DefaultThreadCurrentUICulture = originalDefaultUICulture; GC.SuppressFinalize(this); } } -} +} \ No newline at end of file diff --git a/test/Common/CodeCracker.Test.Common/CodeCracker.Test.Common.csproj b/test/Common/CodeCracker.Test.Common/CodeCracker.Test.Common.csproj index 4775e87f6..ab8625db9 100644 --- a/test/Common/CodeCracker.Test.Common/CodeCracker.Test.Common.csproj +++ b/test/Common/CodeCracker.Test.Common/CodeCracker.Test.Common.csproj @@ -1,7 +1,5 @@  - - - + Debug @@ -16,6 +14,7 @@ + AD0001 true @@ -36,129 +35,36 @@ 4 - - ..\..\..\packages\FluentAssertions.3.4.1\lib\net45\FluentAssertions.dll - True - - - ..\..\..\packages\FluentAssertions.3.4.1\lib\net45\FluentAssertions.Core.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.Workspaces.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.VisualBasic.1.0.0\lib\net45\Microsoft.CodeAnalysis.VisualBasic.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.VisualBasic.Workspaces.1.0.0\lib\net45\Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.Desktop.dll - True - - - ..\..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll - True - - - ..\..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll - True - - - ..\..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll - True - - - ..\..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll - True - - - ..\..\..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll - True - - - ..\..\..\packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll - True - + + + + + + + + + + + - - - - - Designer - - - - - - - + + - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/test/Common/CodeCracker.Test.Common/EmptyAnalyzer.cs b/test/Common/CodeCracker.Test.Common/EmptyAnalyzer.cs new file mode 100644 index 000000000..0d1f5200e --- /dev/null +++ b/test/Common/CodeCracker.Test.Common/EmptyAnalyzer.cs @@ -0,0 +1,17 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace CodeCracker.Test +{ + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public class EmptyAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Empty; + + public override void Initialize(AnalysisContext context) + { + } + } +} diff --git a/test/Common/CodeCracker.Test.Common/Helpers/DiagnosticResult.cs b/test/Common/CodeCracker.Test.Common/Helpers/DiagnosticResult.cs deleted file mode 100644 index 46d38366e..000000000 --- a/test/Common/CodeCracker.Test.Common/Helpers/DiagnosticResult.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Microsoft.CodeAnalysis; -using System; - -namespace CodeCracker.Test -{ - /// - /// Location where the diagnostic appears, as determined by path, line number, and column number. - /// - public struct DiagnosticResultLocation - { - public DiagnosticResultLocation(string path, int line, int column) - { - if (line < 0 && column < 0) - { - throw new ArgumentOutOfRangeException("At least one of line and column must be > 0"); - } - if (line < -1 || column < -1) - { - throw new ArgumentOutOfRangeException("Both line and column must be >= -1"); - } - - Path = path; - Line = line; - Column = column; - } - - public string Path { get; set; } - public int Line { get; set; } - public int Column { get; set; } - } - - /// - /// Struct that stores information about a Diagnostic appearing in a source - /// - public struct DiagnosticResult - { - private DiagnosticResultLocation[] locations; - - public DiagnosticResultLocation[] Locations - { - get - { - if (locations == null) - { - locations = new DiagnosticResultLocation[] { }; - } - return locations; - } - - set - { - locations = value; - } - } - - public DiagnosticSeverity Severity { get; set; } - - public string Id { get; set; } - - public string Message { get; set; } - - public string Path - { - get - { - return Locations.Length > 0 ? Locations[0].Path : ""; - } - } - - public int Line - { - get - { - return Locations.Length > 0 ? Locations[0].Line : -1; - } - } - - public int Column - { - get - { - return Locations.Length > 0 ? Locations[0].Column : -1; - } - } - } -} \ No newline at end of file diff --git a/test/Common/CodeCracker.Test.Common/Helpers/DiagnosticVerifier.Helper.cs b/test/Common/CodeCracker.Test.Common/Helpers/DiagnosticVerifier.Helper.cs index e8866d67f..4a40a1eed 100644 --- a/test/Common/CodeCracker.Test.Common/Helpers/DiagnosticVerifier.Helper.cs +++ b/test/Common/CodeCracker.Test.Common/Helpers/DiagnosticVerifier.Helper.cs @@ -9,7 +9,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Threading; using System.Threading.Tasks; namespace CodeCracker.Test @@ -27,12 +26,12 @@ public abstract partial class DiagnosticVerifier private static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location); private static readonly MetadataReference JsonNetReference = MetadataReference.CreateFromFile(typeof(JsonConvert).Assembly.Location); - internal static string DefaultFilePathPrefix = nameof(Test); - internal static string CSharpDefaultFileExt = "cs"; - internal static string VisualBasicDefaultExt = "vb"; - internal static string CSharpDefaultFilePath = DefaultFilePathPrefix + 0 + "." + CSharpDefaultFileExt; - internal static string VisualBasicDefaultFilePath = DefaultFilePathPrefix + 0 + "." + VisualBasicDefaultExt; - internal static string TestProjectName = "TestProject"; + internal static readonly string DefaultFilePathPrefix = nameof(Test); + internal static readonly string CSharpDefaultFileExt = "cs"; + internal static readonly string VisualBasicDefaultExt = "vb"; + internal static readonly string CSharpDefaultFilePath = DefaultFilePathPrefix + 0 + "." + CSharpDefaultFileExt; + internal static readonly string VisualBasicDefaultFilePath = DefaultFilePathPrefix + 0 + "." + VisualBasicDefaultExt; + internal static readonly string TestProjectName = "TestProject"; /// /// Given classes in the form of strings, their language, and an IDiagnosticAnlayzer to apply to it, return the diagnostics found in the string after converting it to a document. @@ -102,7 +101,7 @@ private static Diagnostic[] SortDiagnostics(List diagnostics) => #region Set up compilation and documents /// - /// Given an array of strings as soruces and a language, turn them into a project and return the documents and spans of it. + /// Given an array of strings as sources and a language, turn them into a project and return the documents and spans of it. /// /// Classes in the form of strings /// The language the source code is in diff --git a/test/Common/CodeCracker.Test.Common/Helpers/Extensions.cs b/test/Common/CodeCracker.Test.Common/Helpers/Extensions.cs index bdc0b912f..7b6e955ab 100644 --- a/test/Common/CodeCracker.Test.Common/Helpers/Extensions.cs +++ b/test/Common/CodeCracker.Test.Common/Helpers/Extensions.cs @@ -2,36 +2,60 @@ { public static class Extensions { - public static string WrapInCSharpClass(this string code, string typeName = "TypeName") + public static string WrapInCSharpClass(this string code, string typeName = "TypeName", string usings = "") { + if (!code.StartsWith("\r") || code.StartsWith("\n")) + { + code = " " + code; + } + return $@" - using System; +using System;{usings} - namespace ConsoleApplication1 +namespace ConsoleApplication1 +{{ + class {typeName} {{ - class {typeName} - {{ - {code} - }} - }}"; +{code} + }} +}}"; } public static string WrapInCSharpMethod(this string code, bool isAsync = false, string typeName = "TypeName", string usings = "") { + if (!code.StartsWith("\r") || code.StartsWith("\n")) + { + code = " " + code; + } + return $@" - using System;{usings} +using System;{usings} - namespace ConsoleApplication1 +namespace ConsoleApplication1 +{{ + class {typeName} {{ - class {typeName} + public {(isAsync ? "async " : "")}void Foo() {{ - public {(isAsync ? "async " : "")}void Foo() - {{ - {code} - }} +{code} }} - }}"; + }} +}}"; } + + public static string WrapInVBClass(this string code, + string typeName = "TypeName", + string imports = "") + { + return $@" +Imports System{imports} +Namespace ConsoleApplication1 + Class {typeName} + {code} + End Class +End Namespace"; + } + public static string WrapInVBMethod(this string code, bool isAsync = false, string typeName = "TypeName", diff --git a/test/Common/CodeCracker.Test.Common/Properties/AssemblyInfo.cs b/test/Common/CodeCracker.Test.Common/Properties/AssemblyInfo.cs index d3cd6086a..c0189f778 100644 --- a/test/Common/CodeCracker.Test.Common/Properties/AssemblyInfo.cs +++ b/test/Common/CodeCracker.Test.Common/Properties/AssemblyInfo.cs @@ -8,10 +8,10 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CodeCracker")] -[assembly: AssemblyCopyright("Copyright © 2014-2015")] +[assembly: AssemblyCopyright("Copyright © 2014-2018")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: NeutralResourcesLanguage("en-us")] [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("1.0.0.10")] +[assembly: AssemblyFileVersion("1.1.0.0")] diff --git a/test/Common/CodeCracker.Test.Common/Verifiers/CodeFixVerifier.cs b/test/Common/CodeCracker.Test.Common/Verifiers/CodeFixVerifier.cs index 6a263e004..5603da4c6 100644 --- a/test/Common/CodeCracker.Test.Common/Verifiers/CodeFixVerifier.cs +++ b/test/Common/CodeCracker.Test.Common/Verifiers/CodeFixVerifier.cs @@ -1,4 +1,5 @@ -using Microsoft.CodeAnalysis; +using FluentAssertions; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; @@ -110,18 +111,14 @@ private async static Task VerifyFixAsync(string language, DiagnosticAnalyzer ana if (!actions.Any()) break; - if (codeFixIndex != null) - { - document = await ApplyFixAsync(document, actions.ElementAt((int)codeFixIndex)).ConfigureAwait(true); - break; - } + document = codeFixIndex != null + ? await ApplyFixAsync(document, actions.ElementAt((int)codeFixIndex)).ConfigureAwait(true) + : await ApplyFixAsync(document, actions.ElementAt(0)).ConfigureAwait(true); - document = await ApplyFixAsync(document, actions.ElementAt(0)).ConfigureAwait(true); analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzer, new[] { document }).ConfigureAwait(true); var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true)); - //check if applying the code fix introduced any new compiler diagnostics if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) { @@ -156,13 +153,9 @@ private async static Task VerifyFixAsync(string language, ImmutableArray if (!actions.Any()) break; - if (codeFixIndex != null) - { - document = await ApplyFixAsync(document, actions.ElementAt((int)codeFixIndex)).ConfigureAwait(true); - break; - } - - document = await ApplyFixAsync(document, actions.ElementAt(0)).ConfigureAwait(true); + document = codeFixIndex != null + ? await ApplyFixAsync(document, actions.ElementAt((int)codeFixIndex)).ConfigureAwait(true) + : await ApplyFixAsync(document, actions.ElementAt(0)).ConfigureAwait(true); var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true)); compilerDiagnostics = (await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true)).ToList(); @@ -326,7 +319,46 @@ private async static Task VerifyFixAllAsync(string language, DiagnosticAnalyzer { var document = docs[i]; var actual = await GetStringFromDocumentAsync(document).ConfigureAwait(true); - Assert.Equal(newSources[i], actual); + newSources[i].Should().Be(actual); + } + } + + /// + /// Called to verify how many code actions a code fix registered + /// Creates a Document from the source string, then gets diagnostics on it and verify if it has x number of code actions registred. + /// It will fail the test if it has a different number of code actions registred to it. + /// + /// A class in the form of a string before the CodeFix was applied to it + /// The codefix to be applied to the code wherever the relevant Diagnostic is found + /// C# language version used for compiling the test project, required unless you inform the VB language version. + /// The expected number of code actions provided by the code fix. + protected async Task VerifyCSharpHasNumberOfCodeActionsAsync(string source, int numberOfCodeActions, CodeFixProvider codeFixProvider = null, LanguageVersion languageVersionCSharp = LanguageVersion.CSharp6) => + await VerifyNumberOfCodeActionsAsync(LanguageNames.CSharp, GetDiagnosticAnalyzer(), codeFixProvider ?? GetCodeFixProvider(), source, numberOfCodeActions, languageVersionCSharp, Microsoft.CodeAnalysis.VisualBasic.LanguageVersion.VisualBasic14).ConfigureAwait(true); + + /// + /// General verifier for a diagnostics that verifies how many code actions a code fix registered. + /// Creates a Document from the source string, then gets diagnostics on it and verify if it has x number of code actions registred. + /// It will fail the test if it has a different number of code actions registred to it. + /// + /// The language the source code is in + /// The analyzer to be applied to the source code + /// The codefix to be applied to the code wherever the relevant Diagnostic is found + /// A class in the form of a string before the CodeFix was applied to it + /// C# language version used for compiling the test project, required unless you inform the VB language version. + /// VB language version used for compiling the test project, required unless you inform the C# language version. + /// The expected number of code actions provided by the code fix. + private async static Task VerifyNumberOfCodeActionsAsync(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string source, int numberOfCodeActions, LanguageVersion languageVersionCSharp, Microsoft.CodeAnalysis.VisualBasic.LanguageVersion languageVersionVB) + { + var document = CreateDocument(source, language, languageVersionCSharp, languageVersionVB); + var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzer, new[] { document }).ConfigureAwait(true); + + foreach (var analyzerDiagnostic in analyzerDiagnostics) + { + var actions = new List(); + var context = new CodeFixContext(document, analyzerDiagnostic, (a, d) => actions.Add(a), CancellationToken.None); + await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(true); + var numberOfCodeActionsFound = actions.Count(); + Assert.True(numberOfCodeActions == numberOfCodeActionsFound, $"Should have {numberOfCodeActions} code actions registered for diagnostic '{analyzerDiagnostic.Id}' but got {numberOfCodeActionsFound}."); } } diff --git a/test/Common/CodeCracker.Test.Common/Verifiers/DiagnosticVerifier.cs b/test/Common/CodeCracker.Test.Common/Verifiers/DiagnosticVerifier.cs index c01eee989..d692a8e10 100644 --- a/test/Common/CodeCracker.Test.Common/Verifiers/DiagnosticVerifier.cs +++ b/test/Common/CodeCracker.Test.Common/Verifiers/DiagnosticVerifier.cs @@ -1,6 +1,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; using System.Collections.Generic; using System.Linq; using System.Text; @@ -97,8 +98,8 @@ protected async Task VerifyCSharpDiagnosticAsync(string[] sources, DiagnosticRes /// An array of strings to create source documents from to run the analyzers on /// DiagnosticResults that should appear after the analyzer is run on the sources /// The VB language version, defaults to the latest stable version. - protected async Task VerifyBasicDiagnosticAsync(string[] sources, DiagnosticResult[] expected, Microsoft.CodeAnalysis.VisualBasic.LanguageVersion languageVersion) => - await VerifyDiagnosticsAsync(sources, LanguageNames.VisualBasic, GetDiagnosticAnalyzer(), expected, LanguageVersion.CSharp6, Microsoft.CodeAnalysis.VisualBasic.LanguageVersion.VisualBasic14).ConfigureAwait(true); + protected async Task VerifyBasicDiagnosticAsync(string[] sources, DiagnosticResult[] expected, Microsoft.CodeAnalysis.VisualBasic.LanguageVersion languageVersion = Microsoft.CodeAnalysis.VisualBasic.LanguageVersion.VisualBasic14) => + await VerifyDiagnosticsAsync(sources, LanguageNames.VisualBasic, GetDiagnosticAnalyzer(), expected, LanguageVersion.CSharp6, languageVersion).ConfigureAwait(true); /// /// General method that gets a collection of actual diagnostics found in the source after the analyzer is run, @@ -113,7 +114,8 @@ protected async Task VerifyBasicDiagnosticAsync(string[] sources, DiagnosticResu private async static Task VerifyDiagnosticsAsync(string[] sources, string language, DiagnosticAnalyzer analyzer, DiagnosticResult[] expected, LanguageVersion languageVersionCSharp, Microsoft.CodeAnalysis.VisualBasic.LanguageVersion languageVersionVB) { var diagnostics = await GetSortedDiagnosticsAsync(sources, language, analyzer, languageVersionCSharp, languageVersionVB).ConfigureAwait(true); - VerifyDiagnosticResults(diagnostics, analyzer, expected); + var defaultFilePath = language == LanguageNames.CSharp ? CSharpDefaultFilePath : VisualBasicDefaultFilePath; + VerifyDiagnosticResults(diagnostics, analyzer, defaultFilePath, expected); } @@ -124,7 +126,7 @@ private async static Task VerifyDiagnosticsAsync(string[] sources, string langua /// The Diagnostics found by the compiler after running the analyzer on the source code /// The analyzer that was being run on the sources /// Diagnsotic Results that should have appeared in the code - private static void VerifyDiagnosticResults(IEnumerable actualResults, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expectedResults) + private static void VerifyDiagnosticResults(IEnumerable actualResults, DiagnosticAnalyzer analyzer, string defaultFilePath, params DiagnosticResult[] expectedResults) { var expectedCount = expectedResults.Length; var actualCount = actualResults.Count(); @@ -139,9 +141,9 @@ private static void VerifyDiagnosticResults(IEnumerable actualResult for (int i = 0; i < expectedResults.Length; i++) { var actual = actualResults.ElementAt(i); - var expected = expectedResults[i]; + var expected = expectedResults[i].WithDefaultPath(defaultFilePath); - if (expected.Line == -1 && expected.Column == -1) + if (!expected.HasLocation) { if (actual.Location != Location.None) { @@ -150,17 +152,17 @@ private static void VerifyDiagnosticResults(IEnumerable actualResult } else { - VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First()); + VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Spans.First()); var additionalLocations = actual.AdditionalLocations.ToArray(); - if (additionalLocations.Length != expected.Locations.Length - 1) + if (additionalLocations.Length != expected.Spans.Length - 1) { - Assert.True(false, $"Expected {expected.Locations.Length - 1} additional locations but got {additionalLocations.Length} for Diagnostic:\r\n {FormatDiagnostics(analyzer, actual)}\r\n"); + Assert.True(false, $"Expected {expected.Spans.Length - 1} additional locations but got {additionalLocations.Length} for Diagnostic:\r\n {FormatDiagnostics(analyzer, actual)}\r\n"); } for (int j = 0; j < additionalLocations.Length; ++j) { - VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Locations[j + 1]); + VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Spans[j + 1]); } } @@ -182,7 +184,7 @@ private static void VerifyDiagnosticResults(IEnumerable actualResult /// The diagnostic that was found in the code /// The Location of the Diagnostic found in the code /// The DiagnosticResultLocation that should have been found - private static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, Location actual, DiagnosticResultLocation expected) + private static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, Location actual, FileLinePositionSpan expected) { var actualSpan = actual.GetLineSpan(); @@ -193,13 +195,13 @@ private static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagno // Only check line position if there is an actual line in the real diagnostic if (actualLinePosition.Line > 0) - if (actualLinePosition.Line + 1 != expected.Line) - Assert.True(false, $"Expected diagnostic to be on line \"{expected.Line}\" was actually on line \"{actualLinePosition.Line + 1}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, diagnostic)}\r\n"); + if (actualLinePosition.Line != expected.StartLinePosition.Line) + Assert.True(false, $"Expected diagnostic to be on line \"{expected.StartLinePosition.Line + 1}\" was actually on line \"{actualLinePosition.Line + 1}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, diagnostic)}\r\n"); // Only check column position if there is an actual column position in the real diagnostic if (actualLinePosition.Character > 0) - if (actualLinePosition.Character + 1 != expected.Column) - Assert.True(false, $"Expected diagnostic to start at column \"{expected.Column}\" was actually at column \"{actualLinePosition.Character + 1}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, diagnostic)}\r\n"); + if (actualLinePosition.Character != expected.StartLinePosition.Character) + Assert.True(false, $"Expected diagnostic to start at column \"{expected.StartLinePosition.Character + 1}\" was actually at column \"{actualLinePosition.Character + 1}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, diagnostic)}\r\n"); } /// diff --git a/test/Common/CodeCracker.Test.Common/VisualBasicCodeFixVerifier`2+Test.cs b/test/Common/CodeCracker.Test.Common/VisualBasicCodeFixVerifier`2+Test.cs new file mode 100644 index 000000000..f41652de3 --- /dev/null +++ b/test/Common/CodeCracker.Test.Common/VisualBasicCodeFixVerifier`2+Test.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic; + +namespace CodeCracker.Test +{ + public static partial class VisualBasicCodeFixVerifier + { + public class Test : CodeFixTest + { + public override string Language => LanguageNames.VisualBasic; + + protected override string DefaultFileExt => "vb"; + + protected override CompilationOptions CreateCompilationOptions() + => new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + + protected override IEnumerable GetDiagnosticAnalyzers() + { + yield return new TAnalyzer(); + } + + protected override IEnumerable GetCodeFixProviders() + { + yield return new TCodeFix(); + } + } + } +} diff --git a/test/Common/CodeCracker.Test.Common/VisualBasicCodeFixVerifier`2.cs b/test/Common/CodeCracker.Test.Common/VisualBasicCodeFixVerifier`2.cs new file mode 100644 index 000000000..76964fad3 --- /dev/null +++ b/test/Common/CodeCracker.Test.Common/VisualBasicCodeFixVerifier`2.cs @@ -0,0 +1,49 @@ +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace CodeCracker.Test +{ + public static partial class VisualBasicCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + public static DiagnosticResult Diagnostic() + => VisualBasicCodeFixVerifier.Diagnostic(); + + public static DiagnosticResult Diagnostic(string diagnosticId) + => VisualBasicCodeFixVerifier.Diagnostic(diagnosticId); + + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => new DiagnosticResult(descriptor); + + public static Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test { TestCode = source }; + test.ExpectedDiagnostics.AddRange(expected); + return test.RunAsync(); + } + + public static Task VerifyCodeFixAsync(string source, string fixedSource) + => VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + + public static Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) + => VerifyCodeFixAsync(source, new[] { expected }, fixedSource); + + public static Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + return test.RunAsync(); + } + } +} diff --git a/test/Common/CodeCracker.Test.Common/packages.config b/test/Common/CodeCracker.Test.Common/packages.config deleted file mode 100644 index 690a853fe..000000000 --- a/test/Common/CodeCracker.Test.Common/packages.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/VisualBasic/CodeCracker.Test/CodeCracker.Test.vbproj b/test/VisualBasic/CodeCracker.Test/CodeCracker.Test.vbproj index 053ac1143..7f18764a2 100644 --- a/test/VisualBasic/CodeCracker.Test/CodeCracker.Test.vbproj +++ b/test/VisualBasic/CodeCracker.Test/CodeCracker.Test.vbproj @@ -1,8 +1,5 @@  - - - - + Debug @@ -17,6 +14,7 @@ + AD0001 true @@ -55,82 +53,16 @@ On - - ..\..\..\packages\FluentAssertions.3.4.1\lib\net45\FluentAssertions.dll - True - - - ..\..\..\packages\FluentAssertions.3.4.1\lib\net45\FluentAssertions.Core.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.VisualBasic.1.0.0\lib\net45\Microsoft.CodeAnalysis.VisualBasic.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.VisualBasic.Workspaces.1.0.0\lib\net45\Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.dll - True - - - ..\..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.Desktop.dll - True - - - ..\..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll - True - - - ..\..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll - True - - - ..\..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll - True - - - ..\..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll - True - - - ..\..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll - True - - - ..\..\..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll - True - - - ..\..\..\packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll - True - + + + + + + @@ -180,8 +112,8 @@ - - + + @@ -204,9 +136,6 @@ My Settings.Designer.vb - - Designer - @@ -225,26 +154,5 @@ CodeCracker.Test.Common - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - \ No newline at end of file diff --git a/test/VisualBasic/CodeCracker.Test/Design/EmptyCatchBlockTests.vb b/test/VisualBasic/CodeCracker.Test/Design/EmptyCatchBlockTests.vb index 0b25c0b04..704674e7c 100644 --- a/test/VisualBasic/CodeCracker.Test/Design/EmptyCatchBlockTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Design/EmptyCatchBlockTests.vb @@ -9,12 +9,12 @@ Namespace Design Imports System Namespace ConsoleApplication1 Class TypeName - Public Async Function Foo() As Task + Public Sub Foo() Try Dim a = ""A"" Catch End Try - End Function + End Sub End Class End Namespace" @@ -24,13 +24,13 @@ End Namespace" Imports System Namespace ConsoleApplication1 Class TypeName - Public Async Function Foo() As Task + Public Sub Foo() Try Dim a = ""A"" Catch Throw End Try - End Function + End Sub End Class End Namespace" @@ -43,9 +43,9 @@ End Namespace" Imports System Namespace ConsoleApplication1 Class TypeName - Public Async Function Foo() As Task + Public Sub Foo() Dim a = ""A"" - End Function + End Sub End Class End Namespace" @@ -58,17 +58,52 @@ End Namespace" Imports System Namespace ConsoleApplication1 Class TypeName - Public Async Function Foo() As Task + Public Sub Foo() Try Dim a = ""A"" Catch ex As Exception Throw End Try - End Function + End Sub End Class End Namespace" Await VerifyBasicFixAsync(test, fix, 1) End Function + + + Public Async Function WhenMultipleCatchRemoveOnlySelectedEmpty() As Task + Const multipleTest As String = " +Imports System +Namespace ConsoleApplication1 + Class TypeName + Public Async Function Foo() As Task + Try + Dim a = ""A"" + Catch aex As ArgumentException + Catch ex As Exception + a = ""B"" + End Try + End Function + End Class +End Namespace" + + Const multipleFix = " +Imports System +Namespace ConsoleApplication1 + Class TypeName + Public Async Function Foo() As Task + Try + Dim a = ""A"" + Catch ex As Exception + a = ""B"" + End Try + End Function + End Class +End Namespace" + + Await VerifyBasicFixAsync(multipleTest, multipleFix, 0) + + End Function End Class End Namespace \ No newline at end of file diff --git a/test/VisualBasic/CodeCracker.Test/Design/NameOfTests.vb b/test/VisualBasic/CodeCracker.Test/Design/NameOfTests.vb index 9b5fb7933..f5b70fa98 100644 --- a/test/VisualBasic/CodeCracker.Test/Design/NameOfTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Design/NameOfTests.vb @@ -1,4 +1,5 @@ Imports CodeCracker.VisualBasic.Design +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Design @@ -20,6 +21,18 @@ End Class" Await VerifyBasicHasNoDiagnosticsAsync(source) End Function + + Public Async Function WhenReferencingExternalSymbolShouldReportDiagnostic() As Task + Dim source = $" +Imports System +Public Class TypeName + Sub Foo() + Dim a = ""Action"" + End Sub +End Class" + Dim expected = CreateNameofDiagnosticResult("Actio" + "n", 5, 17, DiagnosticId.NameOf_External) + Await VerifyBasicDiagnosticAsync(source, expected) + End Function @@ -578,14 +591,10 @@ End Class" Await VerifyBasicHasNoDiagnosticsAsync(test) End Function - Private Function CreateNameofDiagnosticResult(nameofArgument As String, diagnosticLine As Integer, diagnosticColumn As Integer) As DiagnosticResult - Return New DiagnosticResult With - { - .Id = DiagnosticId.NameOf.ToDiagnosticId(), - .Message = $"Use 'NameOf({nameofArgument})' instead of specifying the program element name.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", diagnosticLine, diagnosticColumn)} - } + Private Function CreateNameofDiagnosticResult(nameofArgument As String, diagnosticLine As Integer, diagnosticColumn As Integer, Optional id As DiagnosticId = DiagnosticId.NameOf) As DiagnosticResult + Return New DiagnosticResult(id.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(diagnosticLine, diagnosticColumn) _ + .WithMessage($"Use 'NameOf({nameofArgument})' instead of specifying the program element name.") End Function End Class End Namespace \ No newline at end of file diff --git a/test/VisualBasic/CodeCracker.Test/Design/StaticConstructorExceptionTests.vb b/test/VisualBasic/CodeCracker.Test/Design/StaticConstructorExceptionTests.vb index 9501ea066..de4131a19 100644 --- a/test/VisualBasic/CodeCracker.Test/Design/StaticConstructorExceptionTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Design/StaticConstructorExceptionTests.vb @@ -1,5 +1,6 @@ Imports CodeCracker.VisualBasic.Design Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Testing Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports System.IO @@ -18,12 +19,9 @@ Public Class TestClass End Sub End Class" - Dim expected = New DiagnosticResult With { - .Id = DiagnosticId.StaticConstructorException.ToDiagnosticId(), - .Message = "Don't throw exceptions inside static constructors.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 4, 9)} - } + Dim expected = New DiagnosticResult(DiagnosticId.StaticConstructorException.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(4, 9) _ + .WithMessage("Don't throw exceptions inside static constructors.") Await VerifyBasicDiagnosticAsync(test, expected) End Function diff --git a/test/VisualBasic/CodeCracker.Test/My Project/AssemblyInfo.vb b/test/VisualBasic/CodeCracker.Test/My Project/AssemblyInfo.vb index 829f04730..d3b84acfd 100644 --- a/test/VisualBasic/CodeCracker.Test/My Project/AssemblyInfo.vb +++ b/test/VisualBasic/CodeCracker.Test/My Project/AssemblyInfo.vb @@ -8,10 +8,10 @@ Imports System.Runtime.InteropServices - + - + diff --git a/test/VisualBasic/CodeCracker.Test/Performance/MakeLocalVariablesConstWhenItIsPossibleTests.vb b/test/VisualBasic/CodeCracker.Test/Performance/MakeLocalVariablesConstWhenItIsPossibleTests.vb index b1f8d1987..aca8cb0da 100644 --- a/test/VisualBasic/CodeCracker.Test/Performance/MakeLocalVariablesConstWhenItIsPossibleTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Performance/MakeLocalVariablesConstWhenItIsPossibleTests.vb @@ -1,5 +1,6 @@ Imports CodeCracker.VisualBasic.Performance Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Performance @@ -39,39 +40,27 @@ Namespace Performance Public Async Function CreateDiagnosticsWhenAssigningAPotentialConstant() As Task Dim test = "Dim a As Integer = 10".WrapInVBMethod() - Dim expected = New DiagnosticResult With - { - .Id = MakeLocalVariableConstWhenPossibleAnalyzer.Id, - .Message = "This variable can be made const.", - .Severity = DiagnosticSeverity.Info, - .Locations = {New DiagnosticResultLocation("Test0.vb", 6, 13)} - } + Dim expected = New DiagnosticResult(MakeLocalVariableConstWhenPossibleAnalyzer.Id, DiagnosticSeverity.Info) _ + .WithLocation(6, 13) _ + .WithMessage("This variable can be made const.") Await VerifyBasicDiagnosticAsync(test, expected) End Function Public Async Function CreateDiagnosticsWhenAssigningAPotentialConstantUsingTypeInference() As Task Dim test = "Dim a = 10".WrapInVBMethod() - Dim expected = New DiagnosticResult With - { - .Id = MakeLocalVariableConstWhenPossibleAnalyzer.Id, - .Message = "This variable can be made const.", - .Severity = DiagnosticSeverity.Info, - .Locations = {New DiagnosticResultLocation("Test0.vb", 6, 13)} - } + Dim expected = New DiagnosticResult(MakeLocalVariableConstWhenPossibleAnalyzer.Id, DiagnosticSeverity.Info) _ + .WithLocation(6, 13) _ + .WithMessage("This variable can be made const.") Await VerifyBasicDiagnosticAsync(test, expected) End Function Public Async Function CreateDiagnosticsWhenAssigningNothingToAReferenceType() As Task Dim test = "Dim a As Foo = Nothing".WrapInVBMethod() - Dim expected = New DiagnosticResult With - { - .Id = MakeLocalVariableConstWhenPossibleAnalyzer.Id, - .Message = "This variable can be made const.", - .Severity = DiagnosticSeverity.Info, - .Locations = {New DiagnosticResultLocation("Test0.vb", 6, 13)} - } + Dim expected = New DiagnosticResult(MakeLocalVariableConstWhenPossibleAnalyzer.Id, DiagnosticSeverity.Info) _ + .WithLocation(6, 13) _ + .WithMessage("This variable can be made const.") Await VerifyBasicDiagnosticAsync(test, expected) End Function diff --git a/test/VisualBasic/CodeCracker.Test/Performance/RemoveWhereWhenItIsPossibleTests.vb b/test/VisualBasic/CodeCracker.Test/Performance/RemoveWhereWhenItIsPossibleTests.vb index ce396c0d5..e4176f42e 100644 --- a/test/VisualBasic/CodeCracker.Test/Performance/RemoveWhereWhenItIsPossibleTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Performance/RemoveWhereWhenItIsPossibleTests.vb @@ -1,4 +1,5 @@ Imports CodeCracker.VisualBasic.Performance +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Performance @@ -26,13 +27,9 @@ Namespace Sample End Class End Namespace" - Dim expected = New DiagnosticResult With - { - .Id = RemoveWhereWhenItIsPossibleAnalyzer.Id, - .Message = "You can remove 'Where' moving the predicate to '" + method + "'.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 7, 23)} - } + Dim expected = New DiagnosticResult(RemoveWhereWhenItIsPossibleAnalyzer.Id, Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(7, 23) _ + .WithMessage("You can remove 'Where' moving the predicate to '" + method + "'.") Await VerifyBasicDiagnosticAsync(test, expected) End Function diff --git a/test/VisualBasic/CodeCracker.Test/Performance/SealedAttributeTests.vb b/test/VisualBasic/CodeCracker.Test/Performance/SealedAttributeTests.vb index ec0f627bb..23c231e93 100644 --- a/test/VisualBasic/CodeCracker.Test/Performance/SealedAttributeTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Performance/SealedAttributeTests.vb @@ -1,4 +1,5 @@ Imports CodeCracker.VisualBasic.Performance +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Performance @@ -12,13 +13,9 @@ Public Class MyAttribute Inherits System.Attribute End Class" - Dim expected = New DiagnosticResult With - { - .Id = SealedAttributeAnalyzer.Id, - .Message = "Mark 'MyAttribute' as NotInheritable.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 2, 14)} - } + Dim expected = New DiagnosticResult(SealedAttributeAnalyzer.Id, Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(2, 14) _ + .WithMessage("Mark 'MyAttribute' as NotInheritable.") Await VerifyBasicDiagnosticAsync(test, expected) @@ -35,13 +32,9 @@ Public Class OtherAttribute Inherits MyAttribute End Class" - Dim expected = New DiagnosticResult With - { - .Id = SealedAttributeAnalyzer.Id, - .Message = "Mark 'OtherAttribute' as NotInheritable.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 6, 14)} - } + Dim expected = New DiagnosticResult(SealedAttributeAnalyzer.Id, Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(6, 14) _ + .WithMessage("Mark 'OtherAttribute' as NotInheritable.") Await VerifyBasicDiagnosticAsync(test, expected) End Function diff --git a/test/VisualBasic/CodeCracker.Test/Performance/StringBuilderInLoopTests.vb b/test/VisualBasic/CodeCracker.Test/Performance/StringBuilderInLoopTests.vb index c23032ab9..01d6f79a7 100644 --- a/test/VisualBasic/CodeCracker.Test/Performance/StringBuilderInLoopTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Performance/StringBuilderInLoopTests.vb @@ -1,4 +1,5 @@ Imports CodeCracker.VisualBasic.Performance +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Performance @@ -36,7 +37,7 @@ End Namespace" Public Async Function WhileWithoutStringConcatWithMethoParameterDoesNotCreateDiagnostic() As Task - Dim source = " + Const source = " Public Class TypeName Public Sub Looper(ByRef a As Integer) While a < 10 @@ -75,19 +76,16 @@ Namespace ConsoleApplication1 End Class End Namespace" - Dim expected As DiagnosticResult = GetExpected() - expected.Locations(0).Line = 7 - expected.Locations(0).Column = 17 + Dim expected = New DiagnosticResult(StringBuilderInLoopAnalyzer.Id, Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(7, 17) _ + .WithMessage(String.Format(StringBuilderInLoopAnalyzer.MessageFormat, "a")) Await VerifyBasicDiagnosticAsync(source, expected) End Function Private Shared Function GetExpected() As DiagnosticResult - Return New DiagnosticResult With { - .Id = StringBuilderInLoopAnalyzer.Id, - .Message = String.Format(StringBuilderInLoopAnalyzer.MessageFormat, "a"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 9, 17)} - } + Return New DiagnosticResult(StringBuilderInLoopAnalyzer.Id, Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(9, 17) _ + .WithMessage(String.Format(StringBuilderInLoopAnalyzer.MessageFormat, "a")) End Function @@ -116,9 +114,9 @@ Namespace ConsoleApplication1 End Class End Namespace" - Dim expected As DiagnosticResult = GetExpected() - expected.Locations(0).Line = 7 - expected.Locations(0).Column = 17 + Dim expected = New DiagnosticResult(StringBuilderInLoopAnalyzer.Id, Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(7, 17) _ + .WithMessage(String.Format(StringBuilderInLoopAnalyzer.MessageFormat, "a")) Await VerifyBasicDiagnosticAsync(source, expected) End Function @@ -134,19 +132,12 @@ End Namespace" Console.WriteLine(myString2) ".WrapInVBMethod() - Dim expected1 As New DiagnosticResult With { - .Id = StringBuilderInLoopAnalyzer.Id, - .Message = String.Format(StringBuilderInLoopAnalyzer.MessageFormat, "a"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 10, 17)} - } - - Dim expected2 As New DiagnosticResult With { - .Id = StringBuilderInLoopAnalyzer.Id, - .Message = String.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString2"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 11, 17)} - } + Dim expected1 = New DiagnosticResult(StringBuilderInLoopAnalyzer.Id, Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(10, 17) _ + .WithMessage(String.Format(StringBuilderInLoopAnalyzer.MessageFormat, "a")) + Dim expected2 = New DiagnosticResult(StringBuilderInLoopAnalyzer.Id, Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(11, 17) _ + .WithMessage(String.Format(StringBuilderInLoopAnalyzer.MessageFormat, "myString2")) Await VerifyBasicDiagnosticAsync(source, expected1, expected2) End Function @@ -392,13 +383,9 @@ End Namespace" a += """" Next".WrapInVBMethod - Dim expected As New DiagnosticResult With - { - .Id = StringBuilderInLoopAnalyzer.Id, - .Message = String.Format(StringBuilderInLoopAnalyzer.MessageFormat, "a"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 9, 17)} - } + Dim expected = New DiagnosticResult(StringBuilderInLoopAnalyzer.Id, Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(9, 17) _ + .WithMessage(String.Format(StringBuilderInLoopAnalyzer.MessageFormat, "a")) Await VerifyBasicDiagnosticAsync(source, expected) End Function @@ -430,13 +417,9 @@ End Namespace" Loop Until DateTime.Now.Second Mod 2 = 0 ".WrapInVBMethod - Dim expected As New DiagnosticResult With - { - .Id = StringBuilderInLoopAnalyzer.Id, - .Message = String.Format(StringBuilderInLoopAnalyzer.MessageFormat, "a"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 9, 17)} - } + Dim expected = New DiagnosticResult(StringBuilderInLoopAnalyzer.Id, Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(9, 17) _ + .WithMessage(String.Format(StringBuilderInLoopAnalyzer.MessageFormat, "a")) Await VerifyBasicDiagnosticAsync(source, expected) End Function @@ -464,7 +447,7 @@ End Namespace" Public Async Function ForLoopInMethodWithoutStringShouldNotCreateDiagnostic() As Task - Dim source = " + Const source = " Public Class Test Private Sub AdjustSample(ByRef readIndex As Integer, writeBuffer() As Single, ByRef writeIndex As Integer) For i = 0 To 2 diff --git a/test/VisualBasic/CodeCracker.Test/PlaceholderTest.vb b/test/VisualBasic/CodeCracker.Test/PlaceholderTest.vb deleted file mode 100644 index 759adf72f..000000000 --- a/test/VisualBasic/CodeCracker.Test/PlaceholderTest.vb +++ /dev/null @@ -1,10 +0,0 @@ -Imports Xunit -Imports CodeCracker -Public Class PlaceholderTest - - Public Sub PleaseRemove() - Microsoft.CodeAnalysis.VisualBasic.LanguageVersion.VisualBasic12.RunWithVB14OrGreater(Sub() - End Sub) - End Sub - -End Class diff --git a/test/VisualBasic/CodeCracker.Test/Refactoring/AllowMembersOrderingAnalyzerTests.vb b/test/VisualBasic/CodeCracker.Test/Refactoring/AllowMembersOrderingAnalyzerTests.vb index c4ad336f1..2d65a860e 100644 --- a/test/VisualBasic/CodeCracker.Test/Refactoring/AllowMembersOrderingAnalyzerTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Refactoring/AllowMembersOrderingAnalyzerTests.vb @@ -1,6 +1,7 @@ Imports Microsoft.CodeAnalysis.Diagnostics Imports CodeCracker.VisualBasic.Refactoring Imports Xunit +Imports Microsoft.CodeAnalysis.Testing Namespace Refactoring Public Class AllowMembersOrderingAnalyzerTests @@ -43,12 +44,9 @@ End {0}", typeDeclaration) End Sub End {0}", typeDeclaration) - Dim expected = New DiagnosticResult With { - .Id = AllowMembersOrderingAnalyzer.Id, - .Message = AllowMembersOrderingAnalyzer.MessageFormat, - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Hidden, - .Locations = {New DiagnosticResultLocation("Test0.vb", 2, 14 + typeDeclaration.Length)} - } + Dim expected = New DiagnosticResult(AllowMembersOrderingAnalyzer.Id, Microsoft.CodeAnalysis.DiagnosticSeverity.Hidden) _ + .WithLocation(2, 14 + typeDeclaration.Length) _ + .WithMessage(AllowMembersOrderingAnalyzer.MessageFormat) Await VerifyBasicDiagnosticAsync(test, expected) End Function End Class diff --git a/test/VisualBasic/CodeCracker.Test/Refactoring/ChangeAnyToAllTests.vb b/test/VisualBasic/CodeCracker.Test/Refactoring/ChangeAnyToAllTests.vb index feb3b99d3..64856acaf 100644 --- a/test/VisualBasic/CodeCracker.Test/Refactoring/ChangeAnyToAllTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Refactoring/ChangeAnyToAllTests.vb @@ -1,5 +1,6 @@ Imports CodeCracker.VisualBasic.Refactoring Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Testing Imports System.Threading.Tasks Imports Xunit @@ -29,12 +30,9 @@ Namespace Refactoring Public Async Function AnyAndAllWithLinqCreatesDiagnostic(code As String, column As Integer, diagnosticId As DiagnosticId) As Task Dim source = code.WrapInVBMethod(imports:=" Imports System.Linq") - Dim expected = New DiagnosticResult With { - .Id = diagnosticId.ToDiagnosticId(), - .Message = If(diagnosticId = DiagnosticId.ChangeAnyToAll, ChangeAnyToAllAnalyzer.MessageAny, ChangeAnyToAllAnalyzer.MessageAll), - .Severity = DiagnosticSeverity.Hidden, - .Locations = {New DiagnosticResultLocation("Test0.vb", 9, column)} - } + Dim expected = New DiagnosticResult(diagnosticId.ToDiagnosticId(), DiagnosticSeverity.Hidden) _ + .WithLocation(9, column) _ + .WithMessage(If(diagnosticId = DiagnosticId.ChangeAnyToAll, ChangeAnyToAllAnalyzer.MessageAny, ChangeAnyToAllAnalyzer.MessageAll)) Await VerifyBasicDiagnosticAsync(source, expected) End Function diff --git a/test/VisualBasic/CodeCracker.Test/Reliability/UseConfigureAwaitFalseTests.vb b/test/VisualBasic/CodeCracker.Test/Reliability/UseConfigureAwaitFalseTests.vb index f98bc3847..7c03192f9 100644 --- a/test/VisualBasic/CodeCracker.Test/Reliability/UseConfigureAwaitFalseTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Reliability/UseConfigureAwaitFalseTests.vb @@ -1,4 +1,5 @@ Imports CodeCracker.VisualBasic.Reliability +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Reliability @@ -14,12 +15,9 @@ Namespace Reliability Public Async Function WhenAwaitingTaskAnalyzerCreatesDiagnostic(sample As String, column As Integer) As Task Dim test = sample.WrapInVBMethod(isAsync:=True) - Dim expected = New DiagnosticResult With { - .Id = DiagnosticId.UseConfigureAwaitFalse.ToDiagnosticId(), - .Message = "Consider using ConfigureAwait(False) on the awaited task.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Hidden, - .Locations = {New DiagnosticResultLocation("Test0.vb", 6, column)} - } + Dim expected = New DiagnosticResult(DiagnosticId.UseConfigureAwaitFalse.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Hidden) _ + .WithLocation(6, column) _ + .WithMessage("Consider using ConfigureAwait(False) on the awaited task.") Await VerifyBasicDiagnosticAsync(test, expected) End Function diff --git a/test/VisualBasic/CodeCracker.Test/Style/InterfaceNameTests.vb b/test/VisualBasic/CodeCracker.Test/Style/InterfaceNameTests.vb index 28adafc53..0c61b24b8 100644 --- a/test/VisualBasic/CodeCracker.Test/Style/InterfaceNameTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Style/InterfaceNameTests.vb @@ -1,4 +1,5 @@ Imports CodeCracker.VisualBasic.Style +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Style @@ -23,12 +24,9 @@ End Namespace" End Interface End Namespace" - Dim expected = New DiagnosticResult With { - .Id = DiagnosticId.InterfaceName.ToDiagnosticId(), - .Message = InterfaceNameAnalyzer.MessageFormat, - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Info, - .Locations = {New DiagnosticResultLocation("Test0.vb", 2, 5)} - } + Dim expected = New DiagnosticResult(DiagnosticId.InterfaceName.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Info) _ + .WithLocation(2, 5) _ + .WithMessage(InterfaceNameAnalyzer.MessageFormat) Await VerifyBasicDiagnosticAsync(source, expected) End Function diff --git a/test/VisualBasic/CodeCracker.Test/Style/TernaryOperatorTests.vb b/test/VisualBasic/CodeCracker.Test/Style/TernaryOperatorTests.vb index 811762d42..0a956c38a 100644 --- a/test/VisualBasic/CodeCracker.Test/Style/TernaryOperatorTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Style/TernaryOperatorTests.vb @@ -1,11 +1,14 @@ Imports CodeCracker.VisualBasic.Style +Imports Microsoft.CodeAnalysis.Testing Imports Xunit -Public Class TernaryOperatorWithAssignmentTests - Inherits CodeFixVerifier(Of TernaryOperatorAnalyzer, TernaryOperatorWithAssignmentCodeFixProvider) +Namespace Style + Public Class TernaryOperatorWithAssignmentTests + Inherits CodeFixVerifier(Of TernaryOperatorAnalyzer, TernaryOperatorWithAssignmentCodeFixProvider) - Public Async Function WhenUsingIfWithoutElseAnalyzerDoesNotCreateDiagnostic() As Task - Const sourceWithoutElse = " + + Public Async Function WhenUsingIfWithoutElseAnalyzerDoesNotCreateDiagnostic() As Task + Const sourceWithoutElse = " Namespace ConsoleApplication1 Class TypeName Public Sub Foo() @@ -17,12 +20,12 @@ Namespace ConsoleApplication1 End Sub End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithoutElse) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithoutElse) + End Function - - Public Async Function WhenUsingIfWithElseButWithBlockWith2StatementsOnIfAnalyzerDoesNotCreate() As Task - Const sourceWithMultipleStatements = " + + Public Async Function WhenUsingIfWithElseButWithBlockWith2StatementsOnIfAnalyzerDoesNotCreate() As Task + Const sourceWithMultipleStatements = " Namespace ConsoleApplication1 Class TypeName Public Sub Foo() @@ -37,12 +40,12 @@ Namespace ConsoleApplication1 End Sub End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function - - Public Async Function WhenUsingIfWithElseButWithBlockWith2StatementsOnElseAnalyzerDoesNotCreate() As Task - Const sourceWithMultipleStatements = " + + Public Async Function WhenUsingIfWithElseButWithBlockWith2StatementsOnElseAnalyzerDoesNotCreate() As Task + Const sourceWithMultipleStatements = " Namespace ConsoleApplication1 Class TypeName Public Sub Foo() @@ -57,12 +60,12 @@ Namespace ConsoleApplication1 End Sub End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function - - Public Async Function WhenUsingIfWithElseButWithoutReturnOnElseDoesNotCreate() As Task - Const sourceWithMultipleStatements = " + + Public Async Function WhenUsingIfWithElseButWithoutReturnOnElseDoesNotCreate() As Task + Const sourceWithMultipleStatements = " Namespace ConsoleApplication1 Class TypeName Public Sub Foo() @@ -77,12 +80,12 @@ Namespace ConsoleApplication1 End Sub End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function - - Public Async Function WhenUsingIfWithElseButIfBlockWithoutReturnDoesNotCreate() As Task - Const sourceWithMultipleStatements = " + + Public Async Function WhenUsingIfWithElseButIfBlockWithoutReturnDoesNotCreate() As Task + Const sourceWithMultipleStatements = " Namespace ConsoleApplication1 Class TypeName Public Sub Foo() @@ -97,12 +100,12 @@ Namespace ConsoleApplication1 End Sub End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function - - Public Async Function WhenUsingIElseIfDoesNotCreate() As Task - Const sourceWithMultipleStatements = " + + Public Async Function WhenUsingIElseIfDoesNotCreate() As Task + Const sourceWithMultipleStatements = " Namespace ConsoleApplication1 Class TypeName Public Sub Foo() @@ -117,21 +120,38 @@ Namespace ConsoleApplication1 End Sub End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function - - Public Async Function WhenUsingIfAndElseWithDirectReturnAnalyzerCreatesDiagnostic() As Task - Dim expected As New DiagnosticResult With { - .Id = DiagnosticId.TernaryOperator_Assignment.ToDiagnosticId(), - .Message = "You can use a ternary operator.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 8, 13)} - } - Await VerifyBasicDiagnosticAsync(sourceAssign, expected) - End Function + + Public Async Function WhenUsingIfElseIfElseDoesNotCreate() As Task + Const sourceWithMultipleStatements = " +Namespace ConsoleApplication1 + Class TypeName + Public Sub Foo() + Dim x = 0 + If 1 > 2 Then + x = 1 + ElseIf 2 > 3 Then + x = 2 + Else + x = 3 + End If + End Sub + End Class +End Namespace" + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function + + + Public Async Function WhenUsingIfAndElseWithDirectReturnAnalyzerCreatesDiagnostic() As Task + Dim expected = New DiagnosticResult(DiagnosticId.TernaryOperator_Assignment.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(8, 13) _ + .WithMessage("You can use a ternary operator.") + Await VerifyBasicDiagnosticAsync(sourceAssign, expected) + End Function - Private Const sourceAssign = " + Private Const sourceAssign = " Imports System Namespace ConsoleApplication1 Class MyType @@ -147,9 +167,9 @@ Namespace ConsoleApplication1 End Class End Namespace" - - Public Async Function WhenUsingIfAndElseWithAssignmentChangeToTernaryFix() As Task - Const fix = " + + Public Async Function WhenUsingIfAndElseWithAssignmentChangeToTernaryFix() As Task + Const fix = " Imports System Namespace ConsoleApplication1 Class MyType @@ -160,12 +180,12 @@ Namespace ConsoleApplication1 End Sub End Class End Namespace" - Await VerifyBasicFixAsync(sourceAssign, fix) - End Function + Await VerifyBasicFixAsync(sourceAssign, fix) + End Function - - Public Async Function WhenUsingIfAndElseWithAssignmentChangeToTernaryFixAll() As Task - Const fix = " + + Public Async Function WhenUsingIfAndElseWithAssignmentChangeToTernaryFixAll() As Task + Const fix = " Imports System Namespace ConsoleApplication1 Class MyType @@ -176,16 +196,411 @@ Namespace ConsoleApplication1 End Sub End Class End Namespace" - Await VerifyBasicFixAllAsync(New String() {sourceAssign, sourceAssign.Replace("MyType", "MyType1")}, New String() {fix, fix.Replace("MyType", "MyType1")}) - End Function + Await VerifyBasicFixAllAsync(New String() {sourceAssign, sourceAssign.Replace("MyType", "MyType1")}, New String() {fix, fix.Replace("MyType", "MyType1")}) + End Function + + + Public Async Function WhenTernaryWithObjectDoesApplyFix() As Task + Const source = " +Class MyCustomer + Public Property Value As String +End Class +Public Class ExcelLineRecordClass + Public Property LineNumber As Integer + Public Property imported As Boolean End Class +Class Tester + Private Sub Test() + Dim ExcelRecord As New ExcelLineRecordClass + Dim lCell As New MyCustomer + If lCell Is Nothing Then + ExcelRecord.imported = False + Else + ExcelRecord.imported = If(lCell.Value, lCell.Value.ToString = ""X"") + End If + End Sub +End Class" + + Const fix = " +Class MyCustomer + Public Property Value As String +End Class +Public Class ExcelLineRecordClass + Public Property LineNumber As Integer + Public Property imported As Boolean +End Class +Class Tester + Private Sub Test() + Dim ExcelRecord As New ExcelLineRecordClass + Dim lCell As New MyCustomer + ExcelRecord.imported = If(lCell Is Nothing, False, DirectCast(If(lCell.Value, lCell.Value.ToString = ""X""), Boolean)) + End Sub +End Class" + + Await VerifyBasicFixAsync(source, fix) + + End Function + + + Public Async Function WhenUsingIfAndElseWithNullableValueTypeAssignmentChangeToTernaryFix() As Task + Const source = " +Public Class MyType + Public Sub Foo() + Dim a As Integer? + If True Then + a = 1 + Else + a = Nothing + End If + End Sub +End Class" + + Const fix = " +Public Class MyType + Public Sub Foo() + Dim a As Integer? + a = If(True, 1, DirectCast(Nothing, Integer?)) + End Sub +End Class" + + ' Allowing new diagnostics because without it the test fails because the compiler says Integer? is not defined. + Await VerifyBasicFixAsync(source, fix, allowNewCompilerDiagnostics:=True) + End Function + + + Public Async Function FixConsidersBaseTypeAssignent() As Task + Const source = " +Public Class Base +End Class +Public Class B + Inherits Base +End Class +Public Class MyType + Public Sub Foo() + Dim c As Base + If True Then + c = New Base() + Else + c = New B() + End If + End Sub +End Class" + + Const fix = " +Public Class Base +End Class +Public Class B + Inherits Base +End Class +Public Class MyType + Public Sub Foo() + Dim c As Base + c = If(True, New Base(), DirectCast(New B(), Base)) + End Sub +End Class" + + ' Allowing new diagnostics because without it the test fails because the compiler says Integer? is not defined. + Await VerifyBasicFixAsync(source, fix, allowNewCompilerDiagnostics:=True) + End Function + + Public Async Function WhenUsingCommentsConcatenateAtEndOfTernary() As Task + Const source = " +Public Class MyType + Public Sub Foo() + Dim a As Integer + If True Then + ' a + a = 1 ' One Thing + Else + ' b + a = 2 ' Another + End If + End Sub +End Class" + + Const fix = " +Public Class MyType + Public Sub Foo() + Dim a As Integer + ' a + ' b + a = If(True, 1, 2) ' One Thing ' Another + End Sub +End Class" + + ' Allowing new diagnostics because without it the test fails because the compiler says Integer? is not defined. + Await VerifyBasicFixAsync(source, fix, allowNewCompilerDiagnostics:=True) + End Function + + + Public Async Function WhenUsingIfAndElseWithNullableValueTypeAssignmentChangeToTernaryFixAll() As Task + Const source = " +Public Class MyType + Public Sub Foo() + Dim a As Integer? + If True Then + a = 1 + Else + a = Nothing + End If + End Sub +End Class" + + Const fix = " +Public Class MyType + Public Sub Foo() + Dim a As Integer? + a = If(True, 1, DirectCast(Nothing, Integer?)) + End Sub +End Class" + + Await VerifyBasicFixAllAsync(New String() {source, source.Replace("MyType", "MyType1")}, New String() {fix, fix.Replace("MyType", "MyType1")}) + End Function + + + Public Async Function WhenUsingConcatenationAssignmentExpandsToConcatenateAtEndOfTernary() As Task + Const source = " +Public Class MyType + Public Sub Foo() + Dim x = ""test"" + If True Then + x = ""1"" + Else + x &= ""2"" + End If + End Sub +End Class" + Const fix = " +Public Class MyType + Public Sub Foo() + Dim x = ""test"" + x = If(True, ""1"", x & ""2"") + End Sub +End Class" + Await VerifyBasicFixAsync(source, fix) + End Function + + + Public Async Function WhenUsingAddAssiginmentExpandsOperationProperly() As Task + Const source = " +Public Class MyType + Public Sub Foo() + Dim x = 0 + If True Then + x = 1 + Else + x += 1 + End If + End Sub +End Class" + Const fix = " +Public Class MyType + Public Sub Foo() + Dim x = 0 + x = If(True, 1, x + 1) + End Sub +End Class" + Await VerifyBasicFixAsync(source, fix) + End Function + + + Public Async Function WhenUsingSubtractAssiginmentExpandsOperationProperly() As Task + Const source = " +Public Class MyType + Public Sub Foo() + Dim x = 0 + If True Then + x = 1 + Else + x -= 1 + End If + End Sub +End Class" + Const fix = " +Public Class MyType + Public Sub Foo() + Dim x = 0 + x = If(True, 1, x - 1) + End Sub +End Class" + Await VerifyBasicFixAsync(source, fix) + End Function + + + Public Async Function WhenUsingConcatenationAssignmentOnElseAssignWithPlusExpandsToConcatenateAtEndOfTernary() As Task + Const source = " +Public Class MyType + Public Sub Foo() + Dim x = ""test"" + If True Then + x = ""1"" + Else + x += ""2"" + End If + End Sub +End Class" + Const fix = " +Public Class MyType + Public Sub Foo() + Dim x = ""test"" + x = If(True, ""1"", x + ""2"") + End Sub +End Class" + Await VerifyBasicFixAsync(source, fix) + End Function + + + Public Async Function WhenUsingConcatenationAssignmentOnIfAssignWithPlusExpandsToConcatenateAtEndOfTernary() As Task + Const source = " +Public Class MyType + Public Sub Foo() + Dim x = ""test"" + If True Then + x += ""1"" + Else + x = ""2"" + End If + End Sub +End Class" + Const fix = " +Public Class MyType + Public Sub Foo() + Dim x = ""test"" + x = If(True, x + ""1"", ""2"") + End Sub +End Class" + Await VerifyBasicFixAsync(source, fix) + End Function + + + Public Async Function WhenUsingConcatenationAssignmentOnIfAssignWithAmpersandExpandsToConcatenateAtEndOfTernary() As Task + Const source = " +Public Class MyType + Public Sub Foo() + Dim x = ""test"" + If True Then + x &= ""1"" + Else + x = ""2"" + End If + End Sub +End Class" + Const fix = " +Public Class MyType + Public Sub Foo() + Dim x = ""test"" + x = If(True, x & ""1"", ""2"") + End Sub +End Class" + Await VerifyBasicFixAsync(source, fix) + End Function -Public Class TernaryOperatorWithReturnTests - Inherits CodeFixVerifier(Of TernaryOperatorAnalyzer, TernaryOperatorWithReturnCodeFixProvider) + + Public Async Function WhenUsingAddAssiginmentOnIfAssignExpandsOperationProperly() As Task + Const source = " +Public Class MyType + Public Sub Foo() + Dim x = 0 + If True Then + x += 1 + Else + x = 1 + End If + End Sub +End Class" + Const fix = " +Public Class MyType + Public Sub Foo() + Dim x = 0 + x = If(True, x + 1, 1) + End Sub +End Class" + Await VerifyBasicFixAsync(source, fix) + End Function + + + Public Async Function WhenUsingSubtractAssignmentOnIfAssignExpandsOperationProperly() As Task + Const source = " +Public Class MyType + Public Sub Foo() + Dim x = 0 + If True Then + x -= 1 + Else + x = 1 + End If + End Sub +End Class" + Const fix = " +Public Class MyType + Public Sub Foo() + Dim x = 0 + x = If(True, x - 1, 1) + End Sub +End Class" + Await VerifyBasicFixAsync(source, fix) + End Function + + + Public Async Function WhenUsingAssignmentOperatorReturnSameAssignment() As Task + Const source = " +Class MyType + Public Sub x2() + Dim output As String = String.Empty + Dim test As Boolean + test = True + If test Then + output += ""True"" + Else + output += ""False"" + End If + End Sub +End Class" - - Public Async Function WhenUsingIfWithoutElseAnalyzerDoesNotCreateDiagnostic() As Task - Const sourceWithoutElse = " + Const fix = " +Class MyType + Public Sub x2() + Dim output As String = String.Empty + Dim test As Boolean + test = True + output += If(test, ""True"", ""False"") + End Sub +End Class" + + Await VerifyBasicFixAsync(source, fix, formatBeforeCompare:=True) + End Function + + + Public Async Function WhenUsingDifferentAssiginmentsExpandsOperationProperly() As Task + Const source = " +Public Class MyType + Public Sub Foo() + Dim x = 0 + If True Then + x += 1 + Else + x -= 1 + End If + End Sub +End Class" + + Const fix = " +Public Class MyType + Public Sub Foo() + Dim x = 0 + x = If(True, x + 1, x - 1) + End Sub +End Class" + Await VerifyBasicFixAsync(source, fix) + End Function + End Class + + Public Class TernaryOperatorWithReturnTests + Inherits CodeFixVerifier(Of TernaryOperatorAnalyzer, TernaryOperatorWithReturnCodeFixProvider) + + + Public Async Function WhenUsingIfWithoutElseAnalyzerDoesNotCreateDiagnostic() As Task + Const sourceWithoutElse = " Namespace ConsoleApplication1 Class TypeName Public Function Foo() As Integer @@ -196,12 +611,12 @@ Namespace ConsoleApplication1 End Function End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithoutElse) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithoutElse) + End Function - - Public Async Function WhenUsingIfWithElseButWithBlockWith2StatementsOnIfAnalyzerDoesNotCreate() As Task - Const sourceWithMultipleStatements = " + + Public Async Function WhenUsingIfWithElseButWithBlockWith2StatementsOnIfAnalyzerDoesNotCreate() As Task + Const sourceWithMultipleStatements = " Namespace ConsoleApplication1 Class TypeName Public Function Foo() As Integer @@ -215,12 +630,12 @@ Namespace ConsoleApplication1 End Function End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function - - Public Async Function WhenUsingIfWithElseButWithBlockWith2StatementsOnElseAnalyzerDoesNotCreate() As Task - Const sourceWithMultipleStatements = " + + Public Async Function WhenUsingIfWithElseButWithBlockWith2StatementsOnElseAnalyzerDoesNotCreate() As Task + Const sourceWithMultipleStatements = " Namespace ConsoleApplication1 Class TypeName Public Function Foo() As Integer @@ -234,12 +649,12 @@ Namespace ConsoleApplication1 End Function End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function - - Public Async Function WhenUsingIfWithElseButWithoutReturnOnElseDoesNotCreate() As Task - Const sourceWithMultipleStatements = " + + Public Async Function WhenUsingIfWithElseButWithoutReturnOnElseDoesNotCreate() As Task + Const sourceWithMultipleStatements = " Namespace ConsoleApplication1 Class TypeName Public Function Foo() As Integer @@ -252,12 +667,12 @@ Namespace ConsoleApplication1 End Function End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function - - Public Async Function WhenUsingIfWithElseButIfBlockWithoutReturnDoesNotCreate() As Task - Const sourceWithMultipleStatements = " + + Public Async Function WhenUsingIfWithElseButIfBlockWithoutReturnDoesNotCreate() As Task + Const sourceWithMultipleStatements = " Namespace ConsoleApplication1 Class TypeName Public Function Foo() As Integer @@ -270,12 +685,12 @@ Namespace ConsoleApplication1 End Function End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function - - Public Async Function WhenUsingIElseIfDoesNotCreate() As Task - Const sourceWithMultipleStatements = " + + Public Async Function WhenUsingIElseIfDoesNotCreate() As Task + Const sourceWithMultipleStatements = " Namespace ConsoleApplication1 Class TypeName Public Function Foo() As Integer @@ -289,10 +704,78 @@ Namespace ConsoleApplication1 End Function End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function + + + + Public Async Function WhenUsingIfAndElseWithNullableValueTypeDirectReturnChangeToTernaryFix() As Task + Const source = " +Public Class MyType + Public Function Foo() As Integer? + If True Then + Return 1 + Else + Return Nothing + End If End Function +End Class" + + Const fix = " +Public Class MyType + Public Function Foo() As Integer? + Return If(True, 1, DirectCast(Nothing, Integer?)) + End Function +End Class" + + ' Allowing new diagnostics because without it the test fails because the compiler says Integer? is not defined. + Await VerifyBasicFixAsync(source, fix, allowNewCompilerDiagnostics:=True) + End Function + + + Public Async Function WhenUsingIfElseIfElseDoesNotCreate() As Task + Const sourceWithMultipleStatements = " +Namespace ConsoleApplication1 + Class TypeName + Public Sub Foo() + If 1 > 2 Then + Return 1 + ElseIf 2 > 3 Then + Return 2 + Else + Return 3 + End If + End Sub + End Class +End Namespace" + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function + - Private Const sourceReturn = " + + Public Async Function WhenUsingIfAndElseWithNullableValueTypeDirectReturnChangeToTernaryFixAll() As Task + Const source = " +Public Class MyType + Public Function Foo() As Integer? + If True Then + Return 1 + Else + Return Nothing + End If + End Function +End Class" + + Const fix = " +Public Class MyType + Public Function Foo() As Integer? + Return If(True, 1, DirectCast(Nothing, Integer?)) + End Function +End Class" + + Await VerifyBasicFixAllAsync(New String() {source, source.Replace("MyType", "MyType1")}, New String() {fix, fix.Replace("MyType", "MyType1")}) + End Function + + Private Const sourceReturn = " Namespace ConsoleApplication1 Class MyType Public Function Foo() As Integer @@ -306,20 +789,17 @@ Namespace ConsoleApplication1 End Class End Namespace" - - Public Async Function WhenUsingIfAndElseWithDirectReturnAnalyzerCreatesDiagnostic() As Task - Dim expected As New DiagnosticResult With { - .Id = DiagnosticId.TernaryOperator_Return.ToDiagnosticId(), - .Message = "You can use a ternary operator.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 6, 13)} - } - Await VerifyBasicDiagnosticAsync(sourceReturn, expected) - End Function + + Public Async Function WhenUsingIfAndElseWithDirectReturnAnalyzerCreatesDiagnostic() As Task + Dim expected = New DiagnosticResult(DiagnosticId.TernaryOperator_Return.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(6, 13) _ + .WithMessage("You can use a ternary operator.") + Await VerifyBasicDiagnosticAsync(sourceReturn, expected) + End Function - - Public Async Function WhenUsingIfAndElseWithDirectReturnCreatesFix() As Task - Const fix = " + + Public Async Function WhenUsingIfAndElseWithDirectReturnCreatesFix() As Task + Const fix = " Namespace ConsoleApplication1 Class MyType Public Function Foo() As Integer @@ -329,12 +809,12 @@ Namespace ConsoleApplication1 End Class End Namespace" - Await VerifyBasicFixAsync(sourceReturn, fix) - End Function + Await VerifyBasicFixAsync(sourceReturn, fix) + End Function - - Public Async Function WhenUsingIfAndElseWithDirectReturnCreatesFixAll() As Task - Const fix = " + + Public Async Function WhenUsingIfAndElseWithDirectReturnCreatesFixAll() As Task + Const fix = " Namespace ConsoleApplication1 Class MyType Public Function Foo() As Integer @@ -344,16 +824,125 @@ Namespace ConsoleApplication1 End Class End Namespace" - Await VerifyBasicFixAllAsync(New String() {sourceReturn, sourceReturn.Replace("MyType", "MyType1")}, New String() {fix, fix.Replace("MyType", "MyType1")}) - End Function -End Class + Await VerifyBasicFixAllAsync(New String() {sourceReturn, sourceReturn.Replace("MyType", "MyType1")}, New String() {fix, fix.Replace("MyType", "MyType1")}) + End Function + + + Public Async Function FixWhenThereIsNumericImplicitConversion() As Task + Dim source = " +Function OnReturn() As Double + Dim condition = True + Dim aDouble As Double = 2 + Dim bInteger = 3 + If condition Then + Return aDouble + Else + Return bInteger + End If +End Function".WrapInVBClass() + Dim fix = " +Function OnReturn() As Double + Dim condition = True + Dim aDouble As Double = 2 + Dim bInteger = 3 + Return If(condition, aDouble, bInteger) +End Function".WrapInVBClass() + Await VerifyBasicFixAsync(source, fix, formatBeforeCompare:=True) + End Function + + + Public Async Function FixWhenThereIsEnumImplicitConversionToNumeric() As Task + Dim source = " +Enum Values + Value +End Enum +Function OnReturn() As Double + Dim condition = True + Dim anEnum As Values = Values.Value + Dim bInteger = 3 + If condition Then + Return anEnum + Else + Return bInteger + End If +End Function".WrapInVBClass() + Dim fix = " +Enum Values + Value +End Enum +Function OnReturn() As Double + Dim condition = True + Dim anEnum As Values = Values.Value + Dim bInteger = 3 + Return If(condition, anEnum, bInteger) +End Function".WrapInVBClass() + Await VerifyBasicFixAsync(source, fix, formatBeforeCompare:=True) + End Function + + + Public Async Function FixCanWorkWithCommentsOnIf() As Task + Const source = " +Function s() As Boolean + If True Then + ' a comment + Return False 'b comment + Else + Return True + End If +End Function" + Const fix = " +Function s() As Boolean + ' a comment + Return If(True, False, True) 'b comment +End Function" + Await VerifyBasicFixAsync(source, fix) + End Function + + Public Async Function FixCanWorkWithCommentsOnElse() As Task + Const source = " +Function s() As Boolean + If True Then + Return False + Else + ' a comment + Return True 'b comment + End If +End Function" + Const fix = " +Function s() As Boolean + ' a comment + Return If(True, False, True) 'b comment +End Function" + Await VerifyBasicFixAsync(source, fix) + End Function + + Public Async Function FixCanWorkWithCommentsOnIfAndElse() As Task + Const source = " +Function s() As Boolean + If True Then + ' a comment + Return False 'b comment + Else + ' c comment + Return True 'd comment + End If +End Function" + Const fix = " +Function s() As Boolean + ' a comment + ' c comment + Return If(True, False, True) 'b comment 'd comment +End Function" + Await VerifyBasicFixAsync(source, fix) + End Function + End Class -Public Class TernaryOperatorFromIifTests - Inherits CodeFixVerifier(Of TernaryOperatorAnalyzer, TernaryOperatorFromIifCodeFixProvider) + Public Class TernaryOperatorFromIifTests + Inherits CodeFixVerifier(Of TernaryOperatorAnalyzer, TernaryOperatorFromIifCodeFixProvider) - - Public Async Function WhenUsingIifAndSimpleAssignmentCreatesFix() As Task - Const source = " + + Public Async Function WhenUsingIifAndSimpleAssignmentCreatesFix() As Task + Const source = " Class TypeName Public Sub Foo() Dim x = 1 @@ -361,7 +950,7 @@ Class TypeName End Sub End Class" - Const fix = " + Const fix = " Class TypeName Public Sub Foo() Dim x = 1 @@ -369,12 +958,12 @@ Class TypeName End Sub End Class" - Await VerifyBasicFixAsync(source, fix) - End Function + Await VerifyBasicFixAsync(source, fix) + End Function - - Public Async Function WhenUsingIifAndSimpleAssignmentCreatesFixAll() As Task - Const source = " + + Public Async Function WhenUsingIifAndSimpleAssignmentCreatesFixAll() As Task + Const source = " Class MyType Public Sub Foo() Dim x = 1 @@ -382,7 +971,7 @@ Class MyType End Sub End Class" - Const fix = " + Const fix = " Class MyType Public Sub Foo() Dim x = 1 @@ -390,12 +979,12 @@ Class MyType End Sub End Class" - Await VerifyBasicFixAllAsync(New String() {source, source.Replace("MyType", "MyType1")}, New String() {fix, fix.Replace("MyType", "MyType1")}) - End Function + Await VerifyBasicFixAllAsync(New String() {source, source.Replace("MyType", "MyType1")}, New String() {fix, fix.Replace("MyType", "MyType1")}) + End Function - - Public Async Function WhenUsingIifAndReturnCreatesFix() As Task - Const source = " + + Public Async Function WhenUsingIifAndReturnCreatesFix() As Task + Const source = " Class MyType Public Function Foo() As Integer Dim x = 1 @@ -403,7 +992,7 @@ Class MyType End Function End Class" - Const fix = " + Const fix = " Class MyType Public Function Foo() As Integer Dim x = 1 @@ -411,20 +1000,17 @@ Class MyType End Function End Class" - Dim expected As New DiagnosticResult With { - .Id = DiagnosticId.TernaryOperator_Iif.ToDiagnosticId(), - .Message = "You can use a ternary operator.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 5, 16)} - } + Dim expected = New DiagnosticResult(DiagnosticId.TernaryOperator_Iif.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(5, 16) _ + .WithMessage("You can use a ternary operator.") - Await VerifyBasicDiagnosticAsync(source, expected) - Await VerifyBasicFixAsync(source, fix) - End Function + Await VerifyBasicDiagnosticAsync(source, expected) + Await VerifyBasicFixAsync(source, fix) + End Function - - Public Async Function WhenUsingIifAndReturnCreatesFixAll() As Task - Const source = " + + Public Async Function WhenUsingIifAndReturnCreatesFixAll() As Task + Const source = " Class MyType Public Function Foo() As Integer Dim x = 1 @@ -432,7 +1018,7 @@ Class MyType End Function End Class" - Const fix = " + Const fix = " Class MyType Public Function Foo() As Integer Dim x = 1 @@ -440,43 +1026,44 @@ Class MyType End Function End Class" - Await VerifyBasicFixAllAsync(New String() {source, source.Replace("MyType", "MyType1")}, New String() {fix, fix.Replace("MyType", "MyType1")}) - End Function + Await VerifyBasicFixAllAsync(New String() {source, source.Replace("MyType", "MyType1")}, New String() {fix, fix.Replace("MyType", "MyType1")}) + End Function - - Public Async Function WhenNotUsingIifDoesNotCreateAnalyzer() As Task - Const source = " + + Public Async Function WhenNotUsingIifDoesNotCreateAnalyzer() As Task + Const source = " Class TypeName Public Sub Foo() Dim x = 1 End Sub End Class" - Await VerifyBasicHasNoDiagnosticsAsync(source) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(source) + End Function - - Public Async Function WhenIifWithTooManyParametersDoesNotCreateAnalyzer() As Task - Const source = " + + Public Async Function WhenIifWithTooManyParametersDoesNotCreateAnalyzer() As Task + Const source = " Class TypeName Public Sub Foo() Dim x = Iif(true, 1, 2, 3) End Sub End Class" - Await VerifyBasicHasNoDiagnosticsAsync(source) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(source) + End Function - - Public Async Function WhenIifWithTooFewParametersDoesNotCreateAnalyzer() As Task - Const source = " + + Public Async Function WhenIifWithTooFewParametersDoesNotCreateAnalyzer() As Task + Const source = " Class TypeName Public Sub Foo() Dim x = Iif(true, 1) End Sub End Class" - Await VerifyBasicHasNoDiagnosticsAsync(source) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(source) + End Function -End Class \ No newline at end of file + End Class +End Namespace diff --git a/test/VisualBasic/CodeCracker.Test/Usage/ArgumentExceptionTests.vb b/test/VisualBasic/CodeCracker.Test/Usage/ArgumentExceptionTests.vb index 681fe9584..b2ac9db8d 100644 --- a/test/VisualBasic/CodeCracker.Test/Usage/ArgumentExceptionTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Usage/ArgumentExceptionTests.vb @@ -1,4 +1,5 @@ Imports CodeCracker.VisualBasic.Usage +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Usage @@ -24,12 +25,9 @@ Public Async Function Foo(a As Integer, b As Integer) As Task End Function ") - Dim expected = New DiagnosticResult With { - .Id = DiagnosticId.ArgumentException.ToDiagnosticId(), - .Message = "Type argument 'c' is not in the argument list.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 8, 44)} - } + Dim expected = New DiagnosticResult(DiagnosticId.ArgumentException.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(8, 44) _ + .WithMessage("Type argument 'c' is not in the argument list.") Await VerifyBasicDiagnosticAsync(test, expected) End Function @@ -42,12 +40,9 @@ Public Sub New(a As Integer, b As Integer) End Sub ") - Dim expected = New DiagnosticResult With { - .Id = DiagnosticId.ArgumentException.ToDiagnosticId(), - .Message = "Type argument 'c' is not in the argument list.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 8, 44)} - } + Dim expected = New DiagnosticResult(DiagnosticId.ArgumentException.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(8, 44) _ + .WithMessage("Type argument 'c' is not in the argument list.") Await VerifyBasicDiagnosticAsync(test, expected) End Function @@ -147,12 +142,9 @@ End Sub End Set End Property ") - Dim expected = New DiagnosticResult With { - .Id = DiagnosticId.ArgumentException.ToDiagnosticId(), - .Message = "Type argument 'paramName' is not in the argument list.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 11, 56)} - } + Dim expected = New DiagnosticResult(DiagnosticId.ArgumentException.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(11, 56) _ + .WithMessage("Type argument 'paramName' is not in the argument list.") Await VerifyBasicDiagnosticAsync(test, expected) End Function diff --git a/test/VisualBasic/CodeCracker.Test/Usage/DisposableFieldNotDisposedTests.vb b/test/VisualBasic/CodeCracker.Test/Usage/DisposableFieldNotDisposedTests.vb index ad742040d..f1f583c6d 100644 --- a/test/VisualBasic/CodeCracker.Test/Usage/DisposableFieldNotDisposedTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Usage/DisposableFieldNotDisposedTests.vb @@ -1,4 +1,5 @@ Imports CodeCracker.VisualBasic.Usage +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Usage @@ -35,13 +36,9 @@ Namespace ConsoleApplication1 End Class End Namespace" - Dim expected As New DiagnosticResult With - { - .Id = DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), - .Message = String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Info, - .Locations = {New DiagnosticResultLocation("Test0.vb", 5, 17)} - } + Dim expected = New DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Info) _ + .WithLocation(5, 17) _ + .WithMessage(String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")) Await VerifyBasicDiagnosticAsync(source, expected) End Function @@ -83,20 +80,12 @@ Namespace ConsoleApplication1 End Class End Namespace" - Dim expected As New DiagnosticResult With - { - .Id = DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), - .Message = String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Info, - .Locations = {New DiagnosticResultLocation("Test0.vb", 5, 17)} - } - Dim expected2 As New DiagnosticResult With - { - .Id = DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), - .Message = String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field2"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Info, - .Locations = {New DiagnosticResultLocation("Test0.vb", 6, 17)} - } + Dim expected = New DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Info) _ + .WithLocation(5, 17) _ + .WithMessage(String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")) + Dim expected2 = New DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Info) _ + .WithLocation(6, 17) _ + .WithMessage(String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field2")) Await VerifyBasicDiagnosticAsync(source, expected, expected2) End Function @@ -122,13 +111,9 @@ Namespace ConsoleApplication1 End Class End Namespace" - Dim expected As New DiagnosticResult With - { - .Id = DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), - .Message = String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Info, - .Locations = {New DiagnosticResultLocation("Test0.vb", 5, 17)} - } + Dim expected = New DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Info) _ + .WithLocation(5, 17) _ + .WithMessage(String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")) Await VerifyBasicDiagnosticAsync(source, expected) End Function @@ -154,13 +139,9 @@ Namespace ConsoleApplication1 End Class End Namespace" - Dim expected As New DiagnosticResult With - { - .Id = DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), - .Message = String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Info, - .Locations = {New DiagnosticResultLocation("Test0.vb", 6, 17)} - } + Dim expected = New DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Info) _ + .WithLocation(6, 17) _ + .WithMessage(String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")) Await VerifyBasicDiagnosticAsync(source, expected) End Function @@ -208,13 +189,9 @@ Namespace ConsoleApplication1 End Class End Namespace" - Dim expected As New DiagnosticResult With - { - .Id = DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), - .Message = String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 7, 17)} - } + Dim expected = New DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(7, 17) _ + .WithMessage(String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")) Await VerifyBasicDiagnosticAsync(source, expected) End Function @@ -240,13 +217,9 @@ Namespace ConsoleApplication1 End Class End Namespace" - Dim expected As New DiagnosticResult With - { - .Id = DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), - .Message = String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 8, 17)} - } + Dim expected = New DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(8, 17) _ + .WithMessage(String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")) Await VerifyBasicDiagnosticAsync(source, expected) End Function @@ -273,13 +246,9 @@ Namespace ConsoleApplication1 End Class End Namespace" - Dim expected As New DiagnosticResult With - { - .Id = DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), - .Message = String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 6, 17)} - } + Dim expected = New DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(6, 17) _ + .WithMessage(String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")) Await VerifyBasicDiagnosticAsync(source, expected) End Function @@ -307,13 +276,9 @@ Namespace ConsoleApplication1 End Class End Namespace" - Dim expected As New DiagnosticResult With - { - .Id = DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), - .Message = String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 6, 17)} - } + Dim expected = New DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(6, 17) _ + .WithMessage(String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")) Await VerifyBasicDiagnosticAsync(source, expected) End Function @@ -334,13 +299,9 @@ Namespace ConsoleApplication1 End Class End Namespace" - Dim expected As New DiagnosticResult With - { - .Id = DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), - .Message = String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 5, 17)} - } + Dim expected = New DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Created.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(5, 17) _ + .WithMessage(String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")) Await VerifyBasicDiagnosticAsync(source, expected) End Function @@ -361,13 +322,9 @@ Namespace ConsoleApplication1 End Class End Namespace" - Dim expected As New DiagnosticResult With - { - .Id = DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), - .Message = String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field"), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Info, - .Locations = {New DiagnosticResultLocation("Test0.vb", 5, 17)} - } + Dim expected = New DiagnosticResult(DiagnosticId.DisposableFieldNotDisposed_Returned.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Info) _ + .WithLocation(5, 17) _ + .WithMessage(String.Format(DisposableFieldNotDisposedAnalyzer.MessageFormat, "field")) Await VerifyBasicDiagnosticAsync(source, expected) End Function diff --git a/test/VisualBasic/CodeCracker.Test/Usage/DisposablesShouldCallSuppressFinalizeTests.vb b/test/VisualBasic/CodeCracker.Test/Usage/DisposablesShouldCallSuppressFinalizeTests.vb index ecdd57888..89ea62f97 100644 --- a/test/VisualBasic/CodeCracker.Test/Usage/DisposablesShouldCallSuppressFinalizeTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Usage/DisposablesShouldCallSuppressFinalizeTests.vb @@ -1,4 +1,5 @@ Imports CodeCracker.VisualBasic.Usage +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Usage @@ -15,12 +16,9 @@ Public Class MyType End Sub End Class " - Dim expected = New DiagnosticResult With { - .Id = DiagnosticId.DisposablesShouldCallSuppressFinalize.ToDiagnosticId(), - .Message = "'MyType' should call GC.SuppressFinalize inside the Dispose method.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 5, 16)} - } + Dim expected = New DiagnosticResult(DiagnosticId.DisposablesShouldCallSuppressFinalize.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(5, 16) _ + .WithMessage("'MyType' should call GC.SuppressFinalize inside the Dispose method.") Await VerifyBasicDiagnosticAsync(test, expected) End Function @@ -109,12 +107,9 @@ Public NotInheritable Class MyType End Class " - Dim expected = New DiagnosticResult With { - .Id = DiagnosticId.DisposablesShouldCallSuppressFinalize.ToDiagnosticId(), - .Message = "'MyType' should call GC.SuppressFinalize inside the Dispose method.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 5, 16)} - } + Dim expected = New DiagnosticResult(DiagnosticId.DisposablesShouldCallSuppressFinalize.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(5, 16) _ + .WithMessage("'MyType' should call GC.SuppressFinalize inside the Dispose method.") Await VerifyBasicDiagnosticAsync(test, expected) End Function diff --git a/test/VisualBasic/CodeCracker.Test/Usage/IPAddressAnalyzerTests.vb b/test/VisualBasic/CodeCracker.Test/Usage/IPAddressAnalyzerTests.vb index c2c4a62cf..83b260e86 100644 --- a/test/VisualBasic/CodeCracker.Test/Usage/IPAddressAnalyzerTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Usage/IPAddressAnalyzerTests.vb @@ -2,6 +2,7 @@ Imports CodeCracker.VisualBasic Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Public Class IPAddressAnalyzerTests @@ -53,11 +54,9 @@ End Namespace" End Function Private Function CreateDiagnosticResult(line As Integer, column As Integer, errorMessageAction As Action) As DiagnosticResult - Return New DiagnosticResult With { - .Id = DiagnosticId.IPAddress.ToDiagnosticId(), - .Message = GetErrorMessage(errorMessageAction), - .Severity = DiagnosticSeverity.Error, - .Locations = {New DiagnosticResultLocation("Test0.vb", line, column)}} + Return New DiagnosticResult(DiagnosticId.IPAddress.ToDiagnosticId(), DiagnosticSeverity.Error) _ + .WithLocation(line, column) _ + .WithMessage(GetErrorMessage(errorMessageAction)) End Function Private Shared Function GetErrorMessage(action As Action) As String diff --git a/test/VisualBasic/CodeCracker.Test/Usage/JsonNetTests.vb b/test/VisualBasic/CodeCracker.Test/Usage/JsonNetAnalyzerTests.vb similarity index 90% rename from test/VisualBasic/CodeCracker.Test/Usage/JsonNetTests.vb rename to test/VisualBasic/CodeCracker.Test/Usage/JsonNetAnalyzerTests.vb index ea69e25a7..6b573b250 100644 --- a/test/VisualBasic/CodeCracker.Test/Usage/JsonNetTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Usage/JsonNetAnalyzerTests.vb @@ -1,9 +1,10 @@ Imports CodeCracker.VisualBasic.Usage Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Usage - Public Class JsonNetTests + Public Class JsonNetAnalyzerTests Inherits CodeFixVerifier Private Const TestCode = " @@ -19,12 +20,9 @@ Namespace ConsoleApplication1 End Namespace" Private Shared Function CreateDiagnosticResult(line As Integer, column As Integer) As DiagnosticResult - Return New DiagnosticResult With { - .Id = DiagnosticId.JsonNet.ToDiagnosticId(), - .Message = "Error parsing boolean value. Path '', line 0, position 0.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Error, - .Locations = {New DiagnosticResultLocation("Test0.vb", line, column)} - } + Return New DiagnosticResult(DiagnosticId.JsonNet.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Error) _ + .WithLocation(line, column) _ + .WithMessage("Unexpected end when reading JSON. Path '', line 1, position 3.") End Function Protected Overrides Function GetDiagnosticAnalyzer() As DiagnosticAnalyzer diff --git a/test/VisualBasic/CodeCracker.Test/Usage/MustInheritClassShouldNotHavePublicConstructorTests.vb b/test/VisualBasic/CodeCracker.Test/Usage/MustInheritClassShouldNotHavePublicConstructorTests.vb index 30093ff9e..3ec7ccf1e 100644 --- a/test/VisualBasic/CodeCracker.Test/Usage/MustInheritClassShouldNotHavePublicConstructorTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Usage/MustInheritClassShouldNotHavePublicConstructorTests.vb @@ -1,4 +1,5 @@ Imports CodeCracker.VisualBasic.Usage +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Usage @@ -14,12 +15,9 @@ MustInherit Class Foo End Sub End Class" - Dim expected = New DiagnosticResult With { - .Id = DiagnosticId.AbstractClassShouldNotHavePublicCtors.ToDiagnosticId(), - .Message = "Constructor should not be public.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", 3, 5)} - } + Dim expected = New DiagnosticResult(DiagnosticId.AbstractClassShouldNotHavePublicCtors.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(3, 5) _ + .WithMessage("Constructor should not be public.") Await VerifyBasicDiagnosticAsync(test, expected) End Function diff --git a/test/VisualBasic/CodeCracker.Test/Usage/RemovePrivateMethodNeverUsedAnalyzerTest.vb b/test/VisualBasic/CodeCracker.Test/Usage/RemovePrivateMethodNeverUsedAnalyzerTest.vb index bea88ef20..272de516a 100644 --- a/test/VisualBasic/CodeCracker.Test/Usage/RemovePrivateMethodNeverUsedAnalyzerTest.vb +++ b/test/VisualBasic/CodeCracker.Test/Usage/RemovePrivateMethodNeverUsedAnalyzerTest.vb @@ -5,6 +5,53 @@ Namespace Usage Public Class RemovePrivateMethodNeverUsedAnalyzerTest Inherits CodeFixVerifier(Of RemovePrivateMethodNeverUsedAnalyzer, RemovePrivateMethodNeverUsedCodeFixProvider) + + + + + + Public Async Function DoesNotGenerateDiagnosticsWhenMethodAttributeIsAnException(value As String) As Task + Dim source = " +Class Foo + <" + value + "> + Private Sub PrivateFoo() + End Sub +End Class" + Await VerifyBasicHasNoDiagnosticsAsync(source) + End Function + + + Public Async Function MainMethodEntryPointReturningVoidDoesNotCreateDiagnostic() As Task + Const test = " +Module Foo + Sub Main(args as String()) + End Sub +End Module" + Await VerifyBasicHasNoDiagnosticsAsync(test) + End Function + + + Public Async Function MainMethodEntryPointReturningIntegerDoesNotCreateDiagnostic() As Task + Const test = " +Module Foo + Function Main(args as String()) as Integer + Return 0 + End Function +End Module" + Await VerifyBasicHasNoDiagnosticsAsync(test) + End Function + + + Public Async Function MainMethodEntryPointWithoutParameterDoesNotCreateDiagnostic() As Task + Const test = " +Module Foo + Function Main() as Integer + Return 0 + End Function +End Module" + Await VerifyBasicHasNoDiagnosticsAsync(test) + End Function + Public Async Function DoesNotGenerateDiagnostics() As Task Const test = " diff --git a/test/VisualBasic/CodeCracker.Test/Usage/UnusedParameterTests.vb b/test/VisualBasic/CodeCracker.Test/Usage/UnusedParametersTests.vb similarity index 59% rename from test/VisualBasic/CodeCracker.Test/Usage/UnusedParameterTests.vb rename to test/VisualBasic/CodeCracker.Test/Usage/UnusedParametersTests.vb index f45211a33..1031f3a59 100644 --- a/test/VisualBasic/CodeCracker.Test/Usage/UnusedParameterTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Usage/UnusedParametersTests.vb @@ -1,10 +1,9 @@ Imports CodeCracker.VisualBasic.Usage -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Usage - Public Class UnusedParameterTests + Public Class UnusedParametersTests Inherits CodeFixVerifier(Of UnusedParametersAnalyzer, UnusedParametersCodeFixProvider) Public Async Function MethodWithoutParametersDoesNotCreateDiagnostic() As Task @@ -250,7 +249,7 @@ Class HasRef Dim x = New TypeName().Foo(1, 2) End Sub End Class -Class TypeName +Class TypeName Public Function Foo(a As Integer, b As Integer) as Integer Return a End Function @@ -261,7 +260,7 @@ Class HasRef Dim x = New TypeName().Foo(1) End Sub End Class -Class TypeName +Class TypeName Public Function Foo(a As Integer) as Integer Return a End Function @@ -278,7 +277,7 @@ Class HasRef TypeName.Foo(1, 2) End Sub End Class -Class TypeName +Class TypeName Public Shared Function Foo(a As Integer, b As Integer) as Integer Return a End Function @@ -289,7 +288,7 @@ Class HasRef TypeName.Foo(1) End Sub End Class -Class TypeName +Class TypeName Public Shared Function Foo(a As Integer) as Integer Return a End Function @@ -306,7 +305,7 @@ Class HasRef Dim x = New TypeName(1, 2) End Sub End Class -Class TypeName +Class TypeName Public Sub New(a As Integer, b As Integer) Dim x = a End Sub @@ -317,7 +316,7 @@ Class HasRef Dim x = New TypeName(1) End Sub End Class -Class TypeName +Class TypeName Public Sub New(a As Integer) Dim x = a End Sub @@ -326,6 +325,83 @@ End Class" Await VerifyBasicFixAsync(source, fix) End Function + + Public Async Function FixParamsInConstructor() As Task + Const source = " +Class HasRef + Public Sub IsReferencing() + Dim x = New TypeName(1, 2, 3) + End Sub +End Class +Class TypeName + Public Sub New(a As Integer, b As Integer, ParamArray c As Integer()) + b = a + End Sub +End Class" + Const fix = " +Class HasRef + Public Sub IsReferencing() + Dim x = New TypeName(1, 2) + End Sub +End Class +Class TypeName + Public Sub New(a As Integer, b As Integer) + b = a + End Sub +End Class" + Await VerifyBasicFixAsync(source, fix) + End Function + + + Public Async Function FixParams() As Task + Const source = " +Class Foo + Public Sub IsReferencing() + Dim x = Bar(1, 2, 3, 4) + End Sub + Public Function Bar(a As Integer, b As Integer, ParamArray c As Integer()) + b = a + return 1 + End Function +End Class" + Const fix = " +Class Foo + Public Sub IsReferencing() + Dim x = Bar(1, 2) + End Sub + Public Function Bar(a As Integer, b As Integer) + b = a + return 1 + End Function +End Class" + Await VerifyBasicFixAsync(source, fix) + End Function + + + Public Async Function FixParamsWhenNotInUse() As Task + Const source = " +Class Foo + Public Sub IsReferencing() + Dim x = Bar(1, 2) + End Sub + Public Function Bar(a As Integer, b As Integer, ParamArray c As Integer()) + b = a + return 1 + End Function +End Class" + Const fix = " +Class Foo + Public Sub IsReferencing() + Dim x = Bar(1, 2) + End Sub + Public Function Bar(a As Integer, b As Integer) + b = a + return 1 + End Function +End Class" + Await VerifyBasicFixAsync(source, fix) + End Function + Public Async Function CallToBaseDoesNotCreateDiagnostic() As Task @@ -389,6 +465,19 @@ End Class Await VerifyBasicDiagnosticAsync(source, CreateDiagnosticResult("out2", 3, 77)) End Function + + Public Async Function CallWithDllImport() As Task + Const source = " +Imports System.Runtime.InteropServices +Class Base + + Private Shared Function y(ByRef message As IntPtr) As Integer + End Function +End Class +" + Await VerifyBasicHasNoDiagnosticsAsync(source) + End Function + Public Async Function CallWithRefAndEnumerableDoesNotCreateDiagnostic() As Task Const source = " @@ -413,15 +502,212 @@ End Class Await VerifyBasicHasNoDiagnosticsAsync(source) End Function + + Public Async Function FixAllInSameClass() As Task + Const source As String = " +Class TypeName + Public Sub IsReferencing() + Me.Foo(1, 2, 3, 4) + End Sub + Public Sub Foo(ByVal a As Integer, ByVal b As Integer, ParamArray ByVal c() As Integer) + a = 1 + End Sub +End Class" + Const fixtest As String = " +Class TypeName + Public Sub IsReferencing() + Me.Foo(1) + End Sub + Public Sub Foo(ByVal a As Integer) + a = 1 + End Sub +End Class" + Await VerifyBasicFixAllAsync(source, fixtest) + End Function - Private Function CreateDiagnosticResult(parameterName As String, line As Integer, column As Integer) As DiagnosticResult - Return New DiagnosticResult With { - .Id = DiagnosticId.UnusedParameters.ToDiagnosticId(), - .Message = String.Format(UnusedParametersAnalyzer.Message, parameterName), - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", line, column)} - } + + Public Async Function FixAllInDifferentClass() As Task + Const source1 As String = " +Class TypeName + Public Sub IsReferencing() + Referenced.Foo(1, 2, 3, 4) + End Sub +End Class" + Const source2 As String = " +Class Referenced + Public Shared Sub Foo(ByVal a As Integer, ByVal b As Integer, ParamArray ByVal c() As Integer) + a = 1 + End Sub +End Class" + Const fix1 As String = " +Class TypeName + Public Sub IsReferencing() + Referenced.Foo(1) + End Sub +End Class" + Const fix2 As String = " +Class Referenced + Public Shared Sub Foo(ByVal a As Integer) + a = 1 + End Sub +End Class" + Await VerifyBasicFixAllAsync({source1, source2}, {fix1, fix2}) End Function + + Public Async Function FixAllWithOnlyAnOptionalNotPassedInSameClass() As Task + Const source As String = " +Class TypeName + Public Sub IsReferencing() + Me.Foo() + End Sub + Public Sub Foo(Optional ByVal b As String = Nothing) + dim a = 1 + End Sub +End Class" + Const fixtest As String = " +Class TypeName + Public Sub IsReferencing() + Me.Foo() + End Sub + Public Sub Foo() + dim a = 1 + End Sub +End Class" + Await VerifyBasicFixAllAsync(source, fixtest) + End Function + + + Public Async Function FixAllWithOptionalNotPassedInSameClass() As Task + Const source As String = " +Class TypeName + Public Sub IsReferencing() + Me.Foo(1, 2, , 3, 4) + End Sub + Public Sub Foo(ByVal a As Integer, ByVal b As Integer, Optional ByVal c As String = Nothing, ParamArray ByVal d() As Integer) + a = 1 + End Sub +End Class" + Const fixtest As String = " +Class TypeName + Public Sub IsReferencing() + Me.Foo(1) + End Sub + Public Sub Foo(ByVal a As Integer) + a = 1 + End Sub +End Class" + Await VerifyBasicFixAllAsync(source, fixtest) + End Function + + + Public Async Function FixAllWithOptionalPassedInSameClass() As Task + Const source As String = " +Class TypeName + Public Sub IsReferencing() + Me.Foo(1, 2, """", 3, 4) + End Sub + Public Sub Foo(ByVal a As Integer, ByVal b As Integer, Optional ByVal c As String = Nothing, ParamArray ByVal d() As Integer) + a = 1 + End Sub +End Class" + Const fixtest As String = " +Class TypeName + Public Sub IsReferencing() + Me.Foo(1) + End Sub + Public Sub Foo(ByVal a As Integer) + a = 1 + End Sub +End Class" + Await VerifyBasicFixAllAsync(source, fixtest) + End Function + + + Public Async Function FixAllWithOptionalNotPassedInDifferentClass() As Task + Const source1 As String = " +Class TypeName + Public Sub IsReferencing() + Referenced.Foo(1, 2, , 3, 4) + End Sub +End Class" + Const source2 As String = " +Class Referenced + Public Shared Sub Foo(ByVal a As Integer, ByVal b As Integer, Optional ByVal c As String = Nothing, ParamArray ByVal d() As Integer) + a = 1 + End Sub +End Class" + Const fix1 As String = " +Class TypeName + Public Sub IsReferencing() + Referenced.Foo(1) + End Sub +End Class" + Const fix2 As String = " +Class Referenced + Public Shared Sub Foo(ByVal a As Integer) + a = 1 + End Sub +End Class" + Await VerifyBasicFixAllAsync({source1, source2}, {fix1, fix2}) + End Function + + + Public Async Function FixAllWithOptionalPassedInDifferentClass() As Task + Const source1 As String = " +Class TypeName + Public Sub IsReferencing() + Referenced.Foo(1, 2, """", 3, 4) + End Sub +End Class" + Const source2 As String = " +Class Referenced + Public Shared Sub Foo(ByVal a As Integer, ByVal b As Integer, Optional ByVal c As String = Nothing, ParamArray ByVal d() As Integer) + a = 1 + End Sub +End Class" + Const fix1 As String = " +Class TypeName + Public Sub IsReferencing() + Referenced.Foo(1) + End Sub +End Class" + Const fix2 As String = " +Class Referenced + Public Shared Sub Foo(ByVal a As Integer) + a = 1 + End Sub +End Class" + Await VerifyBasicFixAllAsync({source1, source2}, {fix1, fix2}) + End Function + + + Public Async Function FixAllWithNamedParametersInSameClass() As Task + Const source As String = " +Class TypeName + Public Sub IsReferencing() + Me.Foo(b:= 2, a:= 1) + End Sub + Public Sub Foo(ByVal a As Integer, ByVal b As Integer) + a = 1 + End Sub +End Class" + Const fixtest As String = " +Class TypeName + Public Sub IsReferencing() + Me.Foo(a:= 1) + End Sub + Public Sub Foo(ByVal a As Integer) + a = 1 + End Sub +End Class" + Await VerifyBasicFixAllAsync(source, fixtest) + End Function + + Private Function CreateDiagnosticResult(parameterName As String, line As Integer, column As Integer) As DiagnosticResult + Return New DiagnosticResult(DiagnosticId.UnusedParameters.ToDiagnosticId(), Microsoft.CodeAnalysis.DiagnosticSeverity.Warning) _ + .WithLocation(line, column) _ + .WithMessage(String.Format(UnusedParametersAnalyzer.Message, parameterName)) + End Function End Class End Namespace \ No newline at end of file diff --git a/test/VisualBasic/CodeCracker.Test/Usage/UriAnalyzerTests.vb b/test/VisualBasic/CodeCracker.Test/Usage/UriAnalyzerTests.vb index 92978725d..bb54fcd57 100644 --- a/test/VisualBasic/CodeCracker.Test/Usage/UriAnalyzerTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Usage/UriAnalyzerTests.vb @@ -1,6 +1,7 @@ Imports CodeCracker.VisualBasic.Usage Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Usage @@ -78,12 +79,9 @@ End Namespace" End Function Private Shared Function CreateDiagnosticResult(line As Integer, column As Integer, getErrorMessageAction As Action) As DiagnosticResult - Return New DiagnosticResult() With { - .Id = DiagnosticId.Uri.ToDiagnosticId(), - .Message = GetErrorMessage(getErrorMessageAction), - .Severity = DiagnosticSeverity.[Error], - .Locations = New DiagnosticResultLocation() {New DiagnosticResultLocation("Test0.vb", line, column)} - } + Return New DiagnosticResult(DiagnosticId.Uri.ToDiagnosticId(), DiagnosticSeverity.Error) _ + .WithLocation(line, column) _ + .WithMessage(GetErrorMessage(getErrorMessageAction)) End Function Private Shared Function GetErrorMessage(action As Action) As String diff --git a/test/VisualBasic/CodeCracker.Test/packages.config b/test/VisualBasic/CodeCracker.Test/packages.config deleted file mode 100644 index 8eef2be67..000000000 --- a/test/VisualBasic/CodeCracker.Test/packages.config +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file