diff --git a/README.md b/README.md index 40d269c935b..33bdb1ce666 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ languages in [SonarQube](http://www.sonarqube.org/), [SonarCloud](https://sonarc ## Features -* [370+ C# rules](https://rules.sonarsource.com/csharp) and [160+ VB.​NET rules](https://rules.sonarsource.com/vbnet) +* [370+ C# rules](https://rules.sonarsource.com/csharp) and [170+ VB.​NET rules](https://rules.sonarsource.com/vbnet) * Metrics (cognitive complexity, duplications, number of lines etc.) * Import of [test coverage reports](https://community.sonarsource.com/t/9871) from Visual Studio Code Coverage, dotCover, OpenCover, Coverlet, Altcover. * Import of third party Roslyn Analyzers results diff --git a/analyzers/its/expected/Ember-MM/Ember Media Manager-{9B57D3AB-AF12-4012-B945-284C2448DC81}-S3898.json b/analyzers/its/expected/Ember-MM/Ember Media Manager-{9B57D3AB-AF12-4012-B945-284C2448DC81}-S3898.json new file mode 100644 index 00000000000..cab156df15e --- /dev/null +++ b/analyzers/its/expected/Ember-MM/Ember Media Manager-{9B57D3AB-AF12-4012-B945-284C2448DC81}-S3898.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Ember%20Media%20Manager\frmMain.vb", +"region": { +"startLine": 8978, +"startColumn": 23, +"endLine": 8978, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Ember%20Media%20Manager\frmMain.vb", +"region": { +"startLine": 8997, +"startColumn": 23, +"endLine": 8997, +"endColumn": 30 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/EmberAPI-{208AA35E-C6AE-4D2D-A9DD-B6EFD19A4279}-S3898.json b/analyzers/its/expected/Ember-MM/EmberAPI-{208AA35E-C6AE-4D2D-A9DD-B6EFD19A4279}-S3898.json new file mode 100644 index 00000000000..1ca533d6273 --- /dev/null +++ b/analyzers/its/expected/Ember-MM/EmberAPI-{208AA35E-C6AE-4D2D-A9DD-B6EFD19A4279}-S3898.json @@ -0,0 +1,368 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'CustomUpdaterStruct'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1079, +"startColumn": 22, +"endLine": 1079, +"endColumn": 41 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'MovieSource'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1091, +"startColumn": 22, +"endLine": 1091, +"endColumn": 33 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'TVSource'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1099, +"startColumn": 22, +"endLine": 1099, +"endColumn": 30 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'DBMovie'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1105, +"startColumn": 22, +"endLine": 1105, +"endColumn": 29 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'DBTV'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1135, +"startColumn": 22, +"endLine": 1135, +"endColumn": 26 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Scans'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1169, +"startColumn": 22, +"endLine": 1169, +"endColumn": 27 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'ScrapeInfo'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1180, +"startColumn": 22, +"endLine": 1180, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'ScrapeModifier'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1201, +"startColumn": 22, +"endLine": 1201, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'ScrapeOptions'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1217, +"startColumn": 22, +"endLine": 1217, +"endColumn": 35 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'SettingsResult'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1252, +"startColumn": 22, +"endLine": 1252, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'TVScrapeOptions'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1264, +"startColumn": 22, +"endLine": 1264, +"endColumn": 37 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'ModulesMenus'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1291, +"startColumn": 22, +"endLine": 1291, +"endColumn": 34 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'DVD_Time_Type'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 522, +"startColumn": 23, +"endLine": 522, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'PGC_Cell_Info_Type'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 536, +"startColumn": 23, +"endLine": 536, +"endColumn": 41 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'struct_AudioAttributes_VTSM_VTS'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 547, +"startColumn": 23, +"endLine": 547, +"endColumn": 54 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'struct_IFO_VST_Parse'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 560, +"startColumn": 23, +"endLine": 560, +"endColumn": 43 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'struct_Program_Chain_Type'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 599, +"startColumn": 23, +"endLine": 599, +"endColumn": 48 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'struct_SRPT'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 617, +"startColumn": 23, +"endLine": 617, +"endColumn": 34 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'struct_VideoAttributes_VTS_VOBS'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 630, +"startColumn": 23, +"endLine": 630, +"endColumn": 54 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'SubPictureAtt_VTSM_VTS_Type'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 645, +"startColumn": 23, +"endLine": 645, +"endColumn": 50 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'VTS_PTT_SRPT'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 658, +"startColumn": 23, +"endLine": 658, +"endColumn": 35 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'ModuleResult'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIInterfaces.vb", +"region": { +"startLine": 202, +"startColumn": 22, +"endLine": 202, +"endColumn": 34 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Locs'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPILocalization.vb", +"region": { +"startLine": 267, +"startColumn": 15, +"endLine": 267, +"endColumn": 19 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type '_ISOLanguage'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPILocalization.vb", +"region": { +"startLine": 279, +"startColumn": 15, +"endLine": 279, +"endColumn": 27 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'AssemblyListItem'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIModules.vb", +"region": { +"startLine": 635, +"startColumn": 15, +"endLine": 635, +"endColumn": 31 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'VersionItem'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIModules.vb", +"region": { +"startLine": 646, +"startColumn": 15, +"endLine": 646, +"endColumn": 26 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIScanner.vb", +"region": { +"startLine": 1363, +"startColumn": 23, +"endLine": 1363, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'ProgressValue'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIScanner.vb", +"region": { +"startLine": 1374, +"startColumn": 23, +"endLine": 1374, +"endColumn": 36 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/generic.EmberCore.BulkRename-{EAAB0601-6478-44D2-A39C-89B0850D7833}-S3898.json b/analyzers/its/expected/Ember-MM/generic.EmberCore.BulkRename-{EAAB0601-6478-44D2-A39C-89B0850D7833}-S3898.json new file mode 100644 index 00000000000..18a303bb232 --- /dev/null +++ b/analyzers/its/expected/Ember-MM/generic.EmberCore.BulkRename-{EAAB0601-6478-44D2-A39C-89B0850D7833}-S3898.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type '_MySettings'.", +"location": { +"uri": "sources\Ember-MM\Addons\generic.EmberCore.BulkRename\Module.BulkRenamer.vb", +"region": { +"startLine": 268, +"startColumn": 15, +"endLine": 268, +"endColumn": 26 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/generic.EmberCore.MediaFileManager-{F6CACA89-E8E4-45D9-B942-97FBD4ADD106}-S3898.json b/analyzers/its/expected/Ember-MM/generic.EmberCore.MediaFileManager-{F6CACA89-E8E4-45D9-B942-97FBD4ADD106}-S3898.json new file mode 100644 index 00000000000..545b3fbb10b --- /dev/null +++ b/analyzers/its/expected/Ember-MM/generic.EmberCore.MediaFileManager-{F6CACA89-E8E4-45D9-B942-97FBD4ADD106}-S3898.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\generic.EmberCore.MediaFileManager\Module.MediaFileManagerModule.vb", +"region": { +"startLine": 379, +"startColumn": 23, +"endLine": 379, +"endColumn": 32 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/generic.EmberCore.MovieExporter-{B0BDF9C2-EB9C-4090-B9A9-B703DF00CCEF}-S3898.json b/analyzers/its/expected/Ember-MM/generic.EmberCore.MovieExporter-{B0BDF9C2-EB9C-4090-B9A9-B703DF00CCEF}-S3898.json new file mode 100644 index 00000000000..4aed09c2c4e --- /dev/null +++ b/analyzers/its/expected/Ember-MM/generic.EmberCore.MovieExporter-{B0BDF9C2-EB9C-4090-B9A9-B703DF00CCEF}-S3898.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\generic.EmberCore.MovieExport\dlgExportMovies.vb", +"region": { +"startLine": 837, +"startColumn": 23, +"endLine": 837, +"endColumn": 32 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/generic.EmberCore.NMT-{84B2143A-D04F-4262-923D-21AEDF86E2B7}-S3898.json b/analyzers/its/expected/Ember-MM/generic.EmberCore.NMT-{84B2143A-D04F-4262-923D-21AEDF86E2B7}-S3898.json new file mode 100644 index 00000000000..f2dc9acef87 --- /dev/null +++ b/analyzers/its/expected/Ember-MM/generic.EmberCore.NMT-{84B2143A-D04F-4262-923D-21AEDF86E2B7}-S3898.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\generic.EmberCore.NMT\dlgNMTMovies.vb", +"region": { +"startLine": 1340, +"startColumn": 23, +"endLine": 1340, +"endColumn": 32 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/generic.EmberCore.WebServer-{64D6F035-F186-4193-AF35-D20126B8DA64}-S3898.json b/analyzers/its/expected/Ember-MM/generic.EmberCore.WebServer-{64D6F035-F186-4193-AF35-D20126B8DA64}-S3898.json new file mode 100644 index 00000000000..1b431f38f9a --- /dev/null +++ b/analyzers/its/expected/Ember-MM/generic.EmberCore.WebServer-{64D6F035-F186-4193-AF35-D20126B8DA64}-S3898.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'MimeType'.", +"location": { +"uri": "sources\Ember-MM\Addons\generic.EmberCore.WebServer\Module.WebServer.vb", +"region": { +"startLine": 33, +"startColumn": 19, +"endLine": 33, +"endColumn": 27 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/generic.EmberCore.XBMC-{6FE33C61-E2C2-4982-9536-63AEF0A98AAA}-S3898.json b/analyzers/its/expected/Ember-MM/generic.EmberCore.XBMC-{6FE33C61-E2C2-4982-9536-63AEF0A98AAA}-S3898.json new file mode 100644 index 00000000000..aeed6c2060a --- /dev/null +++ b/analyzers/its/expected/Ember-MM/generic.EmberCore.XBMC-{6FE33C61-E2C2-4982-9536-63AEF0A98AAA}-S3898.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'EmberSource'.", +"location": { +"uri": "sources\Ember-MM\Addons\generic.EmberCore.XBMC\dlgXBMCHost.vb", +"region": { +"startLine": 15, +"startColumn": 15, +"endLine": 15, +"endColumn": 26 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/scraper.EmberCore-{EF6A550E-DD76-4F4D-8250-8598140F828B}-S3898.json b/analyzers/its/expected/Ember-MM/scraper.EmberCore-{EF6A550E-DD76-4F4D-8250-8598140F828B}-S3898.json new file mode 100644 index 00000000000..ed2f2233e69 --- /dev/null +++ b/analyzers/its/expected/Ember-MM/scraper.EmberCore-{EF6A550E-DD76-4F4D-8250-8598140F828B}-S3898.json @@ -0,0 +1,225 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\clsScrapeIMDB.vb", +"region": { +"startLine": 1001, +"startColumn": 27, +"endLine": 1001, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\clsScrapeIMDB.vb", +"region": { +"startLine": 1016, +"startColumn": 27, +"endLine": 1016, +"endColumn": 34 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\clsScrapeIMPA.vb", +"region": { +"startLine": 152, +"startColumn": 27, +"endLine": 152, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\clsScrapeIMPA.vb", +"region": { +"startLine": 163, +"startColumn": 27, +"endLine": 163, +"endColumn": 34 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\clsScrapeMPDB.vb", +"region": { +"startLine": 134, +"startColumn": 27, +"endLine": 134, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\clsScrapeMPDB.vb", +"region": { +"startLine": 144, +"startColumn": 27, +"endLine": 144, +"endColumn": 34 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\clsScrapeTMDB.vb", +"region": { +"startLine": 228, +"startColumn": 27, +"endLine": 228, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\dlgIMDBSearchResults.vb", +"region": { +"startLine": 486, +"startColumn": 23, +"endLine": 486, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\dlgIMDBSearchResults.vb", +"region": { +"startLine": 497, +"startColumn": 23, +"endLine": 497, +"endColumn": 30 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\dlgTrailer.vb", +"region": { +"startLine": 418, +"startColumn": 23, +"endLine": 418, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type '_MySettings'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\scraperMovieNativeModule.vb", +"region": { +"startLine": 668, +"startColumn": 15, +"endLine": 668, +"endColumn": 26 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'TVImages'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\TVScraper\clsScrapeTVDB.vb", +"region": { +"startLine": 130, +"startColumn": 22, +"endLine": 130, +"endColumn": 30 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\TVScraper\clsScrapeTVDB.vb", +"region": { +"startLine": 1262, +"startColumn": 27, +"endLine": 1262, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\TVScraper\clsScrapeTVDB.vb", +"region": { +"startLine": 1269, +"startColumn": 27, +"endLine": 1269, +"endColumn": 34 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\TVScraper\dlgTVDBSearchResults.vb", +"region": { +"startLine": 415, +"startColumn": 23, +"endLine": 415, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\TVScraper\dlgTVDBSearchResults.vb", +"region": { +"startLine": 425, +"startColumn": 23, +"endLine": 425, +"endColumn": 30 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'ImageTag'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\TVScraper\dlgTVImageSelect.vb", +"region": { +"startLine": 1196, +"startColumn": 23, +"endLine": 1196, +"endColumn": 31 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/scraper.EmberCore.XML-{E567C031-1F7B-4637-9B3A-806988DE50CF}-S3898.json b/analyzers/its/expected/Ember-MM/scraper.EmberCore.XML-{E567C031-1F7B-4637-9B3A-806988DE50CF}-S3898.json new file mode 100644 index 00000000000..a586800aa4d --- /dev/null +++ b/analyzers/its/expected/Ember-MM/scraper.EmberCore.XML-{E567C031-1F7B-4637-9B3A-806988DE50CF}-S3898.json @@ -0,0 +1,69 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore.XML\dlgSearchResults.vb", +"region": { +"startLine": 302, +"startColumn": 23, +"endLine": 302, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore.XML\dlgSearchResults.vb", +"region": { +"startLine": 312, +"startColumn": 23, +"endLine": 312, +"endColumn": 30 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore.XML\dlgTrailer.vb", +"region": { +"startLine": 336, +"startColumn": 23, +"endLine": 336, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore.XML\scraperMovieXMLModule.vb", +"region": { +"startLine": 49, +"startColumn": 23, +"endLine": 49, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'BufferContents'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore.XML\XMLScraper\ScraperLib\FunctionInformation.vb", +"region": { +"startLine": 33, +"startColumn": 26, +"endLine": 33, +"endColumn": 40 +} +} +} +] +} diff --git a/analyzers/rspec/vbnet/S3898_vb.net.html b/analyzers/rspec/vbnet/S3898_vb.net.html new file mode 100644 index 00000000000..3c5beea75f2 --- /dev/null +++ b/analyzers/rspec/vbnet/S3898_vb.net.html @@ -0,0 +1,29 @@ +

If you’re using a Structure, it is likely because you’re interested in performance. But by failing to implement +IEquatable<T> you’re loosing performance when comparisons are made because without IEquatable<T>, boxing and +reflection are used to make comparisons.

+

Noncompliant Code Example

+
+Structure MyStruct ' Noncompliant
+
+    Public Property Value As Integer
+
+End Structure
+
+

Compliant Solution

+
+Structure MyStruct
+    Implements IEquatable(Of MyStruct)
+
+    Public Property Value As Integer
+
+    Public Overloads Function Equals(other As MyStruct) As Boolean Implements IEquatable(Of MyStruct).Equals
+        ' ...
+    End Function
+
+End Structure
+
+

See

+ + diff --git a/analyzers/rspec/vbnet/S3898_vb.net.json b/analyzers/rspec/vbnet/S3898_vb.net.json new file mode 100644 index 00000000000..2d73bcab85a --- /dev/null +++ b/analyzers/rspec/vbnet/S3898_vb.net.json @@ -0,0 +1,17 @@ +{ + "title": "Value types should implement \"IEquatable\u003cT\u003e\"", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "20min" + }, + "tags": [ + "performance" + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-3898", + "sqKey": "S3898", + "scope": "All", + "quickfix": "unknown" +} diff --git a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs index 3c5d390c8be..c57298bf631 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs @@ -83,6 +83,11 @@ node switch public override ImmutableArray FieldDeclarationIdentifiers(SyntaxNode node) => Cast(node).Declaration.Variables.Select(x => x.Identifier).ToImmutableArray(); + public override SyntaxKind[] ModifierKinds(SyntaxNode node) => + node is TypeDeclarationSyntax typeDeclaration + ? typeDeclaration.Modifiers.Select(x => x.Kind()).ToArray() + : Array.Empty(); + public override SyntaxNode NodeExpression(SyntaxNode node) => node switch { diff --git a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs index eb759eaa675..0a97a8d62a8 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs @@ -51,10 +51,12 @@ internal sealed class CSharpSyntaxKindFacade : ISyntaxKindFacade public SyntaxKind[] ObjectCreationExpressions => new[] { SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression }; public SyntaxKind Parameter => SyntaxKind.Parameter; public SyntaxKind ParameterList => SyntaxKind.ParameterList; + public SyntaxKind RefKeyword => SyntaxKind.RefKeyword; public SyntaxKind ReturnStatement => SyntaxKind.ReturnStatement; public SyntaxKind SimpleAssignment => SyntaxKind.SimpleAssignmentExpression; public SyntaxKind SimpleMemberAccessExpression => SyntaxKind.SimpleMemberAccessExpression; public SyntaxKind[] StringLiteralExpressions => new[] { SyntaxKind.StringLiteralExpression, SyntaxKindEx.Utf8StringLiteralExpression }; + public SyntaxKind StructDeclaration => SyntaxKind.StructDeclaration; public SyntaxKind[] TypeDeclaration => new[] { SyntaxKind.ClassDeclaration, diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ValueTypeShouldImplementIEquatable.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ValueTypeShouldImplementIEquatable.cs index 224087f118e..ca0ec44cf28 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ValueTypeShouldImplementIEquatable.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ValueTypeShouldImplementIEquatable.cs @@ -18,29 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -namespace SonarAnalyzer.Rules.CSharp -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public sealed class ValueTypeShouldImplementIEquatable : SonarDiagnosticAnalyzer - { - private const string DiagnosticId = "S3898"; - private const string MessageFormat = "Implement 'IEquatable' in value type '{0}'."; - - private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); +namespace SonarAnalyzer.Rules.CSharp; - public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); - - protected override void Initialize(SonarAnalysisContext context) => - context.RegisterNodeAction( - c => - { - var declaration = (StructDeclarationSyntax)c.Node; - if (!declaration.Modifiers.Any(SyntaxKind.RefKeyword) - && c.SemanticModel.GetDeclaredSymbol(declaration) is { } structSymbol - && !structSymbol.Implements(KnownType.System_IEquatable_T)) - { - c.ReportIssue(Diagnostic.Create(Rule, declaration.Identifier.GetLocation(), declaration.Identifier.ValueText)); - } - }, SyntaxKind.StructDeclaration); - } +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class ValueTypeShouldImplementIEquatable : ValueTypeShouldImplementIEquatableBase +{ + protected override ILanguageFacade Language => CSharpFacade.Instance; } diff --git a/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs b/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs index b81a812e6ff..3d8a0052fc4 100644 --- a/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs +++ b/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs @@ -39,11 +39,13 @@ public interface ISyntaxKindFacade abstract TSyntaxKind[] MethodDeclarations { get; } abstract TSyntaxKind[] ObjectCreationExpressions { get; } abstract TSyntaxKind Parameter { get; } + abstract TSyntaxKind RefKeyword { get; } abstract TSyntaxKind ParameterList { get; } abstract TSyntaxKind ReturnStatement { get; } abstract TSyntaxKind SimpleAssignment { get; } abstract TSyntaxKind SimpleMemberAccessExpression { get; } abstract TSyntaxKind[] StringLiteralExpressions { get; } + abstract TSyntaxKind StructDeclaration { get; } abstract TSyntaxKind[] TypeDeclaration { get; } abstract TSyntaxKind LeftShiftExpression { get; } abstract TSyntaxKind RightShiftExpression { get; } diff --git a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs index cd6fbef5c44..d97f5cfa666 100644 --- a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs @@ -40,6 +40,7 @@ public abstract class SyntaxFacade public abstract SyntaxToken? InvocationIdentifier(SyntaxNode invocation); public abstract ImmutableArray LocalDeclarationIdentifiers(SyntaxNode node); public abstract ImmutableArray FieldDeclarationIdentifiers(SyntaxNode node); + public abstract TSyntaxKind[] ModifierKinds(SyntaxNode node); public abstract SyntaxNode NodeExpression(SyntaxNode node); public abstract SyntaxToken? NodeIdentifier(SyntaxNode node); public abstract SyntaxNode RemoveConditionalAccess(SyntaxNode node); diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ValueTypeShouldImplementIEquatableBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ValueTypeShouldImplementIEquatableBase.cs new file mode 100644 index 00000000000..75158d89b8c --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ValueTypeShouldImplementIEquatableBase.cs @@ -0,0 +1,47 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarAnalyzer.Rules; + +public abstract class ValueTypeShouldImplementIEquatableBase : SonarDiagnosticAnalyzer + where TSyntaxKind : struct +{ + private const string DiagnosticId = "S3898"; + + protected override string MessageFormat => "Implement 'IEquatable' in value type '{0}'."; + + protected ValueTypeShouldImplementIEquatableBase() : base(DiagnosticId) { } + + protected sealed override void Initialize(SonarAnalysisContext context) => + context.RegisterNodeAction( + Language.GeneratedCodeRecognizer, + c => + { + var modifiers = Language.Syntax.ModifierKinds(c.Node); + if (!modifiers.Any(x => x.Equals(Language.SyntaxKind.RefKeyword)) + && c.SemanticModel.GetDeclaredSymbol(c.Node) is INamedTypeSymbol structSymbol + && !structSymbol.Implements(KnownType.System_IEquatable_T)) + { + var identifier = Language.Syntax.NodeIdentifier(c.Node).Value; + c.ReportIssue(Diagnostic.Create(Rule, identifier.GetLocation(), identifier.ValueText)); + } + }, + Language.SyntaxKind.StructDeclaration); +} diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs index b387b82a0fb..b49dea3e7b7 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs @@ -81,6 +81,11 @@ node switch public override ImmutableArray FieldDeclarationIdentifiers(SyntaxNode node) => Cast(node).Declarators.SelectMany(d => d.Names.Select(n => n.Identifier)).ToImmutableArray(); + public override SyntaxKind[] ModifierKinds(SyntaxNode node) => + node is StructureBlockSyntax structureBlock + ? structureBlock.StructureStatement.Modifiers.Select(x => x.Kind()).ToArray() + : Array.Empty(); + public override SyntaxNode NodeExpression(SyntaxNode node) => node switch { diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs index a5380fcc3c2..602fd70132b 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs @@ -47,10 +47,12 @@ internal sealed class VisualBasicSyntaxKindFacade : ISyntaxKindFacade new[] { SyntaxKind.ObjectCreationExpression }; public SyntaxKind Parameter => SyntaxKind.Parameter; public SyntaxKind ParameterList => SyntaxKind.ParameterList; + public SyntaxKind RefKeyword => SyntaxKind.ByRefKeyword; public SyntaxKind ReturnStatement => SyntaxKind.ReturnStatement; public SyntaxKind SimpleAssignment => SyntaxKind.SimpleAssignmentStatement; public SyntaxKind SimpleMemberAccessExpression => SyntaxKind.SimpleMemberAccessExpression; public SyntaxKind[] StringLiteralExpressions => new[] { SyntaxKind.StringLiteralExpression }; + public SyntaxKind StructDeclaration => SyntaxKind.StructureBlock; public SyntaxKind[] TypeDeclaration => new[] { SyntaxKind.ClassBlock, SyntaxKind.StructureBlock, SyntaxKind.InterfaceBlock, SyntaxKind.EnumBlock }; public SyntaxKind LeftShiftExpression => SyntaxKind.LeftShiftExpression; public SyntaxKind RightShiftExpression => SyntaxKind.RightShiftExpression; diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicSyntaxHelper.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicSyntaxHelper.cs index 91cde3efb69..b9ceda82773 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicSyntaxHelper.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicSyntaxHelper.cs @@ -18,250 +18,250 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -namespace SonarAnalyzer.Helpers +namespace SonarAnalyzer.Helpers; + +internal static class VisualBasicSyntaxHelper { - internal static class VisualBasicSyntaxHelper + private static readonly SyntaxKind[] LiteralSyntaxKinds = + new[] + { + SyntaxKind.CharacterLiteralExpression, + SyntaxKind.FalseLiteralExpression, + SyntaxKind.NothingLiteralExpression, + SyntaxKind.NumericLiteralExpression, + SyntaxKind.StringLiteralExpression, + SyntaxKind.TrueLiteralExpression, + }; + + public static SyntaxNode GetTopMostContainingMethod(this SyntaxNode node) => + node.AncestorsAndSelf().LastOrDefault(ancestor => ancestor is MethodBaseSyntax || ancestor is PropertyBlockSyntax); + + public static SyntaxNode RemoveParentheses(this SyntaxNode expression) { - private static readonly SyntaxKind[] LiteralSyntaxKinds = - new[] - { - SyntaxKind.CharacterLiteralExpression, - SyntaxKind.FalseLiteralExpression, - SyntaxKind.NothingLiteralExpression, - SyntaxKind.NumericLiteralExpression, - SyntaxKind.StringLiteralExpression, - SyntaxKind.TrueLiteralExpression, - }; - - public static SyntaxNode GetTopMostContainingMethod(this SyntaxNode node) => - node.AncestorsAndSelf().LastOrDefault(ancestor => ancestor is MethodBaseSyntax || ancestor is PropertyBlockSyntax); - - public static SyntaxNode RemoveParentheses(this SyntaxNode expression) + var current = expression; + while (current is ParenthesizedExpressionSyntax parenthesized) { - var current = expression; - while (current is ParenthesizedExpressionSyntax parenthesized) - { - current = parenthesized.Expression; - } - return current; + current = parenthesized.Expression; } + return current; + } - public static ExpressionSyntax RemoveParentheses(this ExpressionSyntax expression) => - (ExpressionSyntax)RemoveParentheses((SyntaxNode)expression); + public static ExpressionSyntax RemoveParentheses(this ExpressionSyntax expression) => + (ExpressionSyntax)RemoveParentheses((SyntaxNode)expression); - public static SyntaxNode GetSelfOrTopParenthesizedExpression(this SyntaxNode node) + public static SyntaxNode GetSelfOrTopParenthesizedExpression(this SyntaxNode node) + { + var current = node; + while (current?.Parent?.IsKind(SyntaxKind.ParenthesizedExpression) ?? false) { - var current = node; - while (current?.Parent?.IsKind(SyntaxKind.ParenthesizedExpression) ?? false) - { - current = current.Parent; - } - return current; + current = current.Parent; } + return current; + } - public static ExpressionSyntax GetSelfOrTopParenthesizedExpression(this ExpressionSyntax expression) => - (ExpressionSyntax)GetSelfOrTopParenthesizedExpression((SyntaxNode)expression); + public static ExpressionSyntax GetSelfOrTopParenthesizedExpression(this ExpressionSyntax expression) => + (ExpressionSyntax)GetSelfOrTopParenthesizedExpression((SyntaxNode)expression); - public static SyntaxNode GetFirstNonParenthesizedParent(this SyntaxNode node) => - node.GetSelfOrTopParenthesizedExpression().Parent; + public static SyntaxNode GetFirstNonParenthesizedParent(this SyntaxNode node) => + node.GetSelfOrTopParenthesizedExpression().Parent; - #region Statement + #region Statement - public static StatementSyntax GetPrecedingStatement(this StatementSyntax currentStatement) - { - var children = currentStatement.Parent.ChildNodes().ToList(); - var index = children.IndexOf(currentStatement); - return index == 0 ? null : children[index - 1] as StatementSyntax; - } + public static StatementSyntax GetPrecedingStatement(this StatementSyntax currentStatement) + { + var children = currentStatement.Parent.ChildNodes().ToList(); + var index = children.IndexOf(currentStatement); + return index == 0 ? null : children[index - 1] as StatementSyntax; + } - public static StatementSyntax GetSucceedingStatement(this StatementSyntax currentStatement) - { - var children = currentStatement.Parent.ChildNodes().ToList(); - var index = children.IndexOf(currentStatement); - return index == children.Count - 1 ? null : children[index + 1] as StatementSyntax; - } + public static StatementSyntax GetSucceedingStatement(this StatementSyntax currentStatement) + { + var children = currentStatement.Parent.ChildNodes().ToList(); + var index = children.IndexOf(currentStatement); + return index == children.Count - 1 ? null : children[index + 1] as StatementSyntax; + } - #endregion Statement + #endregion Statement - public static bool IsNothingLiteral(this SyntaxNode syntaxNode) => - syntaxNode != null && syntaxNode.IsKind(SyntaxKind.NothingLiteralExpression); + public static bool IsNothingLiteral(this SyntaxNode syntaxNode) => + syntaxNode != null && syntaxNode.IsKind(SyntaxKind.NothingLiteralExpression); - public static bool IsAnyKind(this SyntaxNode syntaxNode, params SyntaxKind[] syntaxKinds) => - syntaxNode != null && syntaxKinds.Contains((SyntaxKind)syntaxNode.RawKind); + public static bool IsAnyKind(this SyntaxNode syntaxNode, params SyntaxKind[] syntaxKinds) => + syntaxNode != null && syntaxKinds.Contains((SyntaxKind)syntaxNode.RawKind); - public static bool IsAnyKind(this SyntaxToken syntaxToken, ISet collection) => - collection.Contains((SyntaxKind)syntaxToken.RawKind); + public static bool IsAnyKind(this SyntaxToken syntaxToken, ISet collection) => + collection.Contains((SyntaxKind)syntaxToken.RawKind); - public static bool IsAnyKind(this SyntaxNode syntaxNode, ISet collection) => - syntaxNode != null && collection.Contains((SyntaxKind)syntaxNode.RawKind); + public static bool IsAnyKind(this SyntaxNode syntaxNode, ISet collection) => + syntaxNode != null && collection.Contains((SyntaxKind)syntaxNode.RawKind); - public static bool IsAnyKind(this SyntaxToken syntaxToken, params SyntaxKind[] syntaxKinds) => - syntaxKinds.Contains((SyntaxKind)syntaxToken.RawKind); + public static bool IsAnyKind(this SyntaxToken syntaxToken, params SyntaxKind[] syntaxKinds) => + syntaxKinds.Contains((SyntaxKind)syntaxToken.RawKind); - public static bool AnyOfKind(this IEnumerable nodes, SyntaxKind kind) => - nodes.Any(n => n.RawKind == (int)kind); + public static bool AnyOfKind(this IEnumerable nodes, SyntaxKind kind) => + nodes.Any(n => n.RawKind == (int)kind); - public static SyntaxToken? GetMethodCallIdentifier(this InvocationExpressionSyntax invocation) + public static SyntaxToken? GetMethodCallIdentifier(this InvocationExpressionSyntax invocation) + { + if (invocation == null || + invocation.Expression == null) { - if (invocation == null || - invocation.Expression == null) - { + return null; + } + + var expressionType = invocation.Expression.Kind(); + // in vb.net when using the null - conditional operator (e.g.handle?.IsClosed), the parser + // will generate a SimpleMemberAccessExpression and not a MemberBindingExpressionSyntax like for C# + switch (expressionType) + { + case SyntaxKind.IdentifierName: + return ((IdentifierNameSyntax)invocation.Expression).Identifier; + case SyntaxKind.SimpleMemberAccessExpression: + return ((MemberAccessExpressionSyntax)invocation.Expression).Name.Identifier; + default: return null; - } - - var expressionType = invocation.Expression.Kind(); - // in vb.net when using the null - conditional operator (e.g.handle?.IsClosed), the parser - // will generate a SimpleMemberAccessExpression and not a MemberBindingExpressionSyntax like for C# - switch (expressionType) - { - case SyntaxKind.IdentifierName: - return ((IdentifierNameSyntax)invocation.Expression).Identifier; - case SyntaxKind.SimpleMemberAccessExpression: - return ((MemberAccessExpressionSyntax)invocation.Expression).Name.Identifier; - default: - return null; - } } - public static bool IsMethodInvocation(this InvocationExpressionSyntax expression, KnownType type, string methodName, SemanticModel semanticModel) => - semanticModel.GetSymbolInfo(expression).Symbol is IMethodSymbol methodSymbol && - methodSymbol.IsInType(type) && - // vbnet is case insensitive - methodName.Equals(methodSymbol.Name, System.StringComparison.InvariantCultureIgnoreCase); + } + public static bool IsMethodInvocation(this InvocationExpressionSyntax expression, KnownType type, string methodName, SemanticModel semanticModel) => + semanticModel.GetSymbolInfo(expression).Symbol is IMethodSymbol methodSymbol && + methodSymbol.IsInType(type) && + // vbnet is case insensitive + methodName.Equals(methodSymbol.Name, System.StringComparison.InvariantCultureIgnoreCase); - public static bool IsOnBase(this ExpressionSyntax expression) => - IsOn(expression, SyntaxKind.MyBaseExpression); + public static bool IsOnBase(this ExpressionSyntax expression) => + IsOn(expression, SyntaxKind.MyBaseExpression); - private static bool IsOn(this ExpressionSyntax expression, SyntaxKind onKind) + private static bool IsOn(this ExpressionSyntax expression, SyntaxKind onKind) + { + switch (expression?.Kind()) { - switch (expression?.Kind()) - { - case SyntaxKind.InvocationExpression: - return IsOn(((InvocationExpressionSyntax)expression).Expression, onKind); - - case SyntaxKind.GlobalName: - case SyntaxKind.GenericName: - case SyntaxKind.IdentifierName: - case SyntaxKind.QualifiedName: - // This is a simplification as we don't check where the method is defined (so this could be this or base) - return true; - - case SyntaxKind.SimpleMemberAccessExpression: - return ((MemberAccessExpressionSyntax)expression).Expression.RemoveParentheses().IsKind(onKind); - - case SyntaxKind.ConditionalAccessExpression: - return ((ConditionalAccessExpressionSyntax)expression).Expression.RemoveParentheses().IsKind(onKind); - - default: - return false; - } + case SyntaxKind.InvocationExpression: + return IsOn(((InvocationExpressionSyntax)expression).Expression, onKind); + + case SyntaxKind.GlobalName: + case SyntaxKind.GenericName: + case SyntaxKind.IdentifierName: + case SyntaxKind.QualifiedName: + // This is a simplification as we don't check where the method is defined (so this could be this or base) + return true; + + case SyntaxKind.SimpleMemberAccessExpression: + return ((MemberAccessExpressionSyntax)expression).Expression.RemoveParentheses().IsKind(onKind); + + case SyntaxKind.ConditionalAccessExpression: + return ((ConditionalAccessExpressionSyntax)expression).Expression.RemoveParentheses().IsKind(onKind); + + default: + return false; } + } - public static SyntaxToken? GetIdentifier(this SyntaxNode node) => - node?.RemoveParentheses() switch - { - ClassBlockSyntax x => x.ClassStatement.Identifier, - ClassStatementSyntax x => x.Identifier, - IdentifierNameSyntax x => x.Identifier, - MemberAccessExpressionSyntax x => x.Name.Identifier, - MethodBlockSyntax x => x.SubOrFunctionStatement?.GetIdentifier(), - MethodStatementSyntax x => x.Identifier, - EnumStatementSyntax x => x.Identifier, - EnumMemberDeclarationSyntax x => x.Identifier, - InvocationExpressionSyntax x => x.Expression?.GetIdentifier(), - ModifiedIdentifierSyntax x => x.Identifier, - PredefinedTypeSyntax x => x.Keyword, - ParameterSyntax x => x.Identifier?.GetIdentifier(), - PropertyStatementSyntax x => x.Identifier, - SimpleArgumentSyntax x => x.NameColonEquals?.Name.Identifier, - SimpleNameSyntax x => x.Identifier, - QualifiedNameSyntax x => x.Right.Identifier, - _ => null, - }; - - public static string GetName(this SyntaxNode expression) => - expression.GetIdentifier()?.ValueText ?? string.Empty; - - public static bool NameIs(this ExpressionSyntax expression, string name) => - expression.GetName().Equals(name, StringComparison.InvariantCultureIgnoreCase); - - public static bool HasConstantValue(this ExpressionSyntax expression, SemanticModel semanticModel) => - expression.RemoveParentheses().IsAnyKind(LiteralSyntaxKinds) || expression.FindConstantValue(semanticModel) != null; - - public static string StringValue(this SyntaxNode node, SemanticModel semanticModel) => - node switch - { - LiteralExpressionSyntax literal when literal.IsKind(SyntaxKind.StringLiteralExpression) => literal.Token.ValueText, - InterpolatedStringExpressionSyntax expression => expression.TryGetInterpolatedTextValue(semanticModel, out var interpolatedValue) ? interpolatedValue : expression.GetContentsText(), - _ => null - }; - - public static bool IsLeftSideOfAssignment(this ExpressionSyntax expression) + public static SyntaxToken? GetIdentifier(this SyntaxNode node) => + node?.RemoveParentheses() switch { - var topParenthesizedExpression = expression.GetSelfOrTopParenthesizedExpression(); - return topParenthesizedExpression.Parent.IsKind(SyntaxKind.SimpleAssignmentStatement) && - topParenthesizedExpression.Parent is AssignmentStatementSyntax assignment && - assignment.Left == topParenthesizedExpression; - } + ClassBlockSyntax x => x.ClassStatement.Identifier, + ClassStatementSyntax x => x.Identifier, + IdentifierNameSyntax x => x.Identifier, + MemberAccessExpressionSyntax x => x.Name.Identifier, + MethodBlockSyntax x => x.SubOrFunctionStatement?.GetIdentifier(), + MethodStatementSyntax x => x.Identifier, + EnumStatementSyntax x => x.Identifier, + EnumMemberDeclarationSyntax x => x.Identifier, + InvocationExpressionSyntax x => x.Expression?.GetIdentifier(), + ModifiedIdentifierSyntax x => x.Identifier, + PredefinedTypeSyntax x => x.Keyword, + ParameterSyntax x => x.Identifier?.GetIdentifier(), + PropertyStatementSyntax x => x.Identifier, + SimpleArgumentSyntax x => x.NameColonEquals?.Name.Identifier, + SimpleNameSyntax x => x.Identifier, + StructureBlockSyntax x => x.StructureStatement.Identifier, + QualifiedNameSyntax x => x.Right.Identifier, + _ => null, + }; + + public static string GetName(this SyntaxNode expression) => + expression.GetIdentifier()?.ValueText ?? string.Empty; + + public static bool NameIs(this ExpressionSyntax expression, string name) => + expression.GetName().Equals(name, StringComparison.InvariantCultureIgnoreCase); + + public static bool HasConstantValue(this ExpressionSyntax expression, SemanticModel semanticModel) => + expression.RemoveParentheses().IsAnyKind(LiteralSyntaxKinds) || expression.FindConstantValue(semanticModel) != null; + + public static string StringValue(this SyntaxNode node, SemanticModel semanticModel) => + node switch + { + LiteralExpressionSyntax literal when literal.IsKind(SyntaxKind.StringLiteralExpression) => literal.Token.ValueText, + InterpolatedStringExpressionSyntax expression => expression.TryGetInterpolatedTextValue(semanticModel, out var interpolatedValue) ? interpolatedValue : expression.GetContentsText(), + _ => null + }; + + public static bool IsLeftSideOfAssignment(this ExpressionSyntax expression) + { + var topParenthesizedExpression = expression.GetSelfOrTopParenthesizedExpression(); + return topParenthesizedExpression.Parent.IsKind(SyntaxKind.SimpleAssignmentStatement) && + topParenthesizedExpression.Parent is AssignmentStatementSyntax assignment && + assignment.Left == topParenthesizedExpression; + } - public static bool IsComment(this SyntaxTrivia trivia) + public static bool IsComment(this SyntaxTrivia trivia) + { + switch (trivia.Kind()) { - switch (trivia.Kind()) - { - case SyntaxKind.CommentTrivia: - case SyntaxKind.DocumentationCommentExteriorTrivia: - case SyntaxKind.DocumentationCommentTrivia: - return true; - - default: - return false; - } + case SyntaxKind.CommentTrivia: + case SyntaxKind.DocumentationCommentExteriorTrivia: + case SyntaxKind.DocumentationCommentTrivia: + return true; + + default: + return false; } + } - public static Location FindIdentifierLocation(this MethodBlockBaseSyntax methodBlockBase) => - GetIdentifierOrDefault(methodBlockBase)?.GetLocation(); + public static Location FindIdentifierLocation(this MethodBlockBaseSyntax methodBlockBase) => + GetIdentifierOrDefault(methodBlockBase)?.GetLocation(); - public static SyntaxToken? GetIdentifierOrDefault(this MethodBlockBaseSyntax methodBlockBase) - { - var blockStatement = methodBlockBase?.BlockStatement; + public static SyntaxToken? GetIdentifierOrDefault(this MethodBlockBaseSyntax methodBlockBase) + { + var blockStatement = methodBlockBase?.BlockStatement; - switch (blockStatement?.Kind()) - { - case SyntaxKind.SubNewStatement: - return (blockStatement as SubNewStatementSyntax)?.NewKeyword; + switch (blockStatement?.Kind()) + { + case SyntaxKind.SubNewStatement: + return (blockStatement as SubNewStatementSyntax)?.NewKeyword; - case SyntaxKind.FunctionStatement: - case SyntaxKind.SubStatement: - return (blockStatement as MethodStatementSyntax)?.Identifier; + case SyntaxKind.FunctionStatement: + case SyntaxKind.SubStatement: + return (blockStatement as MethodStatementSyntax)?.Identifier; - default: - return null; - } + default: + return null; } + } - public static string GetIdentifierText(this MethodBlockSyntax method) - => method.SubOrFunctionStatement.Identifier.ValueText; + public static string GetIdentifierText(this MethodBlockSyntax method) + => method.SubOrFunctionStatement.Identifier.ValueText; - public static SeparatedSyntaxList? GetParameters(this MethodBlockSyntax method) - => method.BlockStatement?.ParameterList?.Parameters; + public static SeparatedSyntaxList? GetParameters(this MethodBlockSyntax method) + => method.BlockStatement?.ParameterList?.Parameters; - public static ExpressionSyntax Get(this ArgumentListSyntax argumentList, int index) => - argumentList != null && argumentList.Arguments.Count > index - ? argumentList.Arguments[index].GetExpression().RemoveParentheses() - : null; + public static ExpressionSyntax Get(this ArgumentListSyntax argumentList, int index) => + argumentList != null && argumentList.Arguments.Count > index + ? argumentList.Arguments[index].GetExpression().RemoveParentheses() + : null; - /// - /// Returns argument expressions for given parameter. - /// - /// There can be zero, one or more results based on parameter type (Optional or ParamArray/params). - /// - public static ImmutableArray ArgumentValuesForParameter(SemanticModel semanticModel, ArgumentListSyntax argumentList, string parameterName) + /// + /// Returns argument expressions for given parameter. + /// + /// There can be zero, one or more results based on parameter type (Optional or ParamArray/params). + /// + public static ImmutableArray ArgumentValuesForParameter(SemanticModel semanticModel, ArgumentListSyntax argumentList, string parameterName) + { + var methodParameterLookup = new VisualBasicMethodParameterLookup(argumentList, semanticModel); + if (methodParameterLookup.TryGetSyntax(parameterName, out var expressions)) { - var methodParameterLookup = new VisualBasicMethodParameterLookup(argumentList, semanticModel); - if (methodParameterLookup.TryGetSyntax(parameterName, out var expressions)) - { - return expressions; - } - return ImmutableArray.Empty; + return expressions; } + return ImmutableArray.Empty; } } diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ValueTypeShouldImplementIEquatable.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ValueTypeShouldImplementIEquatable.cs new file mode 100644 index 00000000000..d74a08a373d --- /dev/null +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ValueTypeShouldImplementIEquatable.cs @@ -0,0 +1,32 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.VisualBasic; +using SonarAnalyzer.Helpers; + +namespace SonarAnalyzer.Rules.VisualBasic; + +[DiagnosticAnalyzer(LanguageNames.VisualBasic)] +public sealed class ValueTypeShouldImplementIEquatable : ValueTypeShouldImplementIEquatableBase +{ + protected override ILanguageFacade Language => VisualBasicFacade.Instance; +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Common/ReadMeTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Common/ReadMeTest.cs index 7b35e164d61..4e31432483b 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Common/ReadMeTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Common/ReadMeTest.cs @@ -51,7 +51,7 @@ private void HasCorrectRuleCount(AnalyzerLanguage language, string name) var count = int.Parse(match.Groups["count"].Value); var min = (count / 10) * 10; - rules.Should().BeInRange(min, min + 10); + rules.Should().BeInRange(min, min + 9); } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Facade/SyntaxFacadeTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Facade/SyntaxFacadeTest.cs index 621798750a1..e413aef6b56 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Facade/SyntaxFacadeTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Facade/SyntaxFacadeTest.cs @@ -22,98 +22,105 @@ using CS = Microsoft.CodeAnalysis.CSharp; using VB = Microsoft.CodeAnalysis.VisualBasic; -namespace SonarAnalyzer.UnitTest.Helpers +namespace SonarAnalyzer.UnitTest.Helpers; + +[TestClass] +public class SyntaxFacadeTest { - [TestClass] - public class SyntaxFacadeTest - { - private readonly CSharpSyntaxFacade cs = new(); - private readonly VisualBasicSyntaxFacade vb = new(); - - [TestMethod] - public void EnumMembers_Null_CS() => - cs.EnumMembers(null).Should().BeEmpty(); - - [TestMethod] - public void EnumMembers_Null_VB() => - vb.EnumMembers(null).Should().BeEmpty(); - - [TestMethod] - public void InvocationIdentifier_Null_CS() => - cs.InvocationIdentifier(null).Should().BeNull(); - - [TestMethod] - public void InvocationIdentifier_Null_VB() => - vb.InvocationIdentifier(null).Should().BeNull(); - - [TestMethod] - public void InvocationIdentifier_UnexpectedTypeThrows_CS() => - cs.Invoking(x => x.InvocationIdentifier(CS.SyntaxFactory.IdentifierName("ThisIsNotInvocation"))).Should().Throw(); - - [TestMethod] - public void InvocationIdentifier_UnexpectedTypeThrows_VB() => - vb.Invoking(x => x.InvocationIdentifier(VB.SyntaxFactory.IdentifierName("ThisIsNotInvocation"))).Should().Throw(); - - [TestMethod] - public void NodeExpression_Null_CS() => - cs.NodeExpression(null).Should().BeNull(); - - [TestMethod] - public void NodeExpression_Null_VB() => - vb.NodeExpression(null).Should().BeNull(); - - [TestMethod] - public void NodeExpression_UnexpectedTypeThrows_CS() => - cs.Invoking(x => x.NodeExpression(CS.SyntaxFactory.IdentifierName("ThisTypeDoesNotHaveExpression"))).Should().Throw(); - - [TestMethod] - public void NodeExpression_UnexpectedTypeThrows_VB() => - vb.Invoking(x => x.NodeExpression(VB.SyntaxFactory.IdentifierName("ThisTypeDoesNotHaveExpression"))).Should().Throw(); - - [TestMethod] - public void NodeIdentifier_Null_CS() => - cs.NodeIdentifier(null).Should().BeNull(); - - [TestMethod] - public void NodeIdentifier_Null_VB() => - vb.NodeIdentifier(null).Should().BeNull(); - - [TestMethod] - public void NodeIdentifier_Unexpected_Returns_Null_CS() => - cs.NodeIdentifier(CS.SyntaxFactory.AttributeList()).Should().BeNull(); - - [TestMethod] - public void NodeIdentifier_Unexpected_Returns_Null_VB() => - vb.NodeIdentifier(VB.SyntaxFactory.AttributeList()).Should().BeNull(); - - [TestMethod] - public void StringValue_UnexpectedType_CS() => - cs.StringValue(CS.SyntaxFactory.ThrowStatement(), null).Should().BeNull(); - - [TestMethod] - public void StringValue_UnexpectedType_VB() => - vb.StringValue(VB.SyntaxFactory.ThrowStatement(), null).Should().BeNull(); - - [TestMethod] - public void StringValue_NodeIsNull_CS() => - cs.StringValue(null, null).Should().BeNull(); - - [TestMethod] - public void StringValue_NodeIsNull_VB() => - vb.StringValue(null, null).Should().BeNull(); - - [TestMethod] - public void RemoveConditionalAccess_Null_CS() => - cs.RemoveConditionalAccess(null).Should().BeNull(); - - [DataTestMethod] - [DataRow("M()", "M()")] - [DataRow("this.M()", "this.M()")] - [DataRow("A.B.C.M()", "A.B.C.M()")] - [DataRow("A.B?.C.M()", ".C.M()")] - [DataRow("A.B?.C?.M()", ".M()")] - [DataRow("A.B?.C?.D", ".D")] - public void RemoveConditionalAccess_SimpleInvocation_CS(string invocation, string expected) => - cs.RemoveConditionalAccess(CS.SyntaxFactory.ParseExpression(invocation)).ToString().Should().Be(expected); - } + private readonly CSharpSyntaxFacade cs = new(); + private readonly VisualBasicSyntaxFacade vb = new(); + + [TestMethod] + public void EnumMembers_Null_CS() => + cs.EnumMembers(null).Should().BeEmpty(); + + [TestMethod] + public void EnumMembers_Null_VB() => + vb.EnumMembers(null).Should().BeEmpty(); + + [TestMethod] + public void InvocationIdentifier_Null_CS() => + cs.InvocationIdentifier(null).Should().BeNull(); + + [TestMethod] + public void InvocationIdentifier_Null_VB() => + vb.InvocationIdentifier(null).Should().BeNull(); + + [TestMethod] + public void InvocationIdentifier_UnexpectedTypeThrows_CS() => + cs.Invoking(x => x.InvocationIdentifier(CS.SyntaxFactory.IdentifierName("ThisIsNotInvocation"))).Should().Throw(); + + [TestMethod] + public void InvocationIdentifier_UnexpectedTypeThrows_VB() => + vb.Invoking(x => x.InvocationIdentifier(VB.SyntaxFactory.IdentifierName("ThisIsNotInvocation"))).Should().Throw(); + + [TestMethod] + public void ModifierKinds_Null_CS() => + cs.ModifierKinds(null).Should().BeEmpty(); + + [TestMethod] + public void ModifierKinds_Null_VB() => + vb.ModifierKinds(null).Should().BeEmpty(); + + [TestMethod] + public void NodeExpression_Null_CS() => + cs.NodeExpression(null).Should().BeNull(); + + [TestMethod] + public void NodeExpression_Null_VB() => + vb.NodeExpression(null).Should().BeNull(); + + [TestMethod] + public void NodeExpression_UnexpectedTypeThrows_CS() => + cs.Invoking(x => x.NodeExpression(CS.SyntaxFactory.IdentifierName("ThisTypeDoesNotHaveExpression"))).Should().Throw(); + + [TestMethod] + public void NodeExpression_UnexpectedTypeThrows_VB() => + vb.Invoking(x => x.NodeExpression(VB.SyntaxFactory.IdentifierName("ThisTypeDoesNotHaveExpression"))).Should().Throw(); + + [TestMethod] + public void NodeIdentifier_Null_CS() => + cs.NodeIdentifier(null).Should().BeNull(); + + [TestMethod] + public void NodeIdentifier_Null_VB() => + vb.NodeIdentifier(null).Should().BeNull(); + + [TestMethod] + public void NodeIdentifier_Unexpected_Returns_Null_CS() => + cs.NodeIdentifier(CS.SyntaxFactory.AttributeList()).Should().BeNull(); + + [TestMethod] + public void NodeIdentifier_Unexpected_Returns_Null_VB() => + vb.NodeIdentifier(VB.SyntaxFactory.AttributeList()).Should().BeNull(); + + [TestMethod] + public void StringValue_UnexpectedType_CS() => + cs.StringValue(CS.SyntaxFactory.ThrowStatement(), null).Should().BeNull(); + + [TestMethod] + public void StringValue_UnexpectedType_VB() => + vb.StringValue(VB.SyntaxFactory.ThrowStatement(), null).Should().BeNull(); + + [TestMethod] + public void StringValue_NodeIsNull_CS() => + cs.StringValue(null, null).Should().BeNull(); + + [TestMethod] + public void StringValue_NodeIsNull_VB() => + vb.StringValue(null, null).Should().BeNull(); + + [TestMethod] + public void RemoveConditionalAccess_Null_CS() => + cs.RemoveConditionalAccess(null).Should().BeNull(); + + [DataTestMethod] + [DataRow("M()", "M()")] + [DataRow("this.M()", "this.M()")] + [DataRow("A.B.C.M()", "A.B.C.M()")] + [DataRow("A.B?.C.M()", ".C.M()")] + [DataRow("A.B?.C?.M()", ".M()")] + [DataRow("A.B?.C?.D", ".D")] + public void RemoveConditionalAccess_SimpleInvocation_CS(string invocation, string expected) => + cs.RemoveConditionalAccess(CS.SyntaxFactory.ParseExpression(invocation)).ToString().Should().Be(expected); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs index 16f4ee98537..3cab127083f 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs @@ -3822,7 +3822,7 @@ internal static class RuleTypeMappingVB // ["S3895"], // ["S3896"], // ["S3897"], - // ["S3898"], + ["S3898"] = "CODE_SMELL", // ["S3899"], // ["S3900"], // ["S3901"], diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ValueTypeShouldImplementIEquatableTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ValueTypeShouldImplementIEquatableTest.cs index df93570e5e6..acd8c79b2c8 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ValueTypeShouldImplementIEquatableTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ValueTypeShouldImplementIEquatableTest.cs @@ -18,26 +18,30 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using SonarAnalyzer.Rules.CSharp; +using CS = SonarAnalyzer.Rules.CSharp; +using VB = SonarAnalyzer.Rules.VisualBasic; -namespace SonarAnalyzer.UnitTest.Rules +namespace SonarAnalyzer.UnitTest.Rules; + +[TestClass] +public class ValueTypeShouldImplementIEquatableTest { - [TestClass] - public class ValueTypeShouldImplementIEquatableTest - { - private readonly VerifierBuilder builder = new VerifierBuilder(); + private readonly VerifierBuilder builderCS = new VerifierBuilder(); + private readonly VerifierBuilder builderVB = new VerifierBuilder(); - [TestMethod] - public void ValueTypeShouldImplementIEquatable() => - builder.AddPaths("ValueTypeShouldImplementIEquatable.cs").WithOptions(ParseOptionsHelper.FromCSharp8).Verify(); + [TestMethod] + public void ValueTypeShouldImplementIEquatable_CS() => + builderCS.AddPaths("ValueTypeShouldImplementIEquatable.cs").WithOptions(ParseOptionsHelper.FromCSharp8).Verify(); #if NET - [TestMethod] - public void ValueTypeShouldImplementIEquatable_CSharp10() => - builder.AddPaths("ValueTypeShouldImplementIEquatable.CSharp10.cs").WithOptions(ParseOptionsHelper.FromCSharp10).Verify(); + [TestMethod] + public void ValueTypeShouldImplementIEquatable_CSharp10() => + builderCS.AddPaths("ValueTypeShouldImplementIEquatable.CSharp10.cs").WithOptions(ParseOptionsHelper.FromCSharp10).Verify(); #endif - } + [TestMethod] + public void ValueTypeShouldImplementIEquatable_VB() => + builderVB.AddPaths("ValueTypeShouldImplementIEquatable.vb").Verify(); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.CSharp10.cs index 46b509027dd..22b99839342 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.CSharp10.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.CSharp10.cs @@ -1,17 +1,15 @@ using System; using System.Collections.Generic; -namespace Tests.Diagnostics + +record struct MyStruct // Compliant. Record struct implement IEquatable by definition { - record struct MyStruct // Compliant. Record struct implement IEquatable by definition - { - } +} - record struct MyCompliantStruct : IEquatable // Compliant +record struct MyCompliantStruct : IEquatable // Compliant +{ + public bool Equals(MyCompliantStruct other) { - public bool Equals(MyCompliantStruct other) - { - throw new NotImplementedException(); - } + throw new NotImplementedException(); } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.cs index 4b358ac30aa..8280710378d 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.cs @@ -1,23 +1,25 @@ using System; using System.Collections.Generic; -namespace Tests.Diagnostics +struct MyStruct // Noncompliant {{Implement 'IEquatable' in value type 'MyStruct'.}} +// ^^^^^^^^ { - struct MyStruct // Noncompliant {{Implement 'IEquatable' in value type 'MyStruct'.}} -// ^^^^^^^^ - { - } +} - struct MyCompliantStruct : IEquatable // Compliant +struct MyCompliantStruct : IEquatable // Compliant +{ + public bool Equals(MyCompliantStruct other) { - public bool Equals(MyCompliantStruct other) - { - throw new NotImplementedException(); - } + throw new NotImplementedException(); } +} - // https://github.com/SonarSource/sonar-dotnet/issues/3157 - ref struct Repro_3157 // Compliant, ref structs can not implement interfaces - { - } +struct ComparableStruct : IComparable // Noncompliant +{ + public int CompareTo(ComparableStruct other) { return 0; } +} + +// https://github.com/SonarSource/sonar-dotnet/issues/3157 +ref struct Repro_3157 // Compliant, ref structs can not implement interfaces +{ } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.vb new file mode 100644 index 00000000000..fb225182123 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.vb @@ -0,0 +1,19 @@ +Public Structure MyCompliantStruct ' Compliant + Implements IEquatable(Of MyCompliantStruct) + + Public Overloads Function Equals(other As MyCompliantStruct) As Boolean Implements IEquatable(Of MyCompliantStruct).Equals + Return True + End Function +End Structure + +Structure MyStruct ' Noncompliant {{Implement 'IEquatable' in value type 'MyStruct'.}} + ' ^^^^^^^^ +End Structure + +Structure ComparableStruct ' Noncompliant + Implements IComparable(Of ComparableStruct) + + Public Function CompareTo(other As ComparableStruct) As Integer Implements IComparable(Of ComparableStruct).CompareTo + Return 0 + End Function +End Structure