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 3da239e30..b5c6a38ac 100644 --- a/.gitignore +++ b/.gitignore @@ -192,3 +192,4 @@ ModelManifest.xml log/ .vs nuget.exe +*.nupkg diff --git a/.nuget/packages.config b/.nuget/packages.config index 5e0ac7672..ac480de76 100644 --- a/.nuget/packages.config +++ b/.nuget/packages.config @@ -1,5 +1,10 @@ - + - - + + + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index 252682423..d44726355 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,194 @@ # 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) @@ -22,11 +208,6 @@ - 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) -**Implemented enhancements:** - -- 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) - ## [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) @@ -51,10 +232,13 @@ - 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) @@ -75,6 +259,13 @@ ## [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) @@ -93,6 +284,10 @@ ## [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) @@ -233,6 +428,7 @@ - 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) @@ -307,4 +503,4 @@ -\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* +\* *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 55aa52a2b..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.24720.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 @@ -36,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 @@ -57,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 @@ -88,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 0b2e60b2a..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.23107.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 @@ -42,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}" @@ -56,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 @@ -69,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 @@ -122,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 c771fb052..d30381db7 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,68 @@ # Code Cracker -An analyzer library for C# and VB that uses [Roslyn](https://github.com/dotnet/roslyn) 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](https://img.shields.io/nuget/v/codecracker.svg)](https://www.nuget.org/packages/codecracker/) -[![Nuget downloads](https://img.shields.io/nuget/dt/codecracker.svg)](https://www.nuget.org/packages/codecracker/) +[![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) -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,7 +200,7 @@ 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 @@ -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. @@ -251,7 +281,7 @@ Small code changes or updates outside code files will eventually be made by the 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 +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 c51dd0bf9..455eb5c88 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,42 +1,45 @@ $ErrorActionPreference = "Stop" +$tempDir = Join-Path "$([System.IO.Path]::GetTempPath())" "CodeCracker" +if (!(Test-Path $tempDir)) { mkdir $tempDir | Out-Null } # functions: -function IsNugetVersion3($theNugetExe) { +function IsNugetVersion3OrAbove($theNugetExe) { try { $nugetText = . $theNugetExe | Out-String } catch { return false } - [regex]$regex = '^NuGet Version: (.*)\n' + [regex]$regex = '^NuGet Version: (\d)\.(\d).*\n' $match = $regex.Match($nugetText) $version = $match.Groups[1].Value - return $version.StartsWith(3) + Write-Host "Nuget major version is $version" + return [System.Convert]::ToInt32($version) -ge 3 } function Get-Nuget { if (gcm nuget -ErrorAction SilentlyContinue) { - if (IsNugetVersion3 'nuget') { - return 'nuget' + if (IsNugetVersion3OrAbove 'nuget') { + $script:nugetExe = 'nuget' } else { Download-Nuget - return $localNuget + $script:nugetExe = $localNuget } } else { Download-Nuget - return $localNuget + $script:nugetExe = $localNuget } } function Download-Nuget { $tempNuget = "$env:TEMP\codecracker\nuget.exe" if (!(Test-Path "$env:TEMP\codecracker\")) { - md "$env:TEMP\codecracker\" + md "$env:TEMP\codecracker\" | Out-Null } if (Test-Path $localNuget) { - if (IsNugetVersion3($localNuget)) { return } + if (IsNugetVersion3OrAbove($localNuget)) { return } } if (Test-Path $tempNuget) { - if (IsNugetVersion3($tempNuget)) { + if (IsNugetVersion3OrAbove($tempNuget)) { cp $tempNuget $localNuget return } @@ -46,20 +49,31 @@ function Download-Nuget { } function Import-Psake { - $psakeModule = "$PSScriptRoot\packages\psake.4.5.0\tools\psake.psm1" + $psakeModule = "$PSScriptRoot\packages\psake.4.7.4\tools\psake\psake.psm1" if ((Test-Path $psakeModule) -ne $true) { - . $nugetExe restore $PSScriptRoot\.nuget\packages.config -SolutionDirectory $PSScriptRoot + 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 +$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 diff --git a/build.targets.ps1 b/build.targets.ps1 deleted file mode 100644 index 2e7030fc0..000000000 --- a/build.targets.ps1 +++ /dev/null @@ -1,225 +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.3.3.0\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.1.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.3.5.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 -} - -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 - 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 -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 { . $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 ffae90ba7..f42466e87 100644 --- a/nuget.config +++ b/nuget.config @@ -4,10 +4,12 @@ + + \ No newline at end of file diff --git a/psake.ps1 b/psake.ps1 deleted file mode 100644 index de6e34a2d..000000000 --- a/psake.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -Import-Module $PSScriptRoot\packages\psake.4.5.0\tools\psake.psm1 -force -Invoke-Expression("Invoke-psake -framework '4.5.2' 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 622e9718e..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.1.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 53e5730ea..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.1.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 8c461be91..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 ebbcb04d4..d81e8eee9 100644 --- a/src/CSharp/CodeCracker/CodeCracker.csproj +++ b/src/CSharp/CodeCracker/CodeCracker.csproj @@ -1,5 +1,5 @@  - + 11.0 @@ -10,10 +10,11 @@ Properties CodeCracker.CSharp CodeCracker.CSharp + CodeCracker.CSharp.NewIdRequiredDueToNuGetBug {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Profile7 v4.5 - AD0001 + AD0001,RS1010,RS1016,RS1017,RS1022 true @@ -34,6 +35,15 @@ prompt 4 + + win + true + + + + + + @@ -46,6 +56,8 @@ + + @@ -65,23 +77,29 @@ + + + + - - - + + + + + @@ -114,8 +132,6 @@ - - @@ -136,6 +152,7 @@ + @@ -199,7 +216,6 @@ Designer PreserveNewest - PreserveNewest @@ -221,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 d248171e9..91e3f5c70 100644 --- a/src/CSharp/CodeCracker/CodeCracker.nuspec +++ b/src/CSharp/CodeCracker/CodeCracker.nuspec @@ -2,8 +2,8 @@ codecracker.CSharp - 1.0.0-rc6 - 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 ca0c5506a..82e455d7f 100644 --- a/src/CSharp/CodeCracker/Design/CatchEmptyAnalyzer.cs +++ b/src/CSharp/CodeCracker/Design/CatchEmptyAnalyzer.cs @@ -10,7 +10,7 @@ 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 readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( @@ -33,7 +33,7 @@ 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 if (catchStatement.Block != null) { @@ -42,7 +42,7 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) if (!controlFlow.EndPointIsReachable && controlFlow.ExitPoints.All(i => i.IsKind(SyntaxKind.ThrowStatement))) return; } - var diagnostic = Diagnostic.Create(Rule, catchStatement.GetLocation(), "Consider put an Exception Class in catch."); + 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 9194eecf2..000000000 --- a/src/CSharp/CodeCracker/Design/CopyEventToVariableBeforeFireAnalyzer.cs +++ /dev/null @@ -1,54 +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 firing 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 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."; - internal static readonly 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.Kind == SymbolKind.Local || symbol.Kind == SymbolKind.Parameter || symbol.IsReadOnlyAndInitializedForCertain(context)) 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/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 eb128c92a..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 { @@ -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) { @@ -79,12 +72,20 @@ private static void AnalyzeMethod(SyntaxNodeAnalysisContext context) 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 readonly string[] webFormsMethods = new string[] { + 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", @@ -95,6 +96,11 @@ private static bool IsWebFormsMethod(IMethodSymbol methodSymbol) 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/NameOfAnalyzer.cs b/src/CSharp/CodeCracker/Design/NameOfAnalyzer.cs index 9681645cc..1631dc9f9 100644 --- a/src/CSharp/CodeCracker/Design/NameOfAnalyzer.cs +++ b/src/CSharp/CodeCracker/Design/NameOfAnalyzer.cs @@ -114,6 +114,8 @@ private static string GetParameterNameThatMatchStringLiteral(LiteralExpressionSy break; case SyntaxKind.AttributeList: break; + default: + break; } parameterName = GetParameterWithIdentifierEqualToStringLiteral(stringLiteral, parameters)?.Identifier.Text; } diff --git a/src/CSharp/CodeCracker/Design/StaticConstructorExceptionAnalyzer.cs b/src/CSharp/CodeCracker/Design/StaticConstructorExceptionAnalyzer.cs index 946cae300..c8a9f2f23 100644 --- a/src/CSharp/CodeCracker/Design/StaticConstructorExceptionAnalyzer.cs +++ b/src/CSharp/CodeCracker/Design/StaticConstructorExceptionAnalyzer.cs @@ -10,13 +10,11 @@ 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 readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.StaticConstructorException.ToDiagnosticId(), @@ -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 ceba7ca00..6a30e2533 100644 --- a/src/CSharp/CodeCracker/Design/UseInvokeMethodToFireEventAnalyzer.cs +++ b/src/CSharp/CodeCracker/Design/UseInvokeMethodToFireEventAnalyzer.cs @@ -11,11 +11,12 @@ namespace CodeCracker.CSharp.Design [DiagnosticAnalyzer(LanguageNames.CSharp)] public class UseInvokeMethodToFireEventAnalyzer : DiagnosticAnalyzer { - internal const string Title = "Use Invoke Method To call on delegate"; - internal const string MessageFormat = "Use ?.Invoke operator and method to call on '{0}' delegate."; + 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 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."; + + " 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 readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), @@ -30,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) { @@ -48,17 +49,20 @@ 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) as MethodDeclarationSyntax; + var method = invocation.FirstAncestorOfKind(SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration) as BaseMethodDeclarationSyntax; if (method != null && method.Body != null) { var ifs = method.Body.Statements.OfKind(SyntaxKind.IfStatement); @@ -90,22 +94,37 @@ private static bool HasCheckForNullThatReturns(InvocationExpressionSyntax invoca return false; } - private static bool IsInsideANullCheck(InvocationExpressionSyntax invocation, SemanticModel semanticModel, ISymbol symbol) + 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) { - var ifs = invocation.Ancestors().OfType(); - foreach (IfStatementSyntax @if in ifs) - { - if (!@if.Condition?.IsKind(SyntaxKind.NotEqualsExpression) ?? true) continue; - var equals = (BinaryExpressionSyntax)@if.Condition; - if (equals.Left == null || equals.Right == null) continue; - 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)) return true; - } + 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; } } diff --git a/src/CSharp/CodeCracker/Design/UseInvokeMethodToFireEventCodeFixProvider.cs b/src/CSharp/CodeCracker/Design/UseInvokeMethodToFireEventCodeFixProvider.cs index a4a62017c..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("Change to ?.Invoke to call a delegate", 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 0d82e7692..6b79daa2b 100644 --- a/src/CSharp/CodeCracker/Extensions/CSharpAnalyzerExtensions.cs +++ b/src/CSharp/CodeCracker/Extensions/CSharpAnalyzerExtensions.cs @@ -17,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) { @@ -312,6 +317,8 @@ private static string GetLastIdentifierValueText(CSharpSyntaxNode node) case SyntaxKind.AliasQualifiedName: result = ((AliasQualifiedNameSyntax)node).Name.Identifier.ValueText; break; + default: + break; } return result; } @@ -331,8 +338,9 @@ public static SyntaxToken GetIdentifier(this BaseMethodDeclarationSyntax method) case SyntaxKind.DestructorDeclaration: result = ((DestructorDeclarationSyntax)method).Identifier; break; + default: + return result; } - return result; } @@ -387,6 +395,8 @@ public static MemberDeclarationSyntax WithModifiers(this MemberDeclarationSyntax case SyntaxKind.EventDeclaration: result = ((EventDeclarationSyntax)declaration).WithModifiers(newModifiers); break; + default: + break; } return result; @@ -423,6 +433,8 @@ public static SyntaxTokenList GetModifiers(this MemberDeclarationSyntax memberDe case SyntaxKind.EventDeclaration: result = ((BasePropertyDeclarationSyntax)memberDeclaration).Modifiers; break; + default: + break; } return result; @@ -469,6 +481,13 @@ public static IEnumerable OfKind(this IEnumerable node 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) @@ -644,7 +663,7 @@ private static InitializerState DoesBlockContainCertainInitializer(this IEnumera var ifResult = ifStatement.Statement.DoesBlockContainCertainInitializer(context, symbol); if (ifStatement.Else != null) { - var elseResult = ifStatement.Else.Statement .DoesBlockContainCertainInitializer(context, symbol); + var elseResult = ifStatement.Else.Statement.DoesBlockContainCertainInitializer(context, symbol); if (ifResult == InitializerState.Initializer && elseResult == InitializerState.Initializer) currentState = InitializerState.Initializer; @@ -707,5 +726,208 @@ public static SyntaxTokenList GetTokens(this Accessibility accessibility) 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(" 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; @@ -54,15 +64,13 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) 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/MakeLocalVariableConstWhenItIsPossibleAnalyzer.cs b/src/CSharp/CodeCracker/Performance/MakeLocalVariableConstWhenItIsPossibleAnalyzer.cs index f5c8e2b29..f49aba20a 100644 --- a/src/CSharp/CodeCracker/Performance/MakeLocalVariableConstWhenItIsPossibleAnalyzer.cs +++ b/src/CSharp/CodeCracker/Performance/MakeLocalVariableConstWhenItIsPossibleAnalyzer.cs @@ -12,7 +12,7 @@ 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 readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( @@ -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 6ddf3bb23..9ba262a53 100644 --- a/src/CSharp/CodeCracker/Performance/RemoveWhereWhenItIsPossibleAnalyzer.cs +++ b/src/CSharp/CodeCracker/Performance/RemoveWhereWhenItIsPossibleAnalyzer.cs @@ -25,8 +25,17 @@ public class RemoveWhereWhenItIsPossibleAnalyzer : DiagnosticAnalyzer "Any", "Single", "SingleOrDefault", - "Count" + "Count", + "FirstAsync", + "FirstOrDefaultAsync", + "LastAsync", + "LastOrDefaultAsync", + "AnyAsync", + "SingleAsync", + "SingleOrDefaultAsync", + "CountAsync" }; + internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.RemoveWhereWhenItIsPossible.ToDiagnosticId(), Title, @@ -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/StringBuilderInLoopAnalyzer.cs b/src/CSharp/CodeCracker/Performance/StringBuilderInLoopAnalyzer.cs index 1ef255876..60960be01 100644 --- a/src/CSharp/CodeCracker/Performance/StringBuilderInLoopAnalyzer.cs +++ b/src/CSharp/CodeCracker/Performance/StringBuilderInLoopAnalyzer.cs @@ -14,8 +14,7 @@ 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 readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.StringBuilderInLoop.ToDiagnosticId(), @@ -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 2ecfa57dc..19c4e2374 100644 --- a/src/CSharp/CodeCracker/Performance/UseStaticRegexIsMatchAnalyzer.cs +++ b/src/CSharp/CodeCracker/Performance/UseStaticRegexIsMatchAnalyzer.cs @@ -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/Properties/AssemblyInfo.cs b/src/CSharp/CodeCracker/Properties/AssemblyInfo.cs index 9bbcf16e1..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.13")] +[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 33012a4af..6ef64c506 100644 --- a/src/CSharp/CodeCracker/Refactoring/AddBracesToSwitchSectionsAnalyzer.cs +++ b/src/CSharp/CodeCracker/Refactoring/AddBracesToSwitchSectionsAnalyzer.cs @@ -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/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 aada36ec3..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,10 +12,10 @@ 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 readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.IntroduceFieldFromConstructor.ToDiagnosticId(), @@ -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 ed2730ca6..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; @@ -14,12 +16,14 @@ namespace CodeCracker.CSharp.Refactoring { [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(IntroduceFieldFromConstructorCodeFixProvider)), Shared] - public class IntroduceFieldFromConstructorCodeFixProvider : CodeFixProvider + 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,24 +33,32 @@ 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())) @@ -62,7 +74,7 @@ public static SyntaxNode IntroduceFieldFromConstructor(SyntaxNode root, Construc 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(constructorStatement, newConstructor); + newType = newType.ReplaceNode(constructorStatement, newConstructor); if (addMember) { @@ -70,16 +82,16 @@ public static SyntaxNode IntroduceFieldFromConstructor(SyntaxNode root, Construc .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); + newType = newType.WithMembers(newType.Members.Insert(0, newField)).WithoutAnnotations(Formatter.Annotation); } - var newRoot = root.ReplaceNode(oldClass, newClass); + 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/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/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/SplitIntoNestedIfFixAllProvider.cs b/src/CSharp/CodeCracker/Refactoring/SplitIntoNestedIfFixAllProvider.cs index 568e8af7c..3ca46c2ef 100644 --- a/src/CSharp/CodeCracker/Refactoring/SplitIntoNestedIfFixAllProvider.cs +++ b/src/CSharp/CodeCracker/Refactoring/SplitIntoNestedIfFixAllProvider.cs @@ -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/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/Style/AlwaysUseVarAnalyzer.cs b/src/CSharp/CodeCracker/Style/AlwaysUseVarAnalyzer.cs index dc412018c..7e5c5aab2 100644 --- a/src/CSharp/CodeCracker/Style/AlwaysUseVarAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/AlwaysUseVarAnalyzer.cs @@ -71,33 +71,9 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) } } - var rule = IsPrimitvie(variableType) ? RulePrimitives : RuleNonPrimitives; + var rule = variableType.IsPrimitive() ? RulePrimitives : RuleNonPrimitives; var diagnostic = Diagnostic.Create(rule, variableDeclaration.Type.GetLocation()); context.ReportDiagnostic(diagnostic); } - - private static bool IsPrimitvie(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/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 671e55349..bb335f76d 100644 --- a/src/CSharp/CodeCracker/Style/ConvertLambdaExpressionToMethodGroupAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/ConvertLambdaExpressionToMethodGroupAnalyzer.cs @@ -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/EmptyObjectInitializerAnalyzer.cs b/src/CSharp/CodeCracker/Style/EmptyObjectInitializerAnalyzer.cs index 7597ea48c..1ae23376f 100644 --- a/src/CSharp/CodeCracker/Style/EmptyObjectInitializerAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/EmptyObjectInitializerAnalyzer.cs @@ -12,8 +12,7 @@ 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 readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.EmptyObjectInitializer.ToDiagnosticId(), @@ -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/ForInArrayAnalyzer.cs b/src/CSharp/CodeCracker/Style/ForInArrayAnalyzer.cs index 300462f5e..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 { @@ -65,22 +67,52 @@ 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 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 diff --git a/src/CSharp/CodeCracker/Style/ObjectInitializerAnalyzer.cs b/src/CSharp/CodeCracker/Style/ObjectInitializerAnalyzer.cs index 5dc26127d..4e67acf8b 100644 --- a/src/CSharp/CodeCracker/Style/ObjectInitializerAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/ObjectInitializerAnalyzer.cs @@ -76,6 +76,7 @@ private static void AnalyzeLocalDeclaration(SyntaxNodeAnalysisContext context) 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 11da2a447..258d8975a 100644 --- a/src/CSharp/CodeCracker/Style/ObjectInitializerCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Style/ObjectInitializerCodeFixProvider.cs @@ -131,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/RemoveCommentedCodeAnalyzer.cs b/src/CSharp/CodeCracker/Style/RemoveCommentedCodeAnalyzer.cs index d94d0374d..a25c49944 100644 --- a/src/CSharp/CodeCracker/Style/RemoveCommentedCodeAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/RemoveCommentedCodeAnalyzer.cs @@ -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/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/StringFormatCodeFixProvider.cs b/src/CSharp/CodeCracker/Style/StringFormatCodeFixProvider.cs index 0c7df73b1..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; 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 81d29b07e..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 readonly 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 87dd2f577..a51c2dff6 100644 --- a/src/CSharp/CodeCracker/Style/SwitchToAutoPropAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/SwitchToAutoPropAnalyzer.cs @@ -56,13 +56,13 @@ private static void AnalyzeProperty(SyntaxNodeAnalysisContext context, bool canH ((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 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 index 5499d07c8..8fa876860 100644 --- a/src/CSharp/CodeCracker/Style/SwitchToAutoPropCodeFixAllProvider.cs +++ b/src/CSharp/CodeCracker/Style/SwitchToAutoPropCodeFixAllProvider.cs @@ -27,8 +27,9 @@ public override Task GetFixAsync(FixAllContext fixAllContext) case FixAllScope.Solution: return Task.FromResult(CodeAction.Create(Resources.SwitchToAutoPropCodeFixProvider_Title, async ct => await GetFixedSolutionAsync(fixAllContext, await GetSolutionWithDocsAsync(fixAllContext, fixAllContext.Solution)))); + default: + return null; } - return null; } private async static Task GetSolutionWithDocsAsync(FixAllContext fixAllContext, Solution solution) diff --git a/src/CSharp/CodeCracker/Style/SwitchToAutoPropCodeFixProvider.cs b/src/CSharp/CodeCracker/Style/SwitchToAutoPropCodeFixProvider.cs index 093b9e7de..b5a2bdfd6 100644 --- a/src/CSharp/CodeCracker/Style/SwitchToAutoPropCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Style/SwitchToAutoPropCodeFixProvider.cs @@ -42,53 +42,75 @@ public async static Task MakeAutoPropertyAsync(Document document, Synt { 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 is MemberAccessExpressionSyntax ? ((MemberAccessExpressionSyntax)getterReturn.Expression).Name : 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(); - var fieldSymbol = (IFieldSymbol)semanticModel.GetDeclaredSymbol(variableDeclarator); var propertySymbol = semanticModel.GetDeclaredSymbol(property); - 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); + 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 (!propertySymbol.ExplicitInterfaceImplementations.Any()) + if (IsExplicityImplementation(propertySymbol)) { - newProperty = newProperty.WithModifiers(propertySymbol.DeclaredAccessibility - .GetMinimumCommonAccessibility(fieldSymbol.DeclaredAccessibility) - .GetTokens()); - newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, returnIdentifierSymbol, property.Identifier.ValueText, document.Project.Solution.Workspace.Options, cancellationToken).ConfigureAwait(false); + 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 { - newSolution = await RenameSymbolAndKeepExplicitPropertiesBoundAsync(document.Project.Solution, property.Identifier.ValueText, returnIdentifierSymbol, propertySymbol, cancellationToken); + 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); } - newProperty = newProperty - .WithTriviaFrom(property) - .WithAdditionalAnnotations(Formatter.Annotation); - document = newSolution.GetDocument(document.Id); - root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - root = root.InsertNodesAfter(root.GetCurrentNode(property), new[] { newProperty }); + newDocument = newSolution.GetDocument(newDocument.Id); + newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + newRoot = RemoveField(newRoot, variableDeclarator, fieldDeclaration); + return newSolution.WithDocumentSyntaxRoot(newDocument.Id, newRoot); + } + + private static SyntaxNode RemoveField(SyntaxNode root, VariableDeclaratorSyntax variableDeclarator, FieldDeclarationSyntax fieldDeclaration) + { + var currentField = root.GetCurrentNode(fieldDeclaration); var multipleVariableDeclaration = fieldDeclaration.Declaration.Variables.Count > 1; - if (multipleVariableDeclaration) - { - 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); - } - else - { - root = root.RemoveNodes(root.GetCurrentNodes(new SyntaxNode[] { fieldDeclaration, property }), SyntaxRemoveOptions.KeepNoTrivia); - } - document = document.WithSyntaxRoot(root); - return document.Project.Solution; + root = multipleVariableDeclaration + ? root.ReplaceNode(currentField, fieldDeclaration + .WithDeclaration(fieldDeclaration.Declaration.RemoveNode(variableDeclarator, SyntaxRemoveOptions.KeepNoTrivia))) + : root.RemoveNode(currentField, SyntaxRemoveOptions.KeepNoTrivia); + return root; } - private static async Task RenameSymbolAndKeepExplicitPropertiesBoundAsync(Solution solution, string propertyName, ISymbol returnIdentifierSymbol, IPropertySymbol propertySymbol, CancellationToken cancellationToken) + private static async Task RenameSymbolAndKeepExplicitPropertiesBoundAsync(Solution solution, string propertyName, + ISymbol returnIdentifierSymbol, IPropertySymbol propertySymbol, CancellationToken cancellationToken) { var interfaceType = propertySymbol.ExplicitInterfaceImplementations.First().ContainingType; var references = await SymbolFinder.FindReferencesAsync(returnIdentifierSymbol, solution, cancellationToken).ConfigureAwait(false); @@ -132,16 +154,62 @@ private static async Task RenameSymbolAndKeepExplicitPropertiesBoundAs return newSolution; } - private static PropertyDeclarationSyntax GetSimpleProperty(PropertyDeclarationSyntax property, VariableDeclaratorSyntax variableDeclarator) + private static PropertyDeclarationSyntax CreateAutoProperty(PropertyDeclarationSyntax property, VariableDeclaratorSyntax variableDeclarator, + IFieldSymbol fieldSymbol, IPropertySymbol propertySymbol) { - var simpleGetSetPropetie = property.WithAccessorList(SyntaxFactory.AccessorList(SyntaxFactory.List(new[] { + 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 5fd40d27c..160d792e2 100644 --- a/src/CSharp/CodeCracker/Style/TaskNameAsyncAnalyzer.cs +++ b/src/CSharp/CodeCracker/Style/TaskNameAsyncAnalyzer.cs @@ -3,16 +3,17 @@ 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 readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.TaskNameAsync.ToDiagnosticId(), @@ -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/TernaryOperatorCodeFixProvider.cs b/src/CSharp/CodeCracker/Style/TernaryOperatorCodeFixProvider.cs index cae303ff2..b36fa9584 100644 --- a/src/CSharp/CodeCracker/Style/TernaryOperatorCodeFixProvider.cs +++ b/src/CSharp/CodeCracker/Style/TernaryOperatorCodeFixProvider.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using System; namespace CodeCracker.CSharp.Style { @@ -37,11 +38,10 @@ private static async Task MakeTernaryAsync(Document document, Diagnost var elseStatement = ifStatement.Else; var returnStatementInsideElse = (ReturnStatementSyntax)(elseStatement.Statement is BlockSyntax ? ((BlockSyntax)elseStatement.Statement).Statements.Single() : elseStatement.Statement); var semanticModel = await document.GetSemanticModelAsync(cancellationToken); - ExpressionSyntax trueExpression, falseExpression; - TernaryOperatorCodeFixHelper.CreateExpressions(returnStatementInsideIf.Expression, returnStatementInsideElse.Expression, semanticModel, out trueExpression, out falseExpression); + 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); @@ -76,14 +76,13 @@ private static async Task MakeTernaryAsync(Document document, Diagnost var assignmentExpressionInsideIf = (AssignmentExpressionSyntax)expressionInsideIf.Expression; var assignmentExpressionInsideElse = (AssignmentExpressionSyntax)expressionInsideElse.Expression; var semanticModel = await document.GetSemanticModelAsync(cancellationToken); - ExpressionSyntax trueExpression, falseExpression; - TernaryOperatorCodeFixHelper.CreateExpressions(assignmentExpressionInsideIf.Right, assignmentExpressionInsideElse.Right, semanticModel, out trueExpression, out falseExpression); + 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); @@ -95,13 +94,98 @@ private static async Task MakeTernaryAsync(Document document, Diagnost internal static class TernaryOperatorCodeFixHelper { - public static void CreateExpressions(ExpressionSyntax ifExpression, ExpressionSyntax elseExpression, SemanticModel semanticModel, out ExpressionSyntax trueExpression, out ExpressionSyntax falseExpression) + 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, @@ -135,23 +219,26 @@ private static void CreateExpressions(ExpressionSyntax ifExpression, ExpressionS { falseExpression = elseExpression; } - if (!isNullable && !ifType.CanBeAssignedTo(elseType) || !elseType.CanBeAssignedTo(ifType)) - trueExpression = CastToBaseType(ifExpression, ifType, elseType, trueExpression); + 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 ExpressionSyntax CastToBaseType(ExpressionSyntax ifExpression, ITypeSymbol ifType, ITypeSymbol elseType, ExpressionSyntax trueExpression) - { - var commonBaseType = ifType.GetCommonBaseType(elseType); - if (commonBaseType != null) - trueExpression = SyntaxFactory.CastExpression(SyntaxFactory.ParseTypeName(commonBaseType.Name).WithAdditionalAnnotations(Simplifier.Annotation), ifExpression); - return trueExpression; - } + 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) { @@ -160,21 +247,5 @@ private static bool CanBeAssignedTo(this ITypeSymbol type, ITypeSymbol possibleB } return false; } - - private static ITypeSymbol GetCommonBaseType(this ITypeSymbol type, ITypeSymbol otherType) - { - var baseType = type; - while (baseType != null) - { - var otherBaseType = otherType; - while (otherBaseType != null) - { - if (baseType.Equals(otherBaseType)) return baseType; - otherBaseType = otherBaseType.BaseType; - } - baseType = baseType.BaseType; - } - return null; - } } } \ No newline at end of file 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/UseEmptyStringCodeFixProviderAll.cs b/src/CSharp/CodeCracker/Style/UseEmptyStringCodeFixProviderAll.cs index a26d828df..0b0915ee3 100644 --- a/src/CSharp/CodeCracker/Style/UseEmptyStringCodeFixProviderAll.cs +++ b/src/CSharp/CodeCracker/Style/UseEmptyStringCodeFixProviderAll.cs @@ -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/UseStringEmptyCodeFixProviderAll.cs b/src/CSharp/CodeCracker/Style/UseStringEmptyCodeFixProviderAll.cs index 0498b21d0..6d7e92057 100644 --- a/src/CSharp/CodeCracker/Style/UseStringEmptyCodeFixProviderAll.cs +++ b/src/CSharp/CodeCracker/Style/UseStringEmptyCodeFixProviderAll.cs @@ -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 4e70142cd..273f37ee7 100644 --- a/src/CSharp/CodeCracker/Usage/AbstractClassShouldNotHavePublicCtorsAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/AbstractClassShouldNotHavePublicCtorsAnalyzer.cs @@ -1,16 +1,16 @@ -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; @@ -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/CallExtensionMethodAsExtensionAnalyzer.cs b/src/CSharp/CodeCracker/Usage/CallExtensionMethodAsExtensionAnalyzer.cs index 15d9bff32..0a6f14de5 100644 --- a/src/CSharp/CodeCracker/Usage/CallExtensionMethodAsExtensionAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/CallExtensionMethodAsExtensionAnalyzer.cs @@ -46,6 +46,7 @@ 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); @@ -66,12 +67,15 @@ private static bool IsSelectingADifferentMethod(IEnumerable childNod var speculativeRootWithExtensionMethod = tree.GetCompilationUnitRoot() .ReplaceNode(invocationExpression, newInvocationStatement) .AddUsings(extensionMethodNamespaceUsingDirective); - var speculativeModel = compilation.ReplaceSyntaxTree(tree, speculativeRootWithExtensionMethod.SyntaxTree) - .GetSemanticModel(speculativeRootWithExtensionMethod.SyntaxTree); - var speculativeInvocationStatement = speculativeRootWithExtensionMethod.SyntaxTree.GetCompilationUnitRoot().GetAnnotatedNodes(introduceExtensionMethodAnnotation).Single() as InvocationExpressionSyntax; + 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) => diff --git a/src/CSharp/CodeCracker/Usage/DisposableFieldNotDisposedAnalyzer.cs b/src/CSharp/CodeCracker/Usage/DisposableFieldNotDisposedAnalyzer.cs index 3632de804..b90b12001 100644 --- a/src/CSharp/CodeCracker/Usage/DisposableFieldNotDisposedAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/DisposableFieldNotDisposedAnalyzer.cs @@ -43,6 +43,7 @@ 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; @@ -91,14 +92,41 @@ private static bool CallsDisposeOnField(IFieldSymbol fieldSymbol, MethodDeclarat var hasDisposeCall = body.DescendantNodes().OfKind(SyntaxKind.InvocationExpression) .Any(invocation => { - if (!invocation?.Expression?.IsKind(SyntaxKind.SimpleMemberAccessExpression) ?? true) return false; - var memberAccess = (MemberAccessExpressionSyntax)invocation.Expression; - if (memberAccess?.Name == null) return false; - if (memberAccess.Name.Identifier.ToString() != "Dispose" || memberAccess.Name.Arity != 0) return false; - if (!memberAccess.Expression.IsKind(SyntaxKind.IdentifierName)) return false; - return fieldSymbol.Equals(semanticModel.GetSymbolInfo(memberAccess.Expression).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 fc3cbe6d9..83544effa 100644 --- a/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedAnalyzer.cs @@ -40,13 +40,14 @@ private static void AnalyzeObjectCreation(SyntaxNodeAnalysisContext context) var originalNode = objectCreation; SyntaxNode topSyntaxNode = originalNode; - while (topSyntaxNode.Parent.IsAnyKind(SyntaxKind.ParenthesizedExpression, SyntaxKind.ConditionalExpression, SyntaxKind.CastExpression)) + while (topSyntaxNode.Parent.IsAnyKind(SyntaxKind.ParenthesizedExpression, SyntaxKind.ConditionalExpression, SyntaxKind.CastExpression, SyntaxKind.CoalesceExpression)) topSyntaxNode = topSyntaxNode.Parent; - if (topSyntaxNode.Parent.IsAnyKind(SyntaxKind.ReturnStatement, SyntaxKind.UsingStatement)) + 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))) @@ -77,6 +78,28 @@ private static void AnalyzeObjectCreation(SyntaxNodeAnalysisContext context) 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)) @@ -107,16 +130,30 @@ private static bool IsDisposedOrAssigned(SemanticModel semanticModel, StatementS var method = statement.FirstAncestorOrSelf(); if (method == null) return false; if (IsReturned(method, statement, semanticModel, identitySymbol)) return true; - foreach (var childStatements in method.Body.DescendantNodes().OfType()) + foreach (var childStatement in method.Body.DescendantNodes().OfType()) { - if (childStatements.SpanStart > statement.SpanStart - && (IsCorrectDispose(childStatements as ExpressionStatementSyntax, semanticModel, identitySymbol) - || IsAssignedToField(childStatements 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 IsPassedAsArgument(StatementSyntax statement, SemanticModel semanticModel, ILocalSymbol identitySymbol) + { + 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, @@ -134,10 +171,12 @@ private static bool IsReturned(MethodDeclarationSyntax method, StatementSyntax s body = method.Body; } if (body == null) return true; - var returnExpressions = body.DescendantNodes().OfType().Select(r => r.Expression); var returnTypeSymbol = methodSymbol?.ReturnType; if (returnTypeSymbol == null) return false; if (returnTypeSymbol.SpecialType == SpecialType.System_Void) return false; + 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(returnExpression).Symbol; @@ -147,13 +186,13 @@ private static bool IsReturned(MethodDeclarationSyntax method, StatementSyntax s 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)); } @@ -162,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; @@ -181,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/DisposableVariableNotDisposedFixAllProvider.cs b/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedFixAllProvider.cs index d49053746..d34c097b0 100644 --- a/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedFixAllProvider.cs +++ b/src/CSharp/CodeCracker/Usage/DisposableVariableNotDisposedFixAllProvider.cs @@ -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 4ab5b1e3a..b70f8d735 100644 --- a/src/CSharp/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/DisposablesShouldCallSuppressFinalizeAnalyzer.cs @@ -38,6 +38,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context) 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("); @@ -56,24 +57,22 @@ private static void Analyze(SyntaxNodeAnalysisContext context) if (symbol.IsSealed && !ContainsUserDefinedFinalizer(symbol)) return; if (!ContainsNonPrivateConstructors(symbol)) return; - var statements = method.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 suppress = invocation?.Expression as MemberAccessExpressionSyntax; + var suppress = (expression as InvocationExpressionSyntax)?.Expression as MemberAccessExpressionSyntax; - if (suppress?.Name.ToString() != "SuppressFinalize") - continue; + if (suppress?.Name.ToString() != "SuppressFinalize") + continue; - var containingType = semanticModel.GetSymbolInfo(suppress.Expression).Symbol as INamedTypeSymbol; - if (containingType?.ContainingNamespace.Name != "System") - continue; + var containingType = semanticModel.GetSymbolInfo(suppress.Expression).Symbol as INamedTypeSymbol; + if (containingType?.ContainingNamespace.Name != "System") + continue; - if (containingType.Name == "GC") - return; - } + if (containingType.Name == "GC") + return; } context.ReportDiagnostic(Diagnostic.Create(Rule, methodSymbol.Locations[0], symbol.Name)); } @@ -104,4 +103,4 @@ private static bool IsNestedPrivateType(ISymbol symbol) return IsNestedPrivateType(symbol.ContainingType); } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Usage/IPAddressAnalyzer.cs b/src/CSharp/CodeCracker/Usage/IPAddressAnalyzer.cs index 5bf5ea300..ba2b3b03b 100644 --- a/src/CSharp/CodeCracker/Usage/IPAddressAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/IPAddressAnalyzer.cs @@ -11,13 +11,12 @@ 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 readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.IPAddress.ToDiagnosticId(), @@ -56,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 17afe4477..c03121553 100644 --- a/src/CSharp/CodeCracker/Usage/IfReturnTrueAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/IfReturnTrueAnalyzer.cs @@ -12,8 +12,7 @@ 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 readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.IfReturnTrue.ToDiagnosticId(), @@ -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/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 cf4e39b76..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 { @@ -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) @@ -59,6 +73,13 @@ private static void AnalyzeTree(SyntaxTreeAnalysisContext context, Compilation c ? semanticModel : compilation.GetSemanticModel(syntaxReference.SyntaxTree); var descendants = syntaxReference.GetSyntax().DescendantNodes().ToList(); + var argsWithRefOrOut = descendants.OfType().Where(a => a.RefOrOutKeyword != null); + foreach (var argWithRefOrOut in argsWithRefOrOut) + { + var fieldSymbol = syntaxRefSemanticModel.GetSymbolInfo(argWithRefOrOut.Expression).Symbol as IFieldSymbol; + if (fieldSymbol == null) continue; + variablesToMakeReadonly.Remove(fieldSymbol); + } var assignments = descendants.OfKind(SyntaxKind.SimpleAssignmentExpression, SyntaxKind.AddAssignmentExpression, SyntaxKind.AndAssignmentExpression, SyntaxKind.DivideAssignmentExpression, SyntaxKind.ExclusiveOrAssignmentExpression, SyntaxKind.LeftShiftAssignmentExpression, SyntaxKind.ModuloAssignmentExpression, @@ -96,26 +117,30 @@ private static void VerifyVariable(Dictionary variablesToMakeReadonly, IFieldSymbol fieldSymbol, SyntaxNode assignment, SemanticModel semanticModel) + private static bool HasAssignmentInLambda(SyntaxNode assignment) { var parent = assignment.Parent; while (parent != null) { if (parent is AnonymousFunctionExpressionSyntax) - return; - if (parent is ConstructorDeclarationSyntax) - break; + return true; parent = parent.Parent; } + return false; + } - if (!fieldSymbol.IsReadOnly && !variablesToMakeReadonly.Keys.Contains(fieldSymbol)) + 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; @@ -144,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; @@ -166,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(); 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/RegexAnalyzer.cs b/src/CSharp/CodeCracker/Usage/RegexAnalyzer.cs index 6ce7c1a5d..41393ac96 100644 --- a/src/CSharp/CodeCracker/Usage/RegexAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/RegexAnalyzer.cs @@ -10,11 +10,10 @@ 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 readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.Regex.ToDiagnosticId(), @@ -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 335173ed5..26b80458c 100644 --- a/src/CSharp/CodeCracker/Usage/RemovePrivateMethodNeverUsedAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/RemovePrivateMethodNeverUsedAnalyzer.cs @@ -16,7 +16,7 @@ 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 readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.RemovePrivateMethodNeverUsed.ToDiagnosticId(), @@ -44,6 +44,7 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) 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); @@ -58,7 +59,17 @@ private static bool IsMethodAttributeAnException(MethodDeclarationSyntax methodD foreach (var attribute in attributeList.Attributes) { var identifierName = attribute.Name as IdentifierNameSyntax; - var nameText = identifierName?.Identifier.Text; + 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; } @@ -115,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/RemoveUnreachableCodeFixAllProvider.cs b/src/CSharp/CodeCracker/Usage/RemoveUnreachableCodeFixAllProvider.cs index 936cb1ef5..499cbd6b7 100644 --- a/src/CSharp/CodeCracker/Usage/RemoveUnreachableCodeFixAllProvider.cs +++ b/src/CSharp/CodeCracker/Usage/RemoveUnreachableCodeFixAllProvider.cs @@ -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 410c24501..cf2376d77 100644 --- a/src/CSharp/CodeCracker/Usage/RethrowExceptionAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/RethrowExceptionAnalyzer.cs @@ -13,9 +13,8 @@ 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 readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.RethrowException.ToDiagnosticId(), @@ -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/StringFormatArgsAnalyzer.cs b/src/CSharp/CodeCracker/Usage/StringFormatArgsAnalyzer.cs index 397a4961d..3213dfe6c 100644 --- a/src/CSharp/CodeCracker/Usage/StringFormatArgsAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/StringFormatArgsAnalyzer.cs @@ -15,7 +15,8 @@ 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 readonly DiagnosticDescriptor ExtraArgs = new DiagnosticDescriptor( DiagnosticId.StringFormatArgs_ExtraArgs.ToDiagnosticId(), @@ -83,4 +84,4 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) } } } -} \ No newline at end of file +} diff --git a/src/CSharp/CodeCracker/Usage/UnusedParametersAnalyzer.cs b/src/CSharp/CodeCracker/Usage/UnusedParametersAnalyzer.cs index 9f1071190..db1cbdf65 100644 --- a/src/CSharp/CodeCracker/Usage/UnusedParametersAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/UnusedParametersAnalyzer.cs @@ -14,8 +14,7 @@ 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 readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId.UnusedParameters.ToDiagnosticId(), @@ -59,6 +58,12 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) } 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) @@ -110,19 +115,21 @@ private static void Analyzer(SyntaxNodeAnalysisContext context) private static bool IdentifierRefersToParam(IdentifierNameSyntax iName, ParameterSyntax param) { - if (iName.Identifier.ToString() != param.Identifier.ToString()) + 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" || m.ValueText == "abstract") + 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; @@ -207,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 index e41a8fa8b..74650b882 100644 --- a/src/CSharp/CodeCracker/Usage/UnusedParametersCodeFixAllProvider.cs +++ b/src/CSharp/CodeCracker/Usage/UnusedParametersCodeFixAllProvider.cs @@ -27,8 +27,9 @@ public override Task GetFixAsync(FixAllContext fixAllContext) case FixAllScope.Solution: return Task.FromResult(CodeAction.Create(message, async ct => await GetFixedSolutionAsync(fixAllContext, await GetSolutionWithDocsAsync(fixAllContext, fixAllContext.Solution)))); + default: + return null; } - return null; } private async static Task GetSolutionWithDocsAsync(FixAllContext fixAllContext, Solution solution) diff --git a/src/CSharp/CodeCracker/Usage/VirtualMethodOnConstructorAnalyzer.cs b/src/CSharp/CodeCracker/Usage/VirtualMethodOnConstructorAnalyzer.cs index ce6cfa2eb..f0d2becef 100644 --- a/src/CSharp/CodeCracker/Usage/VirtualMethodOnConstructorAnalyzer.cs +++ b/src/CSharp/CodeCracker/Usage/VirtualMethodOnConstructorAnalyzer.cs @@ -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 776175206..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 9de77c8de..000000000 --- a/src/CodeCracker.nuspec +++ /dev/null @@ -1,30 +0,0 @@ - - - - codecracker - 1.0.0-rc6 - 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 109c559bf..8f502cca1 100644 --- a/src/Common/CodeCracker.Common/CodeCracker.Common.csproj +++ b/src/Common/CodeCracker.Common/CodeCracker.Common.csproj @@ -1,5 +1,5 @@  - + 11.0 @@ -35,11 +35,22 @@ prompt 4 + + win + true + + + + + + + + @@ -54,11 +65,6 @@ - - - Designer - - ResXFileCodeGenerator @@ -69,56 +75,5 @@ 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 b3e757224..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, @@ -73,12 +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 af2272c40..cc3802231 100644 --- a/src/Common/CodeCracker.Common/Extensions/AnalyzerExtensions.cs +++ b/src/Common/CodeCracker.Common/Extensions/AnalyzerExtensions.cs @@ -125,6 +125,8 @@ public static string GetLastIdentifierIfQualiedTypeName(this string typeName) 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)) @@ -166,5 +168,29 @@ public static Accessibility GetMinimumCommonAccessibility(this Accessibility acc 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/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 ae8fadbe6..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.13")] +[assembly: AssemblyFileVersion("1.1.0.0")] [assembly: InternalsVisibleTo("CodeCracker.Test.CSharp")] [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 a9cc2ac5f..af567f32d 100644 --- a/src/Common/CodeCracker.Common/Properties/Resources.Designer.cs +++ b/src/Common/CodeCracker.Common/Properties/Resources.Designer.cs @@ -214,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. /// @@ -259,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.resx b/src/Common/CodeCracker.Common/Properties/Resources.fr.resx index a7953b4d6..630db6544 100644 --- a/src/Common/CodeCracker.Common/Properties/Resources.fr.resx +++ b/src/Common/CodeCracker.Common/Properties/Resources.fr.resx @@ -214,4 +214,16 @@ Si l'erreur est attendu considérer ajouter du logging ou modifier le flow de co 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 1b6e5d917..944e7b07e 100644 --- a/src/Common/CodeCracker.Common/Properties/Resources.resx +++ b/src/Common/CodeCracker.Common/Properties/Resources.resx @@ -216,4 +216,43 @@ 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 c412e21be..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 baa76ca16..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 6571d3534..8c35dc85b 100644 --- a/src/VisualBasic/CodeCracker/CodeCracker.nuspec +++ b/src/VisualBasic/CodeCracker/CodeCracker.nuspec @@ -2,8 +2,8 @@ codecracker.VisualBasic - 1.0.0-rc6 - 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 500c6b6eb..877bee09b 100644 --- a/src/VisualBasic/CodeCracker/CodeCracker.vbproj +++ b/src/VisualBasic/CodeCracker/CodeCracker.vbproj @@ -1,5 +1,5 @@  - + 11.0 @@ -9,11 +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 + AD0001,RS1010,RS1016,RS1017,RS1022 true @@ -54,12 +55,18 @@ On + + win + true + + + + + + - - Designer - PreserveNewest @@ -161,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/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/Extensions/VBAnalyzerExtensions.vb b/src/VisualBasic/CodeCracker/Extensions/VBAnalyzerExtensions.vb index 9b3ed1894..d7fe23787 100644 --- a/src/VisualBasic/CodeCracker/Extensions/VBAnalyzerExtensions.vb +++ b/src/VisualBasic/CodeCracker/Extensions/VBAnalyzerExtensions.vb @@ -42,10 +42,28 @@ Public Module VBAnalyzerExtensions Public Function ConvertToBaseType(source As ExpressionSyntax, sourceType As ITypeSymbol, targetType As ITypeSymbol) As ExpressionSyntax - If targetType?.OriginalDefinition.SpecialType = SpecialType.System_Nullable_T Then Return source + 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 diff --git a/src/VisualBasic/CodeCracker/My Project/AssemblyInfo.vb b/src/VisualBasic/CodeCracker/My Project/AssemblyInfo.vb index 533dd3d92..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 - + - + diff --git a/src/VisualBasic/CodeCracker/Performance/StringBuilderInLoopAnalyzer.vb b/src/VisualBasic/CodeCracker/Performance/StringBuilderInLoopAnalyzer.vb index e00b20f13..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, @@ -71,4 +71,4 @@ Namespace Performance context.ReportDiagnostic(diag) End Sub End Class -End Namespace \ No newline at end of file +End Namespace 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/Style/TernaryOperatorAnalyzer.vb b/src/VisualBasic/CodeCracker/Style/TernaryOperatorAnalyzer.vb index 7b498b661..81602adea 100644 --- a/src/VisualBasic/CodeCracker/Style/TernaryOperatorAnalyzer.vb +++ b/src/VisualBasic/CodeCracker/Style/TernaryOperatorAnalyzer.vb @@ -67,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 @@ -76,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 @@ -95,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 6b24f9e72..9ef918ddf 100644 --- a/src/VisualBasic/CodeCracker/Style/TernaryOperatorCodeFixProviders.vb +++ b/src/VisualBasic/CodeCracker/Style/TernaryOperatorCodeFixProviders.vb @@ -49,8 +49,8 @@ Namespace Style EnsureNothingAsType(semanticModel, type, typeSyntax) Dim leadingTrivia = ifBlock.GetLeadingTrivia() - leadingTrivia = leadingTrivia.InsertRange(leadingTrivia.Count - 1, ifReturn.GetLeadingTrivia().Where(Function(trivia) trivia.IsKind(SyntaxKind.CommentTrivia))) - leadingTrivia = leadingTrivia.InsertRange(leadingTrivia.Count - 1, elseReturn.GetLeadingTrivia().Where(Function(trivia) trivia.IsKind(SyntaxKind.CommentTrivia))) + 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))). @@ -62,7 +62,8 @@ Namespace Style Dim returnStatement = SyntaxFactory.ReturnStatement(ternary). WithLeadingTrivia(leadingTrivia). - WithTrailingTrivia(trailingTrivia) + WithTrailingTrivia(trailingTrivia). + WithAdditionalAnnotations(Formatter.Annotation) Dim newRoot = root.ReplaceNode(ifBlock, returnStatement) Dim newDocument = document.WithSyntaxRoot(newRoot) @@ -111,6 +112,15 @@ Namespace Style 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()). @@ -122,13 +132,15 @@ Namespace Style 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.WithoutTrailingTrivia(), trueExpression.WithoutTrailingTrivia(), - falseExpression.WithoutTrailingTrivia()). - WithAdditionalAnnotations(Formatter.Annotation) + 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(ifAssign.Left.WithLeadingTrivia(leadingTrivia), ternary). + Dim assignment = SyntaxFactory.SimpleAssignmentStatement(ifAssign.Left.WithLeadingTrivia(leadingTrivia), ternaryOperatorToken, ternary). WithTrailingTrivia(trailingTrivia). WithAdditionalAnnotations(Formatter.Annotation) 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/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/RemovePrivateMethodNeverUsedAnalyzer.vb b/src/VisualBasic/CodeCracker/Usage/RemovePrivateMethodNeverUsedAnalyzer.vb index c273ef808..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,12 +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 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/UnusedParametersAnalyzer.vb b/src/VisualBasic/CodeCracker/Usage/UnusedParametersAnalyzer.vb index 5e338294a..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(), @@ -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 @@ -123,4 +123,4 @@ You should delete the parameter in such cases." Return context End Function End Class -End Namespace \ No newline at end of file +End Namespace diff --git a/src/VisualBasic/CodeCracker/packages.config b/src/VisualBasic/CodeCracker/packages.config deleted file mode 100644 index be0fe2547..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 6f9902b76..a2700956e 100644 --- a/test/CSharp/AnalyzeCecil.ps1 +++ b/test/CSharp/AnalyzeCecil.ps1 @@ -3,8 +3,8 @@ $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") -$analyzerCommonDll = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\src\CSharp\CodeCracker\bin\Debug\CodeCracker.Common.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:\p\cecil") { $gitPath = "c:\p\cecil" @@ -78,7 +78,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 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 0cac11358..4a21168c3 100644 --- a/test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj +++ b/test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj @@ -1,6 +1,5 @@  - - + Debug AnyCPU @@ -40,97 +39,29 @@ false - - ..\..\..\packages\FluentAssertions.4.1.0\lib\net45\FluentAssertions.dll - True - - - ..\..\..\packages\FluentAssertions.4.1.0\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.1.0\lib\dotnet\xunit.assert.dll - True - - - ..\..\..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll - True - - - ..\..\..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll - True - + + + + + + + + @@ -141,11 +72,14 @@ + + + @@ -162,7 +96,6 @@ - @@ -171,6 +104,7 @@ + @@ -207,9 +141,6 @@ - - Designer - @@ -217,7 +148,7 @@ CodeCracker.Common - {FF1097FB-A890-461B-979E-064697891B96} + {ff1097fb-a890-461b-979e-064697891b96} CodeCracker @@ -235,24 +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 7dd1728bc..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,7 +32,7 @@ public async Task Foo() } }"; - await VerifyCSharpHasNoDiagnosticsAsync(source); + await Verify.VerifyAnalyzerAsync(source); } [Fact] @@ -44,7 +45,7 @@ namespace ConsoleApplication1 { class TypeName { - public async Task Foo() + public async {|CS0246:Task|} {|CS0161:{|CS1983:Foo|}|}() { try { @@ -57,7 +58,7 @@ public async Task Foo() } } }"; - await VerifyCSharpHasNoDiagnosticsAsync(source); + await Verify.VerifyAnalyzerAsync(source); } [Fact] @@ -87,7 +88,7 @@ public void Foo() } } }"; - await VerifyCSharpHasNoDiagnosticsAsync(source); + await Verify.VerifyAnalyzerAsync(source); } [Fact] public async Task NotAllowedToReturnOutOfEmtpyCatchBlock() @@ -106,13 +107,13 @@ public void Foo() { // do something } - catch + [|catch { if (x == 1) throw; else return; - } + }|] } } }"; @@ -129,19 +130,19 @@ public void Foo() { try { - // do something - } - catch (Exception ex) - { - if (x == 1) - throw; - else - return; + // do something } + catch (Exception ex) + { + if (x == 1) + throw; + else + return; } } + } }"; - await VerifyCSharpFixAsync(test, fixtest, 0, allowNewCompilerDiagnostics: true); + await Verify.VerifyCodeFixAsync(test, fixtest); } [Fact] public async Task WhenFindCatchEmptyThenPutExceptionClass() @@ -159,10 +160,10 @@ public void Foo() { // do something } - catch + [|catch { int x = 0; - } + }|] } } }"; @@ -180,14 +181,14 @@ public void Foo() { // do something } - catch (Exception ex) - { - int x = 0; - } + catch (Exception ex) + { + int x = 0; } } + } }"; - await VerifyCSharpFixAsync(test, fixtest, 0, allowNewCompilerDiagnostics: true); + await Verify.VerifyCodeFixAsync(test, fixtest); } [Fact] public async Task AddCatchEvenIfThereIsReturnInBlock() @@ -205,11 +206,11 @@ public void Foo() { // do something } - catch + [|catch { int x = 0; return; - } + }|] } } }"; @@ -227,15 +228,15 @@ public void Foo() { // do something } - catch (Exception ex) - { - int x = 0; - return; - } + catch (Exception ex) + { + int x = 0; + return; } } + } }"; - await VerifyCSharpFixAsync(test, fixtest, 0, allowNewCompilerDiagnostics: true); + 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 706874d4a..000000000 --- a/test/CSharp/CodeCracker.Test/Design/CopyEventToVariableBeforeFireTests.cs +++ /dev/null @@ -1,769 +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 firing 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 firing 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 firing 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 WarningIfEventIsReadOnlyButNotAssignedInConstructor() - { - const string test = @" - public class MyClass - { - readonly int SomeOtherField; - readonly System.EventHandler MyEvent; - - public MyClass() - { - SomeOtherField = 42; - } - - 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 firing it.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 14, 25) } - }; - - 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 - { - Id = DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId(), - Message = "Copy the 'MyEvent' event to a variable before firing it.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 16, 25) } - }; - - 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 - { - Id = DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId(), - Message = "Copy the 'MyEvent' event to a variable before firing it.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 16, 25) } - }; - - 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 - { - Id = DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId(), - Message = "Copy the 'MyEvent' event to a variable before firing it.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 16, 25) } - }; - - 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 - { - Id = DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId(), - Message = "Copy the 'MyEvent' event to a variable before firing it.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 17, 25) } - }; - - 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 - { - Id = DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId(), - Message = "Copy the 'MyEvent' event to a variable before firing it.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 17, 25) } - }; - - 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 - { - Id = DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId(), - Message = "Copy the 'MyEvent' event to a variable before firing it.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 25) } - }; - - 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 - { - Id = DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId(), - Message = "Copy the 'MyEvent' event to a variable before firing it.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 13, 25) } - }; - - 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 - { - Id = DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId(), - Message = "Copy the 'MyEvent' event to a variable before firing it.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 14, 25) } - }; - - 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 - { - Id = DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId(), - Message = "Copy the 'MyEvent' event to a variable before firing it.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 17, 25) } - }; - - 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 - { - Id = DiagnosticId.CopyEventToVariableBeforeFire.ToDiagnosticId(), - Message = "Copy the 'MyEvent' event to a variable before firing it.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 17, 25) } - }; - - 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 NotWarningIfEventIsReadOnlyAndAssignedInBlockInConstructor() - { - const string test = @" - public class MyClass - { - readonly System.EventHandler MyEvent; - - public MyClass(bool shouldAssign) - { - { - MyEvent = (sender, args) => { }; - } - } - - public void Execute() - { - MyEvent(this, System.EventArgs.Empty); - } - }"; - - await VerifyCSharpHasNoDiagnosticsAsync(test); - } - - [Fact] - public async void NotWarningIfEventIsReadOnlyAndAssignedInIfAndElseInConstructor() - { - const string test = @" - public class MyClass - { - readonly System.EventHandler MyEvent; - - public MyClass(bool shouldAssign) - { - if(shouldAssign) - { - MyEvent = (sender, args) => { }; - } - else - { - MyEvent = (sender, args) => { }; - } - } - - public void Execute() - { - MyEvent(this, System.EventArgs.Empty); - } - }"; - - await VerifyCSharpHasNoDiagnosticsAsync(test); - } - - [Fact] - public async void NotWarningIfEventIsReadOnlyAndAssignedInAllSwitchCasesInConstructor() - { - 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; - default: MyEvent = (sender, args) => { }; break; - } - } - - public void Execute() - { - MyEvent(this, System.EventArgs.Empty); - } - }"; - - await VerifyCSharpHasNoDiagnosticsAsync(test); - } - - [Fact] - public async void WarningIfEventIsReadOnlyAndReturnAfterAssignmentInConstructor() - { - const string test = @" - public class MyClass - { - readonly System.EventHandler MyEvent; - - public MyClass(bool returnEarly) - { - MyEvent = (sender, args) => { }; - if(returnEarly) - { - return; - } - } - - public void Execute() - { - MyEvent(this, System.EventArgs.Empty); - } - }"; - - await VerifyCSharpHasNoDiagnosticsAsync(test); - } - - [Fact] - public async void NotWarningIfEventIsReadOnlyAndAssignedOnFieldDeclaration() - { - const string test = @" - public class MyClass - { - readonly System.EventHandler MyEvent = (sender, args) => { }; - - public void Execute() - { - MyEvent(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 0ecab7be1..87ea33784 100644 --- a/test/CSharp/CodeCracker.Test/Design/EmptyCatchBlockTests.cs +++ b/test/CSharp/CodeCracker.Test/Design/EmptyCatchBlockTests.cs @@ -4,7 +4,9 @@ namespace CodeCracker.Test.CSharp.Design { - public class EmptyCatchBlockTests : CodeFixVerifier + using Verify = CSharpCodeFixVerifier; + + public class EmptyCatchBlockTests { readonly string test = @" using System; @@ -20,9 +22,9 @@ public async Task Foo() { // do something } - catch + [|catch { - } + }|] } } }"; @@ -36,7 +38,7 @@ namespace ConsoleApplication1 { class TypeName { - public async Task Foo() + public async {|CS0246:Task|} {|CS0161:{|CS1983:Foo|}|}() { try { @@ -49,7 +51,7 @@ public async Task Foo() } } }"; - await VerifyCSharpHasNoDiagnosticsAsync(test); + await Verify.VerifyAnalyzerAsync(test); } [Fact] @@ -72,7 +74,7 @@ public async Task Foo() } } }"; - await VerifyCSharpFixAsync(test, fixtest, 0, allowNewCompilerDiagnostics: false, formatBeforeCompare: true); + await Verify.VerifyCodeFixAsync(test, fixtest); } [Fact] @@ -88,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, allowNewCompilerDiagnostics: false, formatBeforeCompare: true); + await new Verify.Test + { + TestCode = test, + FixedCode = fixtest, + CodeFixIndex = 1, + }.RunAsync(); } [Fact] @@ -116,14 +123,19 @@ public async Task Foo() { // do something } - catch (Exception ex) - { - throw; - } + catch (Exception ex) + { + throw; } } + } }"; - await VerifyCSharpFixAsync(test, fixtest, 2, allowNewCompilerDiagnostics: true, formatBeforeCompare: true); + await new Verify.Test + { + TestCode = test, + FixedCode = fixtest, + CodeFixIndex = 2, + }.RunAsync(); } @@ -137,16 +149,16 @@ namespace ConsoleApplication1 { class TypeName { - public async Task Foo() + public async {|CS0246:Task|} {|CS0161:{|CS1983:Foo|}|}() { int x; try { // do something } - catch (System.ArgumentException ae) + [|catch (System.ArgumentException ae) { - } + }|] catch (System.Exception ex) { x = 1; @@ -162,7 +174,7 @@ namespace ConsoleApplication1 { class TypeName { - public async Task Foo() + public async {|CS0246:Task|} {|CS0161:{|CS1983:Foo|}|}() { int x; try @@ -176,7 +188,7 @@ public async Task Foo() } } }"; - await VerifyCSharpFixAsync(test, fixtest, 0); + 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 cc980214d..eb64ea7dc 100644 --- a/test/CSharp/CodeCracker.Test/Design/MakeMethodStaticTests.cs +++ b/test/CSharp/CodeCracker.Test/Design/MakeMethodStaticTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Design; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -100,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); } @@ -1014,14 +1011,62 @@ public void M() } public static void N() { } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.MakeMethodStatic.ToDiagnosticId(), - Message = string.Format(MakeMethodStaticAnalyzer.MessageFormat, "M"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 17) } - }; + 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 982738bef..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; @@ -728,13 +729,9 @@ void Foo(TypeName d) private static DiagnosticResult CreateNameofDiagnosticResult(string nameofArgument, int diagnosticLine, int diagnosticColumn, DiagnosticId id = DiagnosticId.NameOf) { - return new DiagnosticResult - { - Id = id.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 db9e72c7a..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,13 +21,9 @@ public void Execute() } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), - Message = string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat.ToString(), "MyEvent"), - 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); } @@ -45,17 +42,317 @@ public void Execute() } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), - Message = string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat.ToString(), "MyEvent"), - 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 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() { @@ -66,13 +363,9 @@ public class MyClass public void Execute() => MyEvent(this, System.EventArgs.Empty); }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), - Message = string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat.ToString(), "MyEvent"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(6, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -116,13 +409,9 @@ public void Execute() } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), - Message = string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat.ToString(), "MyEvent"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 13, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(13, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -148,13 +437,9 @@ public void Execute() } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), - Message = string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat.ToString(), "MyEvent"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 15, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(15, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "MyEvent")); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -204,13 +489,9 @@ public static void Execute(System.Action action) action(); } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), - Message = string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat.ToString(), "action"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(10, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "action")); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -227,13 +508,9 @@ public static void Execute(System.Action action) action(); } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), - Message = string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat.ToString(), "action"), - 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, "action")); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -249,13 +526,9 @@ public static void Execute(System.Action action) if (action == null) throw new Exception(); } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), - Message = string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat.ToString(), "action"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 25) } - }; + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(6, 25) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "action")); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -339,13 +612,9 @@ public static void Execute(System.Action action) if (null == action) action(); }".WrapInCSharpClass(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), - Message = string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat.ToString(), "action"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 12, 9) } - }; + var expected = new DiagnosticResult(DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(12, 9) + .WithMessage(string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat, "action")); await VerifyCSharpDiagnosticAsync(test, expected); } @@ -455,26 +724,356 @@ public static TReturn Method(System.Func getter) where T { return getter(default(T)); }".WrapInCSharpClass(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.UseInvokeMethodToFireEvent.ToDiagnosticId(), - Message = string.Format(UseInvokeMethodToFireEventAnalyzer.MessageFormat.ToString(), "getter"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 11, 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 83ca47b91..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() @@ -110,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); } @@ -141,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); } @@ -192,7 +185,7 @@ public int Foo(int value) } } - public class XmlDocumentationRemoveUnexistentParametersCodeFixTests : CodeFixVerifier + public class XmlDocumentationRemoveUnexistentParametersCodeFixTests : CodeFixVerifier { [Fact] public async Task FixRemoveParameterDoc() @@ -278,7 +271,7 @@ protected async static Task GetSortedDiagnosticsFromDocumentsAsync } - public class XmlDocumentationCreateMissingParametersCodeFixTests : CodeFixVerifier + public class XmlDocumentationCreateMissingParametersCodeFixTests : CodeFixVerifier { [Fact] public async Task FixCreateOneParameterDoc() 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 c31e7ebac..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); } @@ -388,13 +364,9 @@ 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); } @@ -444,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); } @@ -484,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); } @@ -538,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); } @@ -571,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); } @@ -631,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 969563a93..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); } @@ -119,5 +116,56 @@ public async Task Foo() }"; 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 0440a3f4b..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.13")] +[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 5413edb7e..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 i == 1); }} }}"; - var expected = new DiagnosticResult - { - Id = diagnosticId.ToDiagnosticId(), - Message = diagnosticId == DiagnosticId.ChangeAnyToAll ? ChangeAnyToAllAnalyzer.MessageAny : ChangeAnyToAllAnalyzer.MessageAll, - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 13, 43) } - }; + var expected = new DiagnosticResult(diagnosticId.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(13, 43) + .WithMessage(diagnosticId == DiagnosticId.ChangeAnyToAll ? ChangeAnyToAllAnalyzer.MessageAny : ChangeAnyToAllAnalyzer.MessageAll); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -246,13 +239,9 @@ class TypeName private System.Collections.Generic.IList xs; bool Foo() => xs.{methodName}(i => i == 1); }}"; - var expected = new DiagnosticResult - { - Id = diagnosticId.ToDiagnosticId(), - Message = diagnosticId == DiagnosticId.ChangeAnyToAll ? ChangeAnyToAllAnalyzer.MessageAny : ChangeAnyToAllAnalyzer.MessageAll, - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 22) } - }; + var expected = new DiagnosticResult(diagnosticId.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(7, 22) + .WithMessage(diagnosticId == DiagnosticId.ChangeAnyToAll ? ChangeAnyToAllAnalyzer.MessageAny : ChangeAnyToAllAnalyzer.MessageAll); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -280,13 +269,9 @@ public async Task NegationWithCoalesceExpressionCreatesDiagnostic(string methodN var source = $@" var ints = new [] {{ 1 }}; var query = !ints?.{methodName}(i => i == 1) ?? true;"; - var expected = new DiagnosticResult - { - Id = diagnosticId.ToDiagnosticId(), - Message = diagnosticId == DiagnosticId.ChangeAnyToAll ? ChangeAnyToAllAnalyzer.MessageAny : ChangeAnyToAllAnalyzer.MessageAll, - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 13, 20) } - }; + 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); } 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 fe1b72bf7..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() { 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/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 d78489fbc..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; @@ -130,13 +131,9 @@ void Foo() dynamic Fee() { return 42; } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.AlwaysUseVar.ToDiagnosticId(), - Message = "Use 'var' instead of specifying the type name.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 9, 17) } - }; + 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); } @@ -158,13 +155,9 @@ public async Task Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.AlwaysUseVarOnPrimitives.ToDiagnosticId(), - Message = "Use 'var' instead of specifying the type name.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + 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); } @@ -187,13 +180,9 @@ public async Task Foo() } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.AlwaysUseVar.ToDiagnosticId(), - Message = "Use 'var' instead of specifying the type name.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + 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); } 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 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 98f66c406..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,17 @@ 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() { @@ -130,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); } @@ -183,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() { @@ -229,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); } @@ -465,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); } diff --git a/test/CSharp/CodeCracker.Test/Style/PropertyPrivateSetTests.cs b/test/CSharp/CodeCracker.Test/Style/PropertyPrivateSetTests.cs index 5e16e1a3c..29f79dda9 100644 --- a/test/CSharp/CodeCracker.Test/Style/PropertyPrivateSetTests.cs +++ b/test/CSharp/CodeCracker.Test/Style/PropertyPrivateSetTests.cs @@ -1,5 +1,6 @@ using CodeCracker.CSharp.Style; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using System.Threading.Tasks; using Xunit; @@ -52,13 +53,9 @@ class TypeName public int MyProperty { get; set; } } }"; - var expected = new DiagnosticResult - { - Id = DiagnosticId.PropertyPrivateSet.ToDiagnosticId(), - Message = PropertyPrivateSetAnalyzer.MessageFormat, - Severity = DiagnosticSeverity.Hidden, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 13) } - }; + var expected = new DiagnosticResult(DiagnosticId.PropertyPrivateSet.ToDiagnosticId(), DiagnosticSeverity.Hidden) + .WithLocation(8, 13) + .WithMessage(PropertyPrivateSetAnalyzer.MessageFormat); await VerifyCSharpDiagnosticAsync(test, expected); } 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 b1e31397d..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); } @@ -636,5 +625,27 @@ public async Task FixWithLineBreaksAndStringConcat() 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 da9886e5b..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); } @@ -873,5 +870,68 @@ static void Baz() }"; 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 73ca6e47c..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; @@ -288,13 +289,9 @@ public int Foo() } } }"; - 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) } - }; + var expected = new DiagnosticResult(DiagnosticId.TernaryOperator_Assignment.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(10, 17) + .WithMessage("You can use a ternary operator."); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -334,6 +331,85 @@ public static void Foo() "; 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 @@ -406,6 +482,118 @@ class TypeName 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] public async Task WhenUsingIfAndElseWithDirectReturnChangeToTernaryFixAll() { @@ -567,13 +755,9 @@ public int Foo() } } }"; - 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) } - }; + var expected = new DiagnosticResult(DiagnosticId.TernaryOperator_Return.ToDiagnosticId(), DiagnosticSeverity.Info) + .WithLocation(9, 17) + .WithMessage("You can use a ternary operator."); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -611,5 +795,581 @@ public static Base Foo() "; 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 d3c7c0dc0..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,13 +44,9 @@ 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); } @@ -91,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); } @@ -193,20 +186,12 @@ public async Task FixAllInSolutionChangeMethodToStringEmpty() 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 d951964ae..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,6 +10,25 @@ 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() { @@ -28,7 +49,7 @@ public void Bar() } [Fact] - public async Task WhenCallExtensionMethodAsStaticMenthodTriggerAFix() + public async Task WhenCallExtensionMethodAsStaticMethodTriggerAFix() { const string source = @" using System.Linq; @@ -44,17 +65,56 @@ 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() { @@ -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); } @@ -287,13 +343,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", 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); } @@ -312,13 +364,9 @@ public class Foo } }"; - 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", 8, 51) } - }; + 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 b66e1f327..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; @@ -45,6 +46,29 @@ private void Dispose(bool disposing) 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() { @@ -62,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); } @@ -108,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); } @@ -146,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); } @@ -176,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); } @@ -210,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() { @@ -250,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); } @@ -281,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); } @@ -313,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); } @@ -345,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); } @@ -371,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); } @@ -398,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); } @@ -772,6 +776,21 @@ class TypeName : System.IDisposable 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() { } }"; diff --git a/test/CSharp/CodeCracker.Test/Usage/DisposableVariableNotDisposedTests.cs b/test/CSharp/CodeCracker.Test/Usage/DisposableVariableNotDisposedTests.cs index 60a80b3d0..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; @@ -72,6 +73,16 @@ public async Task FixADisposableDeclarationWithoutDisposeWithParenthese() 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() { @@ -113,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); } @@ -138,13 +175,9 @@ public async Task IgnoresDisposableObjectsCreatedWithUsingStatement() 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); } @@ -296,13 +329,9 @@ static void Foo() void Register(System.Action f) { } } "; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), - Message = string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 34) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(7, 34) + .WithMessage(string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -322,13 +351,9 @@ static void Foo() void Register(System.Action f) { } } "; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), - Message = string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 32) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(8, 32) + .WithMessage(string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -348,13 +373,9 @@ static void Foo() void Register(System.Action f) { } } "; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), - Message = string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 32) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(8, 32) + .WithMessage(string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -374,13 +395,9 @@ static void Foo() void Register(System.Action f) { } } "; - var expected = new DiagnosticResult - { - Id = DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), - Message = string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream"), - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 32) } - }; + var expected = new DiagnosticResult(DiagnosticId.DisposableVariableNotDisposed.ToDiagnosticId(), DiagnosticSeverity.Warning) + .WithLocation(8, 32) + .WithMessage(string.Format(DisposableVariableNotDisposedAnalyzer.MessageFormat, "MemoryStream")); await VerifyCSharpDiagnosticAsync(source, expected); } @@ -428,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); } @@ -443,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); } @@ -471,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); } @@ -523,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); } @@ -595,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); } @@ -609,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(); @@ -682,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); } @@ -932,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) @@ -940,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(); @@ -954,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; @@ -962,7 +963,7 @@ public async Task FixAssignmentInsideBlockWithDifferentScopeInDeclarationAndAssi { using (m = new System.IO.MemoryStream()) { - Console.WriteLine(m); + m.Flush(); } } Console.WriteLine(1);".WrapInCSharpMethod(); @@ -976,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(); @@ -984,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); @@ -1005,7 +1006,7 @@ void Foo() if (DateTime.Now.Second % 2 == 0) { m = new Disposable(); - Console.WriteLine(m); + m.Push(); } m.Flush(); Console.WriteLine(1); @@ -1015,6 +1016,7 @@ class Disposable : IDisposable { void IDisposable.Dispose() { } public void Flush() { } + public void Push() { } } "; const string fixtest = @" @@ -1027,7 +1029,7 @@ void Foo() if (DateTime.Now.Second % 2 == 0) { m = new Disposable(); - Console.WriteLine(m); + m.Push(); } m.Flush(); Console.WriteLine(1); @@ -1038,6 +1040,7 @@ class Disposable : IDisposable { void IDisposable.Dispose() { } public void Flush() { } + public void Push() { } } "; await VerifyCSharpFixAsync(source, fixtest); @@ -1147,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) @@ -1160,6 +1163,7 @@ class Disposable : IDisposable { void IDisposable.Dispose() { } public void Flush() { } + public void Push() { } } "; const string fixtest = @" @@ -1172,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) @@ -1185,6 +1189,7 @@ class Disposable : IDisposable { void IDisposable.Dispose() { } public void Flush() { } + public void Push() { } } "; await VerifyCSharpFixAsync(source, fixtest); @@ -1205,7 +1210,7 @@ void Foo() if (DateTime.Now.Second % 4 == 0) { m = new Disposable(); - Console.WriteLine(m); + m.Push(); } m.Flush(); } @@ -1219,6 +1224,7 @@ class Disposable : IDisposable { void IDisposable.Dispose() { } public void Flush() { } + public void Push() { } } "; const string fixtest = @" @@ -1233,7 +1239,7 @@ void Foo() if (DateTime.Now.Second % 4 == 0) { m = new Disposable(); - Console.WriteLine(m); + m.Push(); } m.Flush(); } @@ -1247,6 +1253,7 @@ class Disposable : IDisposable { void IDisposable.Dispose() { } public void Flush() { } + public void Push() { } } "; await VerifyCSharpFixAsync(source, fixtest); @@ -1493,5 +1500,65 @@ static void Baz() }"; 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 e90b46906..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() @@ -47,17 +58,73 @@ 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() { @@ -193,13 +260,9 @@ public void Dispose() ~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); } diff --git a/test/CSharp/CodeCracker.Test/Usage/IPAddressAnalyzerTests.cs b/test/CSharp/CodeCracker.Test/Usage/IPAddressAnalyzerTests.cs index ccf94d7e4..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 @@ -78,12 +79,9 @@ public async Task IfAbbreviateParseIdentifierFoundAndParameterIsNotStringLiteral 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)} - }; + 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 ec7e2aaf8..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; @@ -19,6 +20,32 @@ class TypeName 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() { @@ -156,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); } @@ -177,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); } @@ -199,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); } @@ -233,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 }); } @@ -278,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); } @@ -299,13 +290,9 @@ 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); } @@ -375,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); } @@ -400,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); } @@ -465,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); } @@ -721,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); } @@ -742,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); } @@ -804,10 +771,10 @@ public void Foo() await VerifyCSharpHasNoDiagnosticsAsync(source1, source2); } - [Fact] - public async Task FieldsAssignedOnLambdaDoesNotCreateDiagnostic() - { - const string source = @" + [Fact] + public async Task FieldsAssignedOnLambdaDoesNotCreateDiagnostic() + { + const string source = @" namespace ConsoleApplication1 { internal class Test @@ -820,7 +787,191 @@ public Test() } } }"; - await VerifyCSharpHasNoDiagnosticsAsync(source); + 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 c866a17b0..ee42e29a9 100644 --- a/test/CSharp/CodeCracker.Test/Usage/RemovePrivateMethodNeverUsedAnalyzerTest.cs +++ b/test/CSharp/CodeCracker.Test/Usage/RemovePrivateMethodNeverUsedAnalyzerTest.cs @@ -1,4 +1,6 @@ using CodeCracker.CSharp.Usage; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using Xunit; namespace CodeCracker.Test.CSharp.Usage @@ -9,6 +11,7 @@ public class RemovePrivateMethodNeverUsedAnalyzerTest : CodeFixVerifier.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 5ceae6e28..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; @@ -27,13 +28,9 @@ public void 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); } @@ -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 714e2bc3c..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,35 +10,31 @@ 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); } diff --git a/test/CSharp/CodeCracker.Test/Usage/StringFormatArgsTests.cs b/test/CSharp/CodeCracker.Test/Usage/StringFormatArgsTests.cs index 5b6cf0257..ebd90f4c7 100644 --- a/test/CSharp/CodeCracker.Test/Usage/StringFormatArgsTests.cs +++ b/test/CSharp/CodeCracker.Test/Usage/StringFormatArgsTests.cs @@ -3,6 +3,7 @@ using Xunit; using Microsoft.CodeAnalysis.Diagnostics; using CodeCracker.CSharp.Usage; +using Microsoft.CodeAnalysis.Testing; namespace CodeCracker.Test.CSharp.Usage { @@ -82,13 +83,9 @@ public async Task IgnoresMethodsCalledWithIncorrectParameterTypes() public async Task NoParametersCreatesError() { var source = @"var result = string.Format(""{0}"");".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormatArgs_InvalidArgs.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); } @@ -96,13 +93,9 @@ public async Task NoParametersCreatesError() public async Task LessParametersCreatesError() { var source = @"var result = string.Format(""one {0} two {1}"", ""a"");".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormatArgs_InvalidArgs.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); } @@ -110,13 +103,9 @@ public async Task LessParametersCreatesError() 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_ExtraArgs.ToDiagnosticId(), - Message = StringFormatArgsAnalyzer.IncorrectNumberOfArgsMessage, - Severity = DiagnosticSeverity.Warning, - 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); } @@ -145,13 +134,9 @@ public async Task MethodWithParamtersReferencingSingleAndFormatSpecifiersArgumen public async Task TwoParametersReferencingSamePlaceholderCreatesWarning() { var source = @"var result = string.Format(""one {0} two {0}"", ""a"", ""b"");".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormatArgs_ExtraArgs.ToDiagnosticId(), - Message = StringFormatArgsAnalyzer.IncorrectNumberOfArgsMessage, - Severity = DiagnosticSeverity.Warning, - 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); } @@ -183,13 +168,9 @@ public async Task VerbatimStringWithMissingArgCreatesError() var noun = ""Giovanni""; var s = string.Format(@""This {0} is """"{1}""""."", noun);".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormatArgs_InvalidArgs.ToDiagnosticId(), - Message = StringFormatArgsAnalyzer.InvalidArgsReferenceMessage, - 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); } @@ -197,13 +178,9 @@ public async Task VerbatimStringWithMissingArgCreatesError() public async Task InvalidArgumentReferenceCreatesError() { var source = @"var result = string.Format(""one {1}"", ""a"");".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormatArgs_InvalidArgs.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); } @@ -211,13 +188,9 @@ public async Task InvalidArgumentReferenceCreatesError() public async Task NonIntegerPlaceholderCreatesError() { var source = @"var result = string.Format(""one {notZero}"", ""a"");".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormatArgs_InvalidArgs.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); } @@ -225,13 +198,9 @@ public async Task NonIntegerPlaceholderCreatesError() public async Task UnusedArgsCreatesWarning() { var source = @"string.Format(""{0}{1}{3}{5}"", ""a"", ""b"", ""c"", ""d"", ""e"", ""f"");".WrapInCSharpMethod(); - var expected = new DiagnosticResult - { - Id = DiagnosticId.StringFormatArgs_ExtraArgs.ToDiagnosticId(), - Message = StringFormatArgsAnalyzer.IncorrectNumberOfArgsMessage, - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 17) } - }; + 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 8402298ac..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; @@ -58,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() { @@ -297,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] @@ -860,5 +890,24 @@ public class TypeName }"; 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 953d54e58..000000000 --- a/test/CSharp/CodeCracker.Test/packages.config +++ /dev/null @@ -1,21 +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/CodeCracker.Test.Common.csproj b/test/Common/CodeCracker.Test.Common/CodeCracker.Test.Common.csproj index 5ca8c8ccc..ab8625db9 100644 --- a/test/Common/CodeCracker.Test.Common/CodeCracker.Test.Common.csproj +++ b/test/Common/CodeCracker.Test.Common/CodeCracker.Test.Common.csproj @@ -1,5 +1,5 @@  - + Debug @@ -35,131 +35,36 @@ 4 - - ..\..\..\packages\FluentAssertions.4.1.0\lib\net45\FluentAssertions.dll - True - - - ..\..\..\packages\FluentAssertions.4.1.0\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.1.0\lib\dotnet\xunit.assert.dll - True - - - ..\..\..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll - True - - - ..\..\..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.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 81a7cfd79..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 diff --git a/test/Common/CodeCracker.Test.Common/Helpers/Extensions.cs b/test/Common/CodeCracker.Test.Common/Helpers/Extensions.cs index 514ab1e6b..7b6e955ab 100644 --- a/test/Common/CodeCracker.Test.Common/Helpers/Extensions.cs +++ b/test/Common/CodeCracker.Test.Common/Helpers/Extensions.cs @@ -4,34 +4,58 @@ public static class Extensions { public static string WrapInCSharpClass(this string code, 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} - {{ - {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 9b9d5375b..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.13")] +[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 92a423897..5603da4c6 100644 --- a/test/Common/CodeCracker.Test.Common/Verifiers/CodeFixVerifier.cs +++ b/test/Common/CodeCracker.Test.Common/Verifiers/CodeFixVerifier.cs @@ -323,6 +323,45 @@ private async static Task VerifyFixAllAsync(string language, DiagnosticAnalyzer } } + /// + /// 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}."); + } + } + /// /// Called to test a C# codefix when it should not had been registered /// diff --git a/test/Common/CodeCracker.Test.Common/Verifiers/DiagnosticVerifier.cs b/test/Common/CodeCracker.Test.Common/Verifiers/DiagnosticVerifier.cs index 6163123c2..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; @@ -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 f1bc5d8d5..000000000 --- a/test/Common/CodeCracker.Test.Common/packages.config +++ /dev/null @@ -1,26 +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 9648c8dbb..7f18764a2 100644 --- a/test/VisualBasic/CodeCracker.Test/CodeCracker.Test.vbproj +++ b/test/VisualBasic/CodeCracker.Test/CodeCracker.Test.vbproj @@ -1,6 +1,5 @@  - - + Debug @@ -54,86 +53,16 @@ On - - ..\..\..\packages\FluentAssertions.4.1.0\lib\net45\FluentAssertions.dll - True - - - ..\..\..\packages\FluentAssertions.4.1.0\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.1.0\lib\dotnet\xunit.assert.dll - True - - - ..\..\..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll - True - - - ..\..\..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll - True - + + + + + + @@ -184,7 +113,7 @@ - + @@ -207,9 +136,6 @@ My Settings.Designer.vb - - Designer - @@ -228,24 +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/NameOfTests.vb b/test/VisualBasic/CodeCracker.Test/Design/NameOfTests.vb index 641b65c8e..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 @@ -591,13 +592,9 @@ End Class" End Function Private Function CreateNameofDiagnosticResult(nameofArgument As String, diagnosticLine As Integer, diagnosticColumn As Integer, Optional id As DiagnosticId = DiagnosticId.NameOf) As DiagnosticResult - Return New DiagnosticResult With - { - .Id = id.ToDiagnosticId(), - .Message = $"Use 'NameOf({nameofArgument})' instead of specifying the program element name.", - .Severity = Microsoft.CodeAnalysis.DiagnosticSeverity.Warning, - .Locations = {New DiagnosticResultLocation("Test0.vb", diagnosticLine, diagnosticColumn)} - } + 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 cb4bb031a..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 02db187a8..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 @@ -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 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 ef72d758e..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,12 +120,12 @@ Namespace ConsoleApplication1 End Sub End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function - - Public Async Function WhenUsingIfElseIfElseDoesNotCreate() As Task - Const sourceWithMultipleStatements = " + + Public Async Function WhenUsingIfElseIfElseDoesNotCreate() As Task + Const sourceWithMultipleStatements = " Namespace ConsoleApplication1 Class TypeName Public Sub Foo() @@ -137,21 +140,18 @@ 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 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 @@ -167,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 @@ -180,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 @@ -196,12 +196,12 @@ 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 = " + + Public Async Function WhenTernaryWithObjectDoesApplyFix() As Task + Const source = " Class MyCustomer Public Property Value As String End Class @@ -221,7 +221,7 @@ Class Tester End Sub End Class" - Const fix = " + Const fix = " Class MyCustomer Public Property Value As String End Class @@ -237,13 +237,13 @@ Class Tester End Sub End Class" - Await VerifyBasicFixAsync(source, fix) + Await VerifyBasicFixAsync(source, fix) - End Function + End Function - - Public Async Function WhenUsingIfAndElseWithNullableValueTypeAssignmentChangeToTernaryFix() As Task - Const source = " + + Public Async Function WhenUsingIfAndElseWithNullableValueTypeAssignmentChangeToTernaryFix() As Task + Const source = " Public Class MyType Public Sub Foo() Dim a As Integer? @@ -255,7 +255,7 @@ Public Class MyType End Sub End Class" - Const fix = " + Const fix = " Public Class MyType Public Sub Foo() Dim a As Integer? @@ -263,13 +263,13 @@ Public Class MyType 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 + ' 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 Async Function FixConsidersBaseTypeAssignent() As Task + Const source = " Public Class Base End Class Public Class B @@ -286,7 +286,7 @@ Public Class MyType End Sub End Class" - Const fix = " + Const fix = " Public Class Base End Class Public Class B @@ -299,38 +299,42 @@ Public Class MyType 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 = " + ' 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 = " + 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 + ' 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 Async Function WhenUsingIfAndElseWithNullableValueTypeAssignmentChangeToTernaryFixAll() As Task + Const source = " Public Class MyType Public Sub Foo() Dim a As Integer? @@ -342,7 +346,7 @@ Public Class MyType End Sub End Class" - Const fix = " + Const fix = " Public Class MyType Public Sub Foo() Dim a As Integer? @@ -350,39 +354,35 @@ Public 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 WhenUsingConcatenationAssignmentExpandsToConcatenateAtEndOfTernary() As Task - Const source = " + + Public Async Function WhenUsingConcatenationAssignmentExpandsToConcatenateAtEndOfTernary() As Task + Const source = " Public Class MyType Public Sub Foo() Dim x = ""test"" If True Then - x = ""1"" + x = ""1"" Else x &= ""2"" End If End Sub End Class" - - Const fix = " + 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 - ' 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 WhenUsingAddAssiginmentExpandsOperationProperly() As Task - Const source = " + + Public Async Function WhenUsingAddAssiginmentExpandsOperationProperly() As Task + Const source = " Public Class MyType Public Sub Foo() Dim x = 0 @@ -393,22 +393,19 @@ Public Class MyType End If End Sub End Class" - - Const fix = " + 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 - ' 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 WhenUsingSubtractAssiginmentExpandsOperationProperly() As Task - Const source = " + + Public Async Function WhenUsingSubtractAssiginmentExpandsOperationProperly() As Task + Const source = " Public Class MyType Public Sub Foo() Dim x = 0 @@ -419,29 +416,191 @@ Public Class MyType End If End Sub End Class" - - Const fix = " + 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 - ' 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 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 -End Class + + 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 Class TernaryOperatorWithReturnTests - Inherits CodeFixVerifier(Of TernaryOperatorAnalyzer, TernaryOperatorWithReturnCodeFixProvider) + + 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 WhenUsingIfWithoutElseAnalyzerDoesNotCreateDiagnostic() As Task - Const sourceWithoutElse = " + + 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" + + 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 @@ -452,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 @@ -471,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 @@ -490,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 @@ -508,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 @@ -526,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 @@ -545,13 +704,13 @@ Namespace ConsoleApplication1 End Function End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function - - Public Async Function WhenUsingIfAndElseWithNullableValueTypeDirectReturnChangeToTernaryFix() As Task - Const source = " + + Public Async Function WhenUsingIfAndElseWithNullableValueTypeDirectReturnChangeToTernaryFix() As Task + Const source = " Public Class MyType Public Function Foo() As Integer? If True Then @@ -562,20 +721,20 @@ Public Class MyType End Function End Class" - Const fix = " + 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 + ' 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 = " + + Public Async Function WhenUsingIfElseIfElseDoesNotCreate() As Task + Const sourceWithMultipleStatements = " Namespace ConsoleApplication1 Class TypeName Public Sub Foo() @@ -589,13 +748,13 @@ Namespace ConsoleApplication1 End Sub End Class End Namespace" - Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) - End Function + Await VerifyBasicHasNoDiagnosticsAsync(sourceWithMultipleStatements) + End Function - - Public Async Function WhenUsingIfAndElseWithNullableValueTypeDirectReturnChangeToTernaryFixAll() As Task - Const source = " + + Public Async Function WhenUsingIfAndElseWithNullableValueTypeDirectReturnChangeToTernaryFixAll() As Task + Const source = " Public Class MyType Public Function Foo() As Integer? If True Then @@ -606,17 +765,17 @@ Public Class MyType End Function End Class" - Const fix = " + 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 + Await VerifyBasicFixAllAsync(New String() {source, source.Replace("MyType", "MyType1")}, New String() {fix, fix.Replace("MyType", "MyType1")}) + End Function - Private Const sourceReturn = " + Private Const sourceReturn = " Namespace ConsoleApplication1 Class MyType Public Function Foo() As Integer @@ -630,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 @@ -653,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 @@ -668,17 +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 + Await VerifyBasicFixAllAsync(New String() {sourceReturn, sourceReturn.Replace("MyType", "MyType1")}, New String() {fix, fix.Replace("MyType", "MyType1")}) + End Function -End Class + + 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 Class TernaryOperatorFromIifTests - Inherits CodeFixVerifier(Of TernaryOperatorAnalyzer, TernaryOperatorFromIifCodeFixProvider) + 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 WhenUsingIifAndSimpleAssignmentCreatesFix() As Task - Const source = " + 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 Async Function WhenUsingIifAndSimpleAssignmentCreatesFix() As Task + Const source = " Class TypeName Public Sub Foo() Dim x = 1 @@ -686,7 +950,7 @@ Class TypeName End Sub End Class" - Const fix = " + Const fix = " Class TypeName Public Sub Foo() Dim x = 1 @@ -694,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 @@ -707,7 +971,7 @@ Class MyType End Sub End Class" - Const fix = " + Const fix = " Class MyType Public Sub Foo() Dim x = 1 @@ -715,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 @@ -728,7 +992,7 @@ Class MyType End Function End Class" - Const fix = " + Const fix = " Class MyType Public Function Foo() As Integer Dim x = 1 @@ -736,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 @@ -757,7 +1018,7 @@ Class MyType End Function End Class" - Const fix = " + Const fix = " Class MyType Public Function Foo() As Integer Dim x = 1 @@ -765,46 +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 - - - -End Class + Await VerifyBasicHasNoDiagnosticsAsync(source) + End Function + 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/UnusedParametersTests.vb b/test/VisualBasic/CodeCracker.Test/Usage/UnusedParametersTests.vb index f8e0e5bc9..1031f3a59 100644 --- a/test/VisualBasic/CodeCracker.Test/Usage/UnusedParametersTests.vb +++ b/test/VisualBasic/CodeCracker.Test/Usage/UnusedParametersTests.vb @@ -1,4 +1,5 @@ Imports CodeCracker.VisualBasic.Usage +Imports Microsoft.CodeAnalysis.Testing Imports Xunit Namespace Usage @@ -464,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 = " @@ -691,12 +705,9 @@ End Class" 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)} - } + 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 2739aafad..000000000 --- a/test/VisualBasic/CodeCracker.Test/packages.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file