diff --git a/.github/ISSUE_TEMPLATE/false-positive---false-negative.md b/.github/ISSUE_TEMPLATE/false-positive---false-negative.md index dd32c8659bd..876ded3c037 100644 --- a/.github/ISSUE_TEMPLATE/false-positive---false-negative.md +++ b/.github/ISSUE_TEMPLATE/false-positive---false-negative.md @@ -1,7 +1,7 @@ --- name: Report a False Positive / False Negative about: Open an issue about a C#/VB.NET analysis rule. -title: '' +title: "Fix Sxxxx [FP/FN]: Issue title" labels: '' assignees: '' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2f78b44977..03ea1a9c6bd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: permissions: id-token: write contents: write - uses: SonarSource/gh-action_release/.github/workflows/main.yaml@v5.0.1 + uses: SonarSource/gh-action_release/.github/workflows/main.yaml@v5 #uses the latest v5.x.y version rather than a specific version with: publishToBinaries: true mavenCentralSync: true 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/packaging/SonarAnalyzer.CSharp.nuspec b/analyzers/packaging/SonarAnalyzer.CSharp.nuspec index 1cb28f09452..d1dd23dc189 100644 --- a/analyzers/packaging/SonarAnalyzer.CSharp.nuspec +++ b/analyzers/packaging/SonarAnalyzer.CSharp.nuspec @@ -2,7 +2,7 @@ SonarAnalyzer.CSharp - 8.52.0.0 + 8.53.0.0 SonarAnalyzer for C# SonarSource SonarSource @@ -13,7 +13,7 @@ false Roslyn analyzers that spot Bugs, Vulnerabilities and Code Smells in your code. For an even better overall experience, you can use SonarLint for Visual Studio, which is a free extension that can be used standalone or with SonarQube and/or SonarCloud. Roslyn analyzers that spot Bugs, Vulnerabilities and Code Smells in your code. For an even better overall experience, you can use SonarLint for Visual Studio, which is a free extension (https://www.sonarlint.org/visualstudio/) that can be used standalone or with SonarQube (https://www.sonarqube.org/) and/or SonarCloud (https://sonarcloud.io/). - https://github.com/SonarSource/sonar-dotnet/releases/tag/8.52.0.0 + https://github.com/SonarSource/sonar-dotnet/releases/tag/8.53.0.0 en-US Copyright © 2015-2023 SonarSource SA Roslyn Analyzers Refactoring CodeAnalysis CleanCode Clean Code diff --git a/analyzers/packaging/SonarAnalyzer.VisualBasic.nuspec b/analyzers/packaging/SonarAnalyzer.VisualBasic.nuspec index 4486a317e26..fed74aa6f05 100644 --- a/analyzers/packaging/SonarAnalyzer.VisualBasic.nuspec +++ b/analyzers/packaging/SonarAnalyzer.VisualBasic.nuspec @@ -2,7 +2,7 @@ SonarAnalyzer.VisualBasic - 8.52.0.0 + 8.53.0.0 SonarAnalyzer for Visual Basic SonarSource SonarSource @@ -13,7 +13,7 @@ false Roslyn analyzers that spot Bugs, Vulnerabilities and Code Smells in your code. For an even better overall experience, you can use SonarLint for Visual Studio, which is a free extension that can be used standalone or with SonarQube and/or SonarCloud. Roslyn analyzers that spot Bugs, Vulnerabilities and Code Smells in your code. For an even better overall experience, you can use SonarLint for Visual Studio, which is a free extension (https://www.sonarlint.org/visualstudio/) that can be used standalone or with SonarQube (https://www.sonarqube.org/) and/or SonarCloud (https://sonarcloud.io/). - https://github.com/SonarSource/sonar-dotnet/releases/tag/8.52.0.0 + https://github.com/SonarSource/sonar-dotnet/releases/tag/8.53.0.0 en-US Copyright © 2015-2023 SonarSource SA Roslyn Analyzers Refactoring CodeAnalysis CleanCode Clean Code diff --git a/analyzers/rspec/cs/S1168_c#.html b/analyzers/rspec/cs/S1168_c#.html index 57a10136058..a648af3a67c 100644 --- a/analyzers/rspec/cs/S1168_c#.html +++ b/analyzers/rspec/cs/S1168_c#.html @@ -1,6 +1,6 @@ -

Returning null instead of an actual array, collection or map forces callers of the method to explicitly test for nullity, making them -more complex and less readable.

-

Moreover, in many cases, null is used as a synonym for empty.

+

Returning null or default instead of an actual collection forces the method callers to explicitly test for null, making +the code more complex and less readable.

+

Moreover, in many cases, null or default is used as a synonym for empty.

Noncompliant Code Example

 public Result[] GetResults()
@@ -8,9 +8,12 @@ 

Noncompliant Code Example

return null; // Noncompliant } -public IEnumerable<Result> GetResults() +public IEnumerable<Result> GetResults(bool condition) { - return null; // Noncompliant + var results = GenerateResults(); + return condition + ? results + : null; // Noncompliant } public IEnumerable<Result> GetResults() => null; // Noncompliant @@ -19,11 +22,11 @@

Noncompliant Code Example

{ get { - return null; // Noncompliant + return default(IEnumerable<Result>); // Noncompliant } } -public IEnumerable<Result> Results => null; // Noncompliant +public IEnumerable<Result> Results => default; // Noncompliant

Compliant Solution

@@ -32,9 +35,12 @@ 

Compliant Solution

return new Result[0]; } -public IEnumerable<Result> GetResults() +public IEnumerable<Result> GetResults(bool condition) { - return Enumerable.Empty<Result>(); + var results = GenerateResults(); + return condition + ? results + : Enumerable.Empty<Result>(); } public IEnumerable<Result> GetResults() => Enumerable.Empty<Result>(); diff --git a/analyzers/rspec/cs/S2197_c#.html b/analyzers/rspec/cs/S2197_c#.html index d9d2710d162..a7294b01d05 100644 --- a/analyzers/rspec/cs/S2197_c#.html +++ b/analyzers/rspec/cs/S2197_c#.html @@ -11,14 +11,14 @@

Compliant Solution

 public bool IsOdd(int x)
 {
-  return x %2 != 0;
+  return x % 2 != 0;
 }
 

or

 public bool IsOdd(uint x)
 {
-  return x %2 == 1;
+  return x % 2 == 1;
 }
 
diff --git a/analyzers/rspec/cs/S2221_c#.json b/analyzers/rspec/cs/S2221_c#.json index 44a372f9783..daf7f8cff3b 100644 --- a/analyzers/rspec/cs/S2221_c#.json +++ b/analyzers/rspec/cs/S2221_c#.json @@ -1,5 +1,5 @@ { - "title": "\"Exception\" should not be caught when not required by called methods", + "title": "\"Exception\" should not be caught", "type": "CODE_SMELL", "status": "ready", "remediation": { diff --git a/analyzers/rspec/cs/S2930_c#.html b/analyzers/rspec/cs/S2930_c#.html index faab67d528c..a318f8bf8a9 100644 --- a/analyzers/rspec/cs/S2930_c#.html +++ b/analyzers/rspec/cs/S2930_c#.html @@ -6,8 +6,8 @@ file descriptors (e.g. FileStream) or sockets (e.g. WebClient) open at any given time. Therefore, it is important to Dispose of them as soon as they are no longer needed, rather than relying on the garbage collector to call these objects' finalizers at some nondeterministic point in the future.

-

This rule tracks private fields and local variables of the following IDisposable types, which are never disposed, closed, -aliased, returned, or passed to other methods.

+

This rule tracks private fields and local variables of the following IDisposable / IAsyncDisposable types, +which are never disposed, closed, aliased, returned, or passed to other methods.

  • System.IO namespace
      @@ -60,7 +60,7 @@

      Noncompliant Code Example

Compliant Solution

-public class ResourceHolder : IDisposable
+public class ResourceHolder : IDisposable, IAsyncDisposable
 {
   private FileStream fs;
 
@@ -74,6 +74,11 @@ 

Compliant Solution

this.fs.Dispose(); } + public async ValueTask DisposeAsync() + { + await fs.DisposeAsync().ConfigureAwait(false); + } + public void WriteToFile(string path, string text) { using (var fs = new FileStream(path, FileMode.Open)) @@ -85,8 +90,9 @@

Compliant Solution

}

Exceptions

-

IDisposable variables returned from a method or passed to other methods are ignored, as are local IDisposables that are -initialized with other IDisposables.

+

IDisposable / IAsyncDisposable variables returned from a method or passed to other methods are ignored, as are local +IDisposable / IAsyncDisposable objects that are initialized with other IDisposable / +IAsyncDisposable objects.

 public Stream WriteToFile(string path, string text)
 {
diff --git a/analyzers/rspec/cs/S3898_c#.html b/analyzers/rspec/cs/S3898_c#.html
index 3d724d2531f..2b56d4bd69d 100644
--- a/analyzers/rspec/cs/S3898_c#.html
+++ b/analyzers/rspec/cs/S3898_c#.html
@@ -5,27 +5,23 @@ 

Noncompliant Code Example

 struct MyStruct  // Noncompliant
 {
-  private int i;
-  public int I
-  {
-    //...
-  }
+    public int Value { get; set; }
 }
 

Compliant Solution

 struct MyStruct : IEquatable<MyStruct>
 {
-  private int i;
-  public int I
-  {
-    //...
-  }
+    public int Value { get; set; }
 
-  public bool Equals(MyStruct other)
-  {
-    throw new NotImplementedException();
-  }
+    public bool Equals(MyStruct other)
+    {
+        // ...
+    }
 }
 
+

See

+ diff --git a/analyzers/rspec/cs/S4200_c#.html b/analyzers/rspec/cs/S4200_c#.html index 61f2bbdad93..74b572f7e42 100644 --- a/analyzers/rspec/cs/S4200_c#.html +++ b/analyzers/rspec/cs/S4200_c#.html @@ -1,8 +1,8 @@ -

Native methods are functions that reside in libraries outside the virtual machine. Being able to call them is useful for interoperability with -applications and libraries written in other programming languages, in particular when performing platform-specific operations. However doing so comes -with extra risks since it means stepping out of the security model of the virtual machine. It is therefore highly recommended to take extra steps, -like input validation, when invoking native methods. This is best done by making the native method private and by providing a wrapper -that performs these extra steps and verifications.

+

Native methods are functions that reside in libraries outside the .NET runtime. Calling them is helpful for interoperability with applications and +libraries written in other programming languages, mainly when performing platform-specific operations. However, doing so comes with additional risks +since it means stepping out of the memory-safety model of the runtime. It is therefore highly recommended to take extra steps, like input validation, +when invoking native methods. Making the native method private and providing a wrapper that performs these additional steps is the best +way to do so.

This rule raises an issue when a native method is declared public or its wrapper is too trivial.

Noncompliant Code Example

@@ -34,7 +34,7 @@ 

Compliant Solution

{ if (s != null && x >= 0 && x < s.Length) { - bar(s, x); + Bar(s, x); } } } diff --git a/analyzers/rspec/cs/S4507_c#.html b/analyzers/rspec/cs/S4507_c#.html index d523f855276..1f990e1c455 100644 --- a/analyzers/rspec/cs/S4507_c#.html +++ b/analyzers/rspec/cs/S4507_c#.html @@ -8,8 +8,8 @@ detailed information on both the system running the application and users.

Ask Yourself Whether

    -
  • the code or configuration enabling the application debug features is deployed on production servers or distributed to end users.
  • -
  • the application runs by default with debug features activated.
  • +
  • The code or configuration enabling the application debug features is deployed on production servers or distributed to end users.
  • +
  • The application runs by default with debug features activated.

There is a risk if you answered yes to any of those questions.

Recommended Secure Coding Practices

diff --git a/analyzers/rspec/cs/Sonar_way_profile.json b/analyzers/rspec/cs/Sonar_way_profile.json index aa51f3cfa31..e021d0716de 100644 --- a/analyzers/rspec/cs/Sonar_way_profile.json +++ b/analyzers/rspec/cs/Sonar_way_profile.json @@ -232,7 +232,6 @@ "S4428", "S4433", "S4456", - "S4457", "S4487", "S4502", "S4507", 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/rspec/vbnet/S4507_vb.net.html b/analyzers/rspec/vbnet/S4507_vb.net.html index b9a53e47ef5..11fbb756404 100644 --- a/analyzers/rspec/vbnet/S4507_vb.net.html +++ b/analyzers/rspec/vbnet/S4507_vb.net.html @@ -8,8 +8,8 @@ detailed information on both the system running the application and users.

Ask Yourself Whether

    -
  • the code or configuration enabling the application debug features is deployed on production servers or distributed to end users.
  • -
  • the application runs by default with debug features activated.
  • +
  • The code or configuration enabling the application debug features is deployed on production servers or distributed to end users.
  • +
  • The application runs by default with debug features activated.

There is a risk if you answered yes to any of those questions.

Recommended Secure Coding Practices

diff --git a/analyzers/src/AssemblyInfo.Shared.cs b/analyzers/src/AssemblyInfo.Shared.cs index ddba2672df3..e135d235835 100644 --- a/analyzers/src/AssemblyInfo.Shared.cs +++ b/analyzers/src/AssemblyInfo.Shared.cs @@ -23,10 +23,10 @@ using System.Resources; using System.Runtime.InteropServices; -[assembly: AssemblyVersion("8.52.0")] -[assembly: AssemblyFileVersion("8.52.0.0")] +[assembly: AssemblyVersion("8.53.0")] +[assembly: AssemblyFileVersion("8.53.0.0")] // The value should look like "Version:X.X.X.X Branch:not-set Sha1:not-set" -[assembly: AssemblyInformationalVersion("Version:8.52.0.0 Branch: Sha1:")] +[assembly: AssemblyInformationalVersion("Version:8.53.0.0 Branch: Sha1:")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("SonarSource")] [assembly: AssemblyCopyright("Copyright © 2015-2023 SonarSource SA")] diff --git a/analyzers/src/SonarAnalyzer.CFG/ShimLayer/SyntaxKindEx.cs b/analyzers/src/SonarAnalyzer.CFG/ShimLayer/SyntaxKindEx.cs index f8428351a7c..fec7e0ca6e9 100644 --- a/analyzers/src/SonarAnalyzer.CFG/ShimLayer/SyntaxKindEx.cs +++ b/analyzers/src/SonarAnalyzer.CFG/ShimLayer/SyntaxKindEx.cs @@ -11,6 +11,7 @@ public static class SyntaxKindEx public const SyntaxKind QuestionQuestionEqualsToken = (SyntaxKind)8284; public const SyntaxKind GreaterThanGreaterThanGreaterThanToken = (SyntaxKind)8286; public const SyntaxKind GreaterThanGreaterThanGreaterThanEqualsToken = (SyntaxKind)8287; + public const SyntaxKind InitKeyword = (SyntaxKind)8443; public const SyntaxKind ManagedKeyword = (SyntaxKind)8445; public const SyntaxKind UnmanagedKeyword = (SyntaxKind)8446; public const SyntaxKind NullableKeyword = (SyntaxKind)8486; diff --git a/analyzers/src/SonarAnalyzer.CFG/SonarAnalyzer.CFG.cs.nuspec b/analyzers/src/SonarAnalyzer.CFG/SonarAnalyzer.CFG.cs.nuspec index 6e102c0e388..496bd4f6645 100644 --- a/analyzers/src/SonarAnalyzer.CFG/SonarAnalyzer.CFG.cs.nuspec +++ b/analyzers/src/SonarAnalyzer.CFG/SonarAnalyzer.CFG.cs.nuspec @@ -2,7 +2,7 @@ SonarAnalyzer.CFG.CSharp - 8.52.0.0 + 8.53.0.0 C# CFG library for SonarAnalyzer SonarSource SonarSource 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/ParameterValidationInMethodWalker.cs b/analyzers/src/SonarAnalyzer.CSharp/Helpers/ParameterValidationInMethodWalker.cs similarity index 88% rename from analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInMethodWalker.cs rename to analyzers/src/SonarAnalyzer.CSharp/Helpers/ParameterValidationInMethodWalker.cs index a627677ac74..2b24e98c81b 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInMethodWalker.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Helpers/ParameterValidationInMethodWalker.cs @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -namespace SonarAnalyzer.Rules.CSharp +namespace SonarAnalyzer.Helpers { internal class ParameterValidationInMethodWalker : SafeCSharpSyntaxWalker { @@ -33,7 +33,7 @@ internal class ParameterValidationInMethodWalker : SafeCSharpSyntaxWalker private readonly SemanticModel semanticModel; private readonly List argumentExceptionLocations = new(); - private bool keepWalking = true; + protected bool keepWalking = true; public IEnumerable ArgumentExceptionLocations => argumentExceptionLocations; @@ -49,9 +49,6 @@ public override void Visit(SyntaxNode node) } } - public override void VisitAwaitExpression(AwaitExpressionSyntax node) => - keepWalking = false; - public override void VisitThrowStatement(ThrowStatementSyntax node) { // When throw is like `throw new XXX` where XXX derives from ArgumentException, save location @@ -61,6 +58,7 @@ public override void VisitThrowStatement(ThrowStatementSyntax node) { argumentExceptionLocations.Add(node.Expression.GetLocation()); } + // there is no need to visit children } @@ -68,9 +66,14 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (node.IsMemberAccessOnKnownType("ThrowIfNull", KnownType.System_ArgumentNullException, semanticModel)) { + // "ThrowIfNull" returns void so it cannot be an argument. We can stop. argumentExceptionLocations.Add(node.GetLocation()); } - // "ThrowIfNull" returns void so it cannot be an argument. We can stop. + else + { + // Need to check the children of this node because of the pattern (await SomeTask()).Invocation() + base.VisitInvocationExpression(node); + } } } } diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs index 10554b311a4..8ddcf45946b 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs @@ -40,7 +40,9 @@ public sealed class DisposableNotDisposed : SonarDiagnosticAnalyzer KnownType.System_Net_Sockets_TcpClient, KnownType.System_Net_Sockets_UdpClient); - private static readonly ISet DisposeMethods = new HashSet { "Dispose", "Close" }; + private static readonly ImmutableArray DisposableTypes = ImmutableArray.Create(KnownType.System_IDisposable, KnownType.System_IAsyncDisposable); + + private static readonly ISet DisposeMethods = new HashSet { "Dispose", "DisposeAsync", "Close" }; private static readonly ISet FactoryMethods = new HashSet { @@ -142,7 +144,7 @@ public sealed class DisposableNotDisposed : SonarDiagnosticAnalyzer foreach (var simpleAssignment in simpleAssignments) { - if (!simpleAssignment.Parent.IsKind(SyntaxKind.UsingStatement) + if (!IsNodeInsideUsingStatement(simpleAssignment) && IsInstantiation(simpleAssignment.Right, semanticModel) && semanticModel.GetSymbolInfo(simpleAssignment.Left).Symbol is { } referencedSymbol && IsLocalOrPrivateField(referencedSymbol)) @@ -152,6 +154,22 @@ public sealed class DisposableNotDisposed : SonarDiagnosticAnalyzer } } + private static bool IsNodeInsideUsingStatement(SyntaxNode node) + { + var ancestors = node.AncestorsAndSelf().ToArray(); + + var isPartOfUsingStatement = ancestors + .OfType() + .Any(x => (x.Expression is not null && x.Expression.DescendantNodesAndSelf().Contains(node)) + || (x.Declaration is not null && x.Declaration.DescendantNodesAndSelf().Contains(node))); + + var isPartOfUsingDeclaration = ancestors + .OfType() + .Any(x => x.UsingKeyword() != default); + + return isPartOfUsingStatement || isPartOfUsingDeclaration; + } + private static IEnumerable GetDescendantNodes(INamedTypeSymbol namedType, SyntaxNode typeDeclaration) => namedType.IsTopLevelProgram() ? typeDeclaration.ChildNodes().OfType().Select(x => x.ChildNodes().First()) @@ -185,7 +203,7 @@ private static void ExcludeDisposedAndClosedLocalsAndPrivateFields(SyntaxNode ty } if (name != null - && DisposeMethods.Contains(name.Identifier.Text) + && (DisposeMethods.Contains(name.Identifier.Text) || IsNodeInsideUsingStatement(expression)) && semanticModel.GetSymbolInfo(expression).Symbol is { } referencedSymbol && IsLocalOrPrivateField(referencedSymbol)) { @@ -247,7 +265,7 @@ private static void ExcludeReturnedPassedAndAliasedLocalsAndPrivateFields(Syntax && semanticModel.GetTypeInfo(expression).Type is var type && type.IsAny(TrackedTypes) && semanticModel.GetSymbolInfo(expression).Symbol is IMethodSymbol constructor - && !constructor.Parameters.Any(x => x.Type.Implements(KnownType.System_IDisposable)); + && !constructor.Parameters.Any(x => x.Type.ImplementsAny(DisposableTypes)); private static bool IsDisposableRefStructCreation(ExpressionSyntax expression, SemanticModel semanticModel) => expression.IsAnyKind(SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/InfiniteRecursion.RoslynCfg.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/InfiniteRecursion.RoslynCfg.cs index 21373ee7e68..5bc6c8a46c7 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/InfiniteRecursion.RoslynCfg.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/InfiniteRecursion.RoslynCfg.cs @@ -41,7 +41,7 @@ public void CheckForNoExitProperty(SonarSyntaxNodeReportingContext c, PropertyDe { var cfg = ControlFlowGraph.Create(accessor, c.SemanticModel, c.Cancel); var context = new RecursionContext(c, cfg, propertySymbol, accessor.Keyword.GetLocation(), "property accessor's recursion"); - var walker = new RecursionSearcher(context, !accessor.Keyword.IsKind(SyntaxKind.SetKeyword)); + var walker = new RecursionSearcher(context, !accessor.Keyword.IsAnyKind(SyntaxKind.SetKeyword, SyntaxKindEx.InitKeyword)); walker.CheckPaths(); } } diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInAsyncShouldBeWrapped.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInAsyncShouldBeWrapped.cs index b7e0a4e4545..2943a8c0ed6 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInAsyncShouldBeWrapped.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInAsyncShouldBeWrapped.cs @@ -41,7 +41,7 @@ public sealed class ParameterValidationInAsyncShouldBeWrapped : SonarDiagnosticA return; } - var walker = new ParameterValidationInMethodWalker(c.SemanticModel); + var walker = new ParameterValidationInAsyncWalker(c.SemanticModel); walker.SafeVisit(method); if (walker.ArgumentExceptionLocations.Any()) { @@ -49,5 +49,16 @@ public sealed class ParameterValidationInAsyncShouldBeWrapped : SonarDiagnosticA } }, SyntaxKind.MethodDeclaration); + + private sealed class ParameterValidationInAsyncWalker : ParameterValidationInMethodWalker + { + public ParameterValidationInAsyncWalker(SemanticModel semanticModel) + : base(semanticModel) + { + } + + public override void VisitAwaitExpression(AwaitExpressionSyntax node) => + keepWalking = false; + } } } diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInYieldShouldBeWrapped.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInYieldShouldBeWrapped.cs index ab85302138b..d06306b2932 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInYieldShouldBeWrapped.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInYieldShouldBeWrapped.cs @@ -23,16 +23,14 @@ namespace SonarAnalyzer.Rules.CSharp [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ParameterValidationInYieldShouldBeWrapped : SonarDiagnosticAnalyzer { - internal const string DiagnosticId = "S4456"; + private const string DiagnosticId = "S4456"; private const string MessageFormat = "Split this method into two, one handling parameters check and the other " + "handling the iterator."; - private static readonly DiagnosticDescriptor rule = - DescriptorFactory.Create(DiagnosticId, MessageFormat); - public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); + private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); - protected override void Initialize(SonarAnalysisContext context) - { + protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { @@ -44,13 +42,12 @@ protected override void Initialize(SonarAnalysisContext context) if (walker.HasYieldStatement && walker.ArgumentExceptionLocations.Any()) { - c.ReportIssue(rule.CreateDiagnostic(c.Compilation, methodDeclaration.Identifier.GetLocation(), additionalLocations: walker.ArgumentExceptionLocations)); + c.ReportIssue(Rule.CreateDiagnostic(c.Compilation, methodDeclaration.Identifier.GetLocation(), additionalLocations: walker.ArgumentExceptionLocations)); } }, SyntaxKind.MethodDeclaration); - } - private class ParameterValidationInYieldWalker : ParameterValidationInMethodWalker + private sealed class ParameterValidationInYieldWalker : ParameterValidationInMethodWalker { public bool HasYieldStatement { get; private set; } @@ -62,7 +59,6 @@ public ParameterValidationInYieldWalker(SemanticModel semanticModel) public override void VisitYieldStatement(YieldStatementSyntax node) { HasYieldStatement = true; - base.VisitYieldStatement(node); } } 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.CSharp/sonarpedia.json b/analyzers/src/SonarAnalyzer.CSharp/sonarpedia.json index f50f2c0a936..4aa94652203 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/sonarpedia.json +++ b/analyzers/src/SonarAnalyzer.CSharp/sonarpedia.json @@ -3,5 +3,5 @@ "languages": [ "CSH" ], - "latest-update": "2022-12-16T14:29:21.863815900Z" + "latest-update": "2023-01-27T09:06:22.842339300Z" } \ No newline at end of file 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/src/SonarAnalyzer.VisualBasic/sonarpedia.json b/analyzers/src/SonarAnalyzer.VisualBasic/sonarpedia.json index 06e3fe757d6..3ab3c9d1c94 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/sonarpedia.json +++ b/analyzers/src/SonarAnalyzer.VisualBasic/sonarpedia.json @@ -3,5 +3,5 @@ "languages": [ "VBNET" ], - "latest-update": "2022-12-16T14:30:02.140782800Z" + "latest-update": "2023-01-27T09:06:56.299025700Z" } \ No newline at end of file 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 d56e370520c..40aa172ff41 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/DisposableNotDisposedTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DisposableNotDisposedTest.cs index 4b690c4faea..74a98a08d46 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DisposableNotDisposedTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DisposableNotDisposedTest.cs @@ -30,12 +30,19 @@ public class DisposableNotDisposedTest [TestMethod] public void DisposableNotDisposed() => builder.AddPaths("DisposableNotDisposed.cs") - .WithOptions(ParseOptionsHelper.FromCSharp8) - .AddReferences(MetadataReferenceFacade.SystemNetHttp.Concat(NuGetMetadataReference.FluentAssertions("5.9.0"))) + .WithOptions(ParseOptionsHelper.FromCSharp7) + .AddReferences(MetadataReferenceFacade.SystemNetHttp) .Verify(); #if NET + [TestMethod] + public void DisposableNotDisposed_CSharp8() => + builder.AddPaths("DisposableNotDisposed.CSharp8.cs") + .WithOptions(ParseOptionsHelper.FromCSharp8) + .AddReferences(NuGetMetadataReference.FluentAssertions("5.9.0")) + .Verify(); + [TestMethod] public void DisposableNotDisposed_CSharp9() => builder.AddPaths("DisposableNotDisposed.CSharp9.cs") diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/InfiniteRecursionTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/InfiniteRecursionTest.cs index 5cd750f63bd..bb6e0bd15f0 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/InfiniteRecursionTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/InfiniteRecursionTest.cs @@ -47,6 +47,12 @@ public class InfiniteRecursionTest #if NET + [TestMethod] + public void InfiniteRecursion_RoslynCfg_CSharp9() => + roslynCfg.AddPaths("InfiniteRecursion.RoslynCfg.CSharp9.cs") + .WithOptions(ParseOptionsHelper.FromCSharp9) + .Verify(); + [TestMethod] public void InfiniteRecursion_CSharp11() => roslynCfg.AddPaths("InfiniteRecursion.CSharp11.cs") 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/DisposableNotDisposed.CSharp8.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp8.cs new file mode 100644 index 00000000000..83b471e5921 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp8.cs @@ -0,0 +1,149 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using FluentAssertions.Execution; + +namespace Tests.Diagnostics +{ + public class DisposableNotDisposedAsync + { + private FileStream field_fs1 = new FileStream(@"c:\foo.txt", FileMode.Open); // Compliant - disposed in a public async method + private FileStream field_fs2 = File.Open(@"c:\foo.txt", FileMode.Open); // Compliant - disposed in a public async method + private FileStream field_fs3 = new FileStream(@"c:\foo.txt", FileMode.Open); // FN - the method which disposes it is private, and it's not referenced anywhere + private FileStream field_fs4 = new FileStream(@"c:\foo.txt", FileMode.Open); // Compliant - disposed in a public async ValueTask method + private FileStream field_fs5 = new FileStream(@"c:\foo.txt", FileMode.Open); // Compliant - disposed in a public ValueTask method (without async/await) + private FileStream field_fs6 = new FileStream(@"c:\foo.txt", FileMode.Open); // Compliant - disposed in a public ValueTask method (without async/await) + + public async Task DisposeAsynchronously() + { + await using (var fs = new FileStream(@"c:\foo.txt", FileMode.Open)) // Compliant - automatically disposed with the async using block + { + // do nothing + } + + FileStream fs2; + await using (fs2 = new FileStream(@"c:\foo.txt", FileMode.Open)) + { + // do nothing + } + + FileStream fs3 = new FileStream(@"c:\foo.txt", FileMode.Open); + await using (fs3) + { + // do nothing + } + + FileStream fs4 = new FileStream(@"c:\foo.txt", FileMode.Open); + await using (fs4.ConfigureAwait(false)) + { + // do nothing + } + + FileStream fs5; + await using ((fs5 = new FileStream(@"c:\foo.txt", FileMode.Open)).ConfigureAwait(false)) + { + var fs5_1 = new FileStream(@"c:\foo.txt", FileMode.Open); // Noncompliant + fs5_1 = new FileStream(@"c:\foo.txt", FileMode.Open); // Noncompliant + + using (var fs5_2 = new FileStream(@"c:\foo.txt", FileMode.Open)) + { + fs5_1 = new FileStream(@"c:\foo.txt", FileMode.Open); // Noncompliant + } + } + + FileStream fs6; + await using ((fs6 = File.Open(@"c:\foo.txt", FileMode.Open)).ConfigureAwait(false)) + { + // do nothing + } + + FileStream fs7 = new FileStream(@"c:\foo.txt", FileMode.Open); + await using (var ignored = fs7.ConfigureAwait(false)); + + FileStream fs8 = new FileStream(@"c:\foo.txt", FileMode.Open); + await using var ignored2 = fs8.ConfigureAwait(false); + + using var fs9 = new FileStream(@"c:\foo.txt", FileMode.Open); + + await using var fs10 = new FileStream(@"c:\foo.txt", FileMode.Open); + + await using var fs11 = File.Open(@"c:\foo.txt", FileMode.Open); + + var fs12 = new FileStream(@"c:\foo.txt", FileMode.Open); // Compliant - asynchronously disposed manually + await fs12.DisposeAsync(); + } + + public async Task SomePublicAsyncMethod() + { + await field_fs1.DisposeAsync().ConfigureAwait(false); + await field_fs2.DisposeAsync(); + } + + private async Task SomePrivateAsyncMethod() + { + await field_fs3.DisposeAsync(); + } + + public async ValueTask SomePublicAsyncMethodWithValueTask() + { + await field_fs4.DisposeAsync(); + } + + public ValueTask SomePublicMethodWithValueTask() + { + return field_fs5.DisposeAsync(); + } + + public ValueTask AnotherPublicValueTaskMethod() => field_fs6.DisposeAsync(); + } + + public sealed class ImplementsAsyncDisposable : IAsyncDisposable + { + private readonly FileStream stream; + + public ImplementsAsyncDisposable() + { + stream = new FileStream(@"c:\foo.txt", FileMode.Open); // Compliant - see GitHub issue: https://github.com/SonarSource/sonar-dotnet/issues/5879 + } + + public async ValueTask DisposeAsync() + { + await stream.DisposeAsync(); + } + } + + public class AsyncDisposableTest + { + private ImplementsAsyncDisposable stream = new ImplementsAsyncDisposable(); // Compliant - the rule only tracks specific IDisposable / IAsyncDisposable types + } + + public class FluentAssertionsTest + { + public void FluentAssertionTypes() + { + var scope = new AssertionScope(); // Noncompliant + var s = new FluentAssertions.Execution.AssertionScope(); // Noncompliant + + using var _ = new AssertionScope(); + using (var disposed = new AssertionScope()) + { + } + } + } + + public ref struct Struct + { + public void Dispose() + { + } + } + + public class Consumer + { + public void Method() + { + using var x = new Struct(); + var y = new Struct(); // Noncompliant + } + } +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp9.cs index 3dd2b57c804..330c8860b56 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp9.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp9.cs @@ -97,19 +97,3 @@ public void Foo() var s = new WebClient(); // Noncompliant - another tracked type } } - -// Reproducer for https://github.com/SonarSource/sonar-dotnet/issues/5879 -public class Test : IAsyncDisposable -{ - private readonly FileStream stream; - - public Test() - { - stream = new FileStream("C://some-path", FileMode.CreateNew); // Noncompliant - FP stream is disposed in DisposeAsync - } - - public async ValueTask DisposeAsync() - { - await stream.DisposeAsync(); - } -} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.cs index 48bfd71eb55..57cfd032185 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.cs @@ -1,7 +1,7 @@ -using System.IO; +using System; +using System.IO; using System.Net; using System.Net.Sockets; -using FluentAssertions.Execution; namespace Tests.Diagnostics { @@ -57,6 +57,12 @@ public DisposableNotDisposed(FileStream fs) // do nothing but dispose } + FileStream fs6_1 = new FileStream(@"c:\foo.txt", FileMode.Open); + using (fs6_1) + { + // do nothing but dispose + } + var fs7 = new FileStream(@"c:\foo.txt", FileMode.Open); // Compliant - Dispose() fs7.Dispose(); @@ -98,7 +104,7 @@ public DisposableNotDisposed(FileStream fs) field_fs5 = new FileStream(@"c:\foo.txt", FileMode.Open); // Noncompliant NoOperation(field_fs6); - field_fs6 = new FileStream(@"c:\foo.txt", FileMode.Open); // Compliant - field_fs6 gets passed to a method + field_fs6 = new FileStream(@"c:\foo.txt", FileMode.Open); // FN - field_fs6 is re-assigned a new FileStream (and not disposed) after passing it to a method field_fs7 = new FileStream(@"c:\foo.txt", FileMode.Open); // Noncompliant - even if field_fs7's type is object @@ -118,41 +124,29 @@ private void NoOperation(FileStream fs) { // do nothing } + } - - private void Clear() - { - using var inner_field_fs1 = new FileStream(@"c:\foo.txt", FileMode.Open); - } - - public void FluentAssertionTypes() - { - var scope = new AssertionScope(); // Noncompliant - var s = new FluentAssertions.Execution.AssertionScope(); // Noncompliant - - using var _ = new AssertionScope(); - using (var disposed = new AssertionScope()) { - } - } + public class Empty + { } - public ref struct Struct + public sealed class ImplementsDisposable : IDisposable { - public void Dispose() + private readonly FileStream stream; + + public ImplementsDisposable() { + stream = new FileStream(@"c:\foo.txt", FileMode.Open); // Compliant } - } - public class Consumer - { - public void Method() + public void Dispose() { - using var x = new Struct(); - var y = new Struct(); // Noncompliant + stream.Dispose(); } } - public class Empty + public class DisposableTest { + private ImplementsDisposable stream = new ImplementsDisposable(); // Compliant - the rule only tracks specific IDisposable / IAsyncDisposable types } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/InfiniteRecursion.RoslynCfg.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/InfiniteRecursion.RoslynCfg.CSharp9.cs new file mode 100644 index 00000000000..fe0ca18fe7f --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/InfiniteRecursion.RoslynCfg.CSharp9.cs @@ -0,0 +1,19 @@ +// https://github.com/SonarSource/sonar-dotnet/issues/6646 +namespace Repro_6646 +{ + public class Repro + { + public string Name + { + init // Noncompliant + { + Name = value; + } + } + + public string Arrow + { + init => Arrow = value; // Noncompliant + } + } +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInAsyncShouldBeWrapped.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInAsyncShouldBeWrapped.cs index b4864aa4475..0e4c1df555c 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInAsyncShouldBeWrapped.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInAsyncShouldBeWrapped.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Threading.Tasks; namespace Tests.Diagnostics @@ -55,6 +56,20 @@ private static ArgumentNullException GetArgumentExpression(string name) public class ValidCases { + // https://github.com/SonarSource/sonar-dotnet/issues/6449 + public class Repro_6449 + { + public Task CheckAsync() => Task.FromResult(new int[] { 1 }); + public Task Check2Async() => Task.FromResult(1); + + public async Task HasS4457Async(int request) // Compliant + { + var identifierType = (await CheckAsync()).FirstOrDefault(x => x == request); + if (identifierType == 0) + throw new ArgumentException("message"); + } + } + public static Task FooAsync(string something) { if (something == null) { throw new ArgumentNullException(nameof(something)); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.CSharp10.cs index d7905a6b8f8..cf38aad2e9b 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.CSharp10.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.CSharp10.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace Tests.Diagnostics { @@ -27,6 +28,32 @@ public static class InvalidCases yield break; } + + // For details, check https://github.com/SonarSource/sonar-dotnet/pull/6624. + public static async IAsyncEnumerable AsyncThenYield(object arg) // Noncompliant + { + if (arg is null) + { + throw new ArgumentException(nameof(arg)); +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Secondary + } + + var res = await Task.Run(() => 42); + yield return res; + } + + // For details, check https://github.com/SonarSource/sonar-dotnet/pull/6624. + public static async IAsyncEnumerable NestedAsyncThenYield(object arg) // Noncompliant + { + if (arg is null) + { + throw new ArgumentException(nameof(arg)); +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Secondary + } + + var res = (await Task.Run(() => 42)).GetHashCode(); + yield return res; + } } public static class ValidCases diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.cs index dc8dc267400..c249a8aabd7 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.cs @@ -54,6 +54,20 @@ private static ArgumentNullException GetArgumentExpression(string name) { return new ArgumentNullException(name); } + + // Documenting that this rule fires even if T:ArgumentException has no argument + public static IEnumerable ThrowWithoutArgument(int a) // Noncompliant + { + throw new ArgumentNullException(); // Secondary + yield return 42; + } + + // Documenting that this rule fires even if T:ArgumentException has an ad-hoc argument + public static IEnumerable ThrowWithAdHocArgument(int a) // Noncompliant + { + throw new ArgumentNullException("i am not a parameter name"); // Secondary + yield return 42; + } } public static class ValidCases diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Sonar/EmptyNullableValueAccess.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Sonar/EmptyNullableValueAccess.CSharp9.cs index d1767f71819..8edd9b0bc2a 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Sonar/EmptyNullableValueAccess.CSharp9.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Sonar/EmptyNullableValueAccess.CSharp9.cs @@ -125,3 +125,27 @@ public partial class Partial var v = nullable.Value; // Noncompliant } } + +// https://github.com/SonarSource/sonar-dotnet/issues/6682 +public struct Repro_6682 +{ + public bool SomeProperty { get; } + + public void PatternMatching(Repro_6682? arg, bool condition) + { + if (condition) + { + arg = null; + } + + if (arg is { SomeProperty: true }) // A null check + { + var value = arg.Value; // Noncompliant FP + } + + if (arg is { }) // A null check + { + var value = arg.Value; // Noncompliant FP + } + } +} 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 diff --git a/its/pom.xml b/its/pom.xml index fb81166a246..1bf9d63ae06 100644 --- a/its/pom.xml +++ b/its/pom.xml @@ -6,7 +6,7 @@ org.sonarsource.dotnet sonar-dotnet - 8.52-SNAPSHOT + 8.53-SNAPSHOT com.sonarsource.it @@ -20,7 +20,7 @@ - 5.10.0.59947 + 5.11.0.60783 -server diff --git a/its/src/test/java/com/sonar/it/shared/TestUtils.java b/its/src/test/java/com/sonar/it/shared/TestUtils.java index d7d19d23984..fa60ead3386 100644 --- a/its/src/test/java/com/sonar/it/shared/TestUtils.java +++ b/its/src/test/java/com/sonar/it/shared/TestUtils.java @@ -141,7 +141,7 @@ private static String getProjectBaseDir(Path projectDir, String subProjectName) private static Build newScanner(Path projectDir) { // We need to set the fallback version to run from inside the IDE when the property isn't set return ScannerForMSBuild.create(projectDir.toFile()) - .setScannerVersion(System.getProperty("scannerMsbuild.version", "5.10.0.59947")) + .setScannerVersion(System.getProperty("scannerMsbuild.version", "5.11.0.60783")) // In order to be able to run tests on Azure pipelines, the AGENT_BUILDDIRECTORY environment variable // needs to be set to the analyzed project directory. diff --git a/pom.xml b/pom.xml index b5ab3964581..17cc7e342d6 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ org.sonarsource.dotnet sonar-dotnet - 8.52-SNAPSHOT + 8.53-SNAPSHOT pom .NET Analyzers parent diff --git a/scripts/rspec/rspec-templates/Rule.Base.cs b/scripts/rspec/rspec-templates/Rule.Base.cs index 6cbcb64a80f..31080ba1f50 100644 --- a/scripts/rspec/rspec-templates/Rule.Base.cs +++ b/scripts/rspec/rspec-templates/Rule.Base.cs @@ -18,11 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using SonarAnalyzer.Common; -using SonarAnalyzer.Helpers; - namespace SonarAnalyzer.Rules; public abstract class $DiagnosticClassName$Base : SonarDiagnosticAnalyzer diff --git a/scripts/rspec/rspec-templates/Rule.CS.cs b/scripts/rspec/rspec-templates/Rule.CS.cs index b2ab0f3c0ec..f3f35120fea 100644 --- a/scripts/rspec/rspec-templates/Rule.CS.cs +++ b/scripts/rspec/rspec-templates/Rule.CS.cs @@ -18,15 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using SonarAnalyzer.Common; -using SonarAnalyzer.Helpers; - namespace SonarAnalyzer.Rules.CSharp; [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -40,7 +31,7 @@ public sealed class $DiagnosticClassName$ : SonarDiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => - context.RegisterSyntaxNodeActionInNonGenerated(c => + context.RegisterNodeAction(c => { var node = c.Node; if (true) diff --git a/scripts/rspec/rspec-templates/Rule.VB.cs b/scripts/rspec/rspec-templates/Rule.VB.cs index ea26f71b0fd..7a2eb22d765 100644 --- a/scripts/rspec/rspec-templates/Rule.VB.cs +++ b/scripts/rspec/rspec-templates/Rule.VB.cs @@ -18,15 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.VisualBasic; -using Microsoft.CodeAnalysis.VisualBasic.Syntax; -using SonarAnalyzer.Common; -using SonarAnalyzer.Helpers; - namespace SonarAnalyzer.Rules.VisualBasic; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] @@ -40,7 +31,7 @@ public sealed class $DiagnosticClassName$ : SonarDiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => - context.RegisterSyntaxNodeActionInNonGenerated(c => + context.RegisterNodeAction(c => { var node = c.Node; if (true) diff --git a/scripts/version/Version.props b/scripts/version/Version.props index 77df7836fd4..f2a2379e7b7 100644 --- a/scripts/version/Version.props +++ b/scripts/version/Version.props @@ -1,8 +1,8 @@ - 8.52.0 + 8.53.0 0 - 8.52 + 8.53 $(Sha1) $(BranchName) $(MainVersion).$(BuildNumber) diff --git a/sonar-csharp-plugin/pom.xml b/sonar-csharp-plugin/pom.xml index 5e0401b77ba..f34c2683811 100644 --- a/sonar-csharp-plugin/pom.xml +++ b/sonar-csharp-plugin/pom.xml @@ -6,7 +6,7 @@ org.sonarsource.dotnet sonar-dotnet - 8.52-SNAPSHOT + 8.53-SNAPSHOT sonar-csharp-plugin @@ -100,7 +100,7 @@ org.mockito mockito-core - 5.0.0 + 5.1.1 test diff --git a/sonar-dotnet-shared-library/pom.xml b/sonar-dotnet-shared-library/pom.xml index 3861ae6a47a..4237acb33a9 100644 --- a/sonar-dotnet-shared-library/pom.xml +++ b/sonar-dotnet-shared-library/pom.xml @@ -6,7 +6,7 @@ org.sonarsource.dotnet sonar-dotnet - 8.52-SNAPSHOT + 8.53-SNAPSHOT sonar-dotnet-shared-library @@ -87,7 +87,7 @@ org.mockito mockito-core - 5.0.0 + 5.1.1 test diff --git a/sonar-vbnet-plugin/pom.xml b/sonar-vbnet-plugin/pom.xml index 3cdd1160119..eeb433d5b3d 100644 --- a/sonar-vbnet-plugin/pom.xml +++ b/sonar-vbnet-plugin/pom.xml @@ -6,7 +6,7 @@ org.sonarsource.dotnet sonar-dotnet - 8.52-SNAPSHOT + 8.53-SNAPSHOT sonar-vbnet-plugin @@ -100,7 +100,7 @@ org.mockito mockito-core - 5.0.0 + 5.1.1 test